mdmeld 0.1.0
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/LICENSE +21 -0
- package/README.md +100 -0
- package/dist/chunk-UF532LMZ.js +426 -0
- package/dist/chunk-UF532LMZ.js.map +1 -0
- package/dist/chunk-UJZ43GZC.js +128 -0
- package/dist/chunk-UJZ43GZC.js.map +1 -0
- package/dist/cli/index.js +90 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/core/index.cjs +609 -0
- package/dist/core/index.cjs.map +1 -0
- package/dist/core/index.d.cts +71 -0
- package/dist/core/index.d.ts +71 -0
- package/dist/core/index.js +570 -0
- package/dist/core/index.js.map +1 -0
- package/dist/finalize-NGDCBCTD.js +84 -0
- package/dist/finalize-NGDCBCTD.js.map +1 -0
- package/dist/pack-ODWNG5OH.js +84 -0
- package/dist/pack-ODWNG5OH.js.map +1 -0
- package/dist/release/SHA256SUMS +1 -0
- package/dist/release/SHA256SUMS.asc +16 -0
- package/dist/release/SHA256SUMS.minisig +4 -0
- package/dist/release/SHA512SUMS +1 -0
- package/dist/release/SHA512SUMS.asc +16 -0
- package/dist/release/SHA512SUMS.minisig +4 -0
- package/dist/release/mdmeld-0.1.0.tgz +0 -0
- package/dist/release/mdmeld-minisign.pub +2 -0
- package/dist/release/mdmeld-release-signing-key.asc +64 -0
- package/dist/release/release-notes-v0.1.0.md +29 -0
- package/dist/unpack-2BBZMGLH.js +25 -0
- package/dist/unpack-2BBZMGLH.js.map +1 -0
- package/package.json +65 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/core/index.ts","../../src/core/constants.ts","../../src/core/backticks.ts","../../src/core/base64.ts","../../src/core/detect.ts","../../src/core/hash.ts","../../src/core/syntax.ts","../../src/core/yaml.ts","../../src/core/pack.ts","../../src/core/unpack.ts"],"sourcesContent":["export { pack } from \"./pack.js\";\nexport type {\n\tCheckResult,\n\tFileEntry,\n\tHashAlgorithm,\n\tManifest,\n\tPackOptions,\n\tPositionMetadata,\n\tUnpackResult,\n\tVirtualFile,\n} from \"./types.js\";\nexport { check, unpack } from \"./unpack.js\";\n","export const FORMAT_VERSION = \"1.0.0\";\n\n/** Minimum backtick count for code fences */\nexport const MIN_BACKTICK_COUNT = 4;\n\n/** Maximum backtick count allowed */\nexport const MAX_BACKTICK_COUNT = 8;\n\n/** Placeholder value for hashes that haven't been computed yet */\nexport const HASH_PLACEHOLDER = \"HASH_PLACEHOLDER\";\n\n/** Self-describing header comment prepended to every archive */\nexport const ARCHIVE_HEADER = `<!-- MDMeld v1.0 archive — a directory tree encoded as markdown.\n Read the manifest below for file listing and positions.\n Spec: https://github.com/3leaps/mdmeld -->`;\n","import { MAX_BACKTICK_COUNT, MIN_BACKTICK_COUNT } from \"./constants.js\";\n\n/**\n * Scan a string and return the maximum run of consecutive backticks found.\n */\nexport function scanForMaxBackticks(content: string): number {\n\tlet max = 0;\n\tlet current = 0;\n\n\tfor (let i = 0; i < content.length; i++) {\n\t\tif (content[i] === \"`\") {\n\t\t\tcurrent++;\n\t\t\tif (current > max) max = current;\n\t\t} else {\n\t\t\tcurrent = 0;\n\t\t}\n\t}\n\n\treturn max;\n}\n\n/**\n * Determine the backtick count needed to safely fence all file contents.\n * Returns the count, or null if the content cannot be safely fenced\n * (exceeds MAX_BACKTICK_COUNT).\n */\nexport function resolveBacktickCount(maxFoundInContent: number): number | null {\n\tconst needed = Math.max(maxFoundInContent + 1, MIN_BACKTICK_COUNT);\n\tif (needed > MAX_BACKTICK_COUNT) return null;\n\treturn needed;\n}\n\n/** Generate a string of `count` backticks. */\nexport function generateBackticks(count: number): string {\n\treturn \"`\".repeat(count);\n}\n","const CHARS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\n/** Encode a Uint8Array to a base64 string. Pure JS, no Buffer. */\nexport function encodeBase64(data: Uint8Array): string {\n\tlet result = \"\";\n\tconst len = data.length;\n\tconst remainder = len % 3;\n\tconst mainLen = len - remainder;\n\n\tfor (let i = 0; i < mainLen; i += 3) {\n\t\tconst a = data[i] as number;\n\t\tconst b = data[i + 1] as number;\n\t\tconst c = data[i + 2] as number;\n\t\tresult += CHARS[a >> 2];\n\t\tresult += CHARS[((a & 3) << 4) | (b >> 4)];\n\t\tresult += CHARS[((b & 15) << 2) | (c >> 6)];\n\t\tresult += CHARS[c & 63];\n\t}\n\n\tif (remainder === 1) {\n\t\tconst a = data[mainLen] as number;\n\t\tresult += `${CHARS[a >> 2]}${CHARS[(a & 3) << 4]}==`;\n\t} else if (remainder === 2) {\n\t\tconst a = data[mainLen] as number;\n\t\tconst b = data[mainLen + 1] as number;\n\t\tresult += `${CHARS[a >> 2]}${CHARS[((a & 3) << 4) | (b >> 4)]}${CHARS[(b & 15) << 2]}=`;\n\t}\n\n\treturn result;\n}\n\n/** Build a reverse lookup table for decoding */\nconst DECODE_TABLE = new Uint8Array(128);\nfor (let i = 0; i < CHARS.length; i++) {\n\tDECODE_TABLE[CHARS.charCodeAt(i)] = i;\n}\n\n/** Decode a base64 string to a Uint8Array. Pure JS, no Buffer. */\nexport function decodeBase64(base64: string): Uint8Array {\n\t// Strip whitespace\n\tconst str = base64.replace(/\\s/g, \"\");\n\tconst len = str.length;\n\n\t// Count padding\n\tlet padding = 0;\n\tif (str[len - 1] === \"=\") padding++;\n\tif (str[len - 2] === \"=\") padding++;\n\n\tconst byteLen = (len * 3) / 4 - padding;\n\tconst result = new Uint8Array(byteLen);\n\n\tlet j = 0;\n\tfor (let i = 0; i < len; i += 4) {\n\t\tconst a = DECODE_TABLE[str.charCodeAt(i)] as number;\n\t\tconst b = DECODE_TABLE[str.charCodeAt(i + 1)] as number;\n\t\tconst c = DECODE_TABLE[str.charCodeAt(i + 2)] as number;\n\t\tconst d = DECODE_TABLE[str.charCodeAt(i + 3)] as number;\n\n\t\tresult[j++] = (a << 2) | (b >> 4);\n\t\tif (j < byteLen) result[j++] = ((b & 15) << 4) | (c >> 2);\n\t\tif (j < byteLen) result[j++] = ((c & 3) << 6) | d;\n\t}\n\n\treturn result;\n}\n","/**\n * Detect whether a Uint8Array likely contains text or binary data.\n * Uses byte distribution analysis — no filesystem or MIME dependencies.\n */\nexport function isText(data: Uint8Array): boolean {\n\tif (data.length === 0) return true;\n\n\t// Sample up to 8KB for analysis\n\tconst sample = data.length > 8192 ? data.subarray(0, 8192) : data;\n\tconst len = sample.length;\n\n\tlet nullBytes = 0;\n\tlet controlChars = 0;\n\tlet printableOrWhitespace = 0;\n\n\tfor (let i = 0; i < len; i++) {\n\t\tconst byte = sample[i]!;\n\n\t\tif (byte === 0x00) {\n\t\t\tnullBytes++;\n\t\t} else if (byte === 0x09 || byte === 0x0a || byte === 0x0d) {\n\t\t\t// Common whitespace (tab, LF, CR)\n\t\t\tprintableOrWhitespace++;\n\t\t} else if (byte < 0x20) {\n\t\t\t// Non-whitespace control char\n\t\t\tcontrolChars++;\n\t\t} else if (byte <= 0x7e) {\n\t\t\t// Printable ASCII (0x20–0x7E, includes space)\n\t\t\tprintableOrWhitespace++;\n\t\t}\n\t\t// High bytes (0x80-0xFF) are allowed — could be UTF-8 continuation\n\t}\n\n\t// Any null bytes → binary\n\tif (nullBytes > 0) return false;\n\n\t// More than 5% control characters → binary\n\tif (controlChars / len > 0.05) return false;\n\n\t// At least 80% printable ASCII or common whitespace (per spec)\n\tif (len > 32 && printableOrWhitespace / len < 0.8) return false;\n\n\treturn true;\n}\n","import xxhashInit from \"xxhash-wasm\";\nimport type { HashAlgorithm } from \"./types.js\";\n\n/**\n * SHA-256 digest via Web Crypto.\n * Node 19+ exposes globalThis.crypto; Node 18 requires explicit import.\n * In browsers, globalThis.crypto is always available.\n */\nasync function sha256(data: Uint8Array): Promise<ArrayBuffer> {\n\tif (typeof globalThis.crypto?.subtle !== \"undefined\") {\n\t\treturn globalThis.crypto.subtle.digest(\"SHA-256\", data);\n\t}\n\t// Node 18 fallback\n\tconst { webcrypto } = await import(\"node:crypto\");\n\treturn (webcrypto as typeof globalThis.crypto).subtle.digest(\"SHA-256\", data);\n}\n\nlet xxhashInstance: Awaited<ReturnType<typeof xxhashInit>> | null = null;\n\nasync function getXxhash() {\n\tif (!xxhashInstance) {\n\t\txxhashInstance = await xxhashInit();\n\t}\n\treturn xxhashInstance;\n}\n\n/** Compute a hash of the given string using the specified algorithm. */\nexport async function computeHash(content: string, algorithm: HashAlgorithm): Promise<string> {\n\tif (algorithm === \"xxh64\") {\n\t\tconst xxhash = await getXxhash();\n\t\treturn xxhash.h64ToString(content);\n\t}\n\n\t// sha256 via Web Crypto (works in browser + Node 18+)\n\tconst encoder = new TextEncoder();\n\tconst data = encoder.encode(content);\n\tconst hashBuffer = await sha256(data);\n\tconst hashArray = new Uint8Array(hashBuffer);\n\treturn Array.from(hashArray)\n\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t.join(\"\");\n}\n\n/** Compute a hash of raw bytes. */\nexport async function computeHashBytes(\n\tdata: Uint8Array,\n\talgorithm: HashAlgorithm,\n): Promise<string> {\n\tif (algorithm === \"xxh64\") {\n\t\tconst xxhash = await getXxhash();\n\t\tconst raw = xxhash.h64Raw(data);\n\t\treturn raw.toString(16).padStart(16, \"0\");\n\t}\n\n\tconst hashBuffer = await sha256(data);\n\tconst hashArray = new Uint8Array(hashBuffer);\n\treturn Array.from(hashArray)\n\t\t.map((b) => b.toString(16).padStart(2, \"0\"))\n\t\t.join(\"\");\n}\n","/** Map file extensions to markdown code fence language identifiers. */\nconst EXT_TO_SYNTAX: Record<string, string> = {\n\t// Web\n\t\".html\": \"html\",\n\t\".htm\": \"html\",\n\t\".css\": \"css\",\n\t\".js\": \"javascript\",\n\t\".mjs\": \"javascript\",\n\t\".cjs\": \"javascript\",\n\t\".jsx\": \"jsx\",\n\t\".ts\": \"typescript\",\n\t\".tsx\": \"tsx\",\n\t\".mts\": \"typescript\",\n\t\".cts\": \"typescript\",\n\t\".vue\": \"vue\",\n\t\".svelte\": \"svelte\",\n\t\".astro\": \"astro\",\n\n\t// Data\n\t\".json\": \"json\",\n\t\".jsonc\": \"jsonc\",\n\t\".yaml\": \"yaml\",\n\t\".yml\": \"yaml\",\n\t\".toml\": \"toml\",\n\t\".xml\": \"xml\",\n\t\".csv\": \"csv\",\n\t\".graphql\": \"graphql\",\n\t\".gql\": \"graphql\",\n\n\t// Config\n\t\".env\": \"dotenv\",\n\t\".ini\": \"ini\",\n\t\".conf\": \"conf\",\n\t\".cfg\": \"ini\",\n\n\t// Shell\n\t\".sh\": \"bash\",\n\t\".bash\": \"bash\",\n\t\".zsh\": \"zsh\",\n\t\".fish\": \"fish\",\n\t\".ps1\": \"powershell\",\n\t\".bat\": \"batch\",\n\t\".cmd\": \"batch\",\n\n\t// Systems\n\t\".c\": \"c\",\n\t\".h\": \"c\",\n\t\".cpp\": \"cpp\",\n\t\".cc\": \"cpp\",\n\t\".cxx\": \"cpp\",\n\t\".hpp\": \"cpp\",\n\t\".rs\": \"rust\",\n\t\".go\": \"go\",\n\t\".java\": \"java\",\n\t\".kt\": \"kotlin\",\n\t\".kts\": \"kotlin\",\n\t\".swift\": \"swift\",\n\t\".cs\": \"csharp\",\n\t\".fs\": \"fsharp\",\n\t\".zig\": \"zig\",\n\n\t// Scripting\n\t\".py\": \"python\",\n\t\".rb\": \"ruby\",\n\t\".php\": \"php\",\n\t\".pl\": \"perl\",\n\t\".lua\": \"lua\",\n\t\".r\": \"r\",\n\t\".R\": \"r\",\n\t\".jl\": \"julia\",\n\t\".ex\": \"elixir\",\n\t\".exs\": \"elixir\",\n\t\".erl\": \"erlang\",\n\t\".clj\": \"clojure\",\n\t\".scala\": \"scala\",\n\t\".dart\": \"dart\",\n\n\t// Markup / Docs\n\t\".md\": \"markdown\",\n\t\".mdx\": \"mdx\",\n\t\".rst\": \"rst\",\n\t\".tex\": \"latex\",\n\t\".typ\": \"typst\",\n\n\t// DevOps\n\t\".dockerfile\": \"dockerfile\",\n\t\".tf\": \"terraform\",\n\t\".hcl\": \"hcl\",\n\t\".nix\": \"nix\",\n\n\t// SQL\n\t\".sql\": \"sql\",\n\n\t// Misc\n\t\".diff\": \"diff\",\n\t\".patch\": \"diff\",\n\t\".prisma\": \"prisma\",\n\t\".proto\": \"protobuf\",\n\t\".wasm\": \"wasm\",\n\t\".lock\": \"text\",\n\t\".log\": \"text\",\n\t\".txt\": \"text\",\n};\n\n/** Well-known filenames that map to a syntax */\nconst NAME_TO_SYNTAX: Record<string, string> = {\n\tDockerfile: \"dockerfile\",\n\tMakefile: \"makefile\",\n\tContainerfile: \"dockerfile\",\n\tJustfile: \"makefile\",\n\tVagrantfile: \"ruby\",\n\tGemfile: \"ruby\",\n\tRakefile: \"ruby\",\n\t\".gitignore\": \"gitignore\",\n\t\".gitattributes\": \"gitattributes\",\n\t\".editorconfig\": \"editorconfig\",\n\t\".prettierrc\": \"json\",\n\t\".eslintrc\": \"json\",\n};\n\n/** Get the syntax language identifier for a file path. Defaults to \"text\" per spec. */\nexport function getSyntax(filePath: string): string {\n\t// Check full filename first\n\tconst fileName = filePath.split(\"/\").pop() ?? \"\";\n\tconst byName = NAME_TO_SYNTAX[fileName];\n\tif (byName) return byName;\n\n\t// Then check extension\n\tconst dotIdx = fileName.lastIndexOf(\".\");\n\tif (dotIdx === -1) return \"text\";\n\tconst ext = fileName.slice(dotIdx).toLowerCase();\n\treturn EXT_TO_SYNTAX[ext] ?? \"text\";\n}\n","import jsYaml from \"js-yaml\";\n\n/** Serialize a value to YAML string. */\nexport function dump(value: unknown): string {\n\treturn jsYaml.dump(value, {\n\t\tlineWidth: -1,\n\t\tnoRefs: true,\n\t\tquotingType: '\"',\n\t\tforceQuotes: false,\n\t});\n}\n\n/** Parse a YAML string. */\nexport function load(text: string): unknown {\n\treturn jsYaml.load(text);\n}\n","import { generateBackticks, resolveBacktickCount, scanForMaxBackticks } from \"./backticks.js\";\nimport { encodeBase64 } from \"./base64.js\";\nimport { ARCHIVE_HEADER, FORMAT_VERSION, HASH_PLACEHOLDER } from \"./constants.js\";\nimport { isText } from \"./detect.js\";\nimport { computeHash, computeHashBytes } from \"./hash.js\";\nimport { getSyntax } from \"./syntax.js\";\nimport type { FileEntry, HashAlgorithm, Manifest, PackOptions, VirtualFile } from \"./types.js\";\nimport * as yaml from \"./yaml.js\";\n\nconst TEXT_DECODER = new TextDecoder();\n\n/**\n * Pack an array of virtual files into an mdmeld archive string.\n */\nexport async function pack(files: VirtualFile[], options: PackOptions = {}): Promise<string> {\n\tconst hashAlgorithm: HashAlgorithm = options.hashAlgorithm ?? \"xxh64\";\n\tconst created = options.created ?? new Date().toISOString();\n\n\t// Determine backtick count by scanning all text content\n\tlet maxBackticks = 0;\n\tconst fileInfos: Array<{\n\t\tfile: VirtualFile;\n\t\tisTextFile: boolean;\n\t\ttextContent: string | null;\n\t\tbase64Content: string | null;\n\t}> = [];\n\n\tfor (const file of files) {\n\t\tconst textFile = isText(file.content);\n\n\t\tif (textFile) {\n\t\t\tconst text = TEXT_DECODER.decode(file.content);\n\t\t\tconst found = scanForMaxBackticks(text);\n\t\t\tif (found > maxBackticks) maxBackticks = found;\n\t\t\tfileInfos.push({ file, isTextFile: true, textContent: text, base64Content: null });\n\t\t} else {\n\t\t\tconst b64 = encodeBase64(file.content);\n\t\t\tfileInfos.push({ file, isTextFile: false, textContent: null, base64Content: b64 });\n\t\t}\n\t}\n\n\tconst backtickCount = resolveBacktickCount(maxBackticks);\n\tif (backtickCount === null) {\n\t\tthrow new Error(\n\t\t\t\"Cannot pack: file content contains a backtick run of 8 or more characters, \" +\n\t\t\t\t\"which exceeds the maximum allowed fence width. \" +\n\t\t\t\t\"Remove the excessive backtick sequences or exclude the file.\",\n\t\t);\n\t}\n\tconst fence = generateBackticks(backtickCount);\n\n\t// Build content blocks and manifest entries\n\tconst contentBlocks: string[] = [];\n\tconst fileEntries: FileEntry[] = [];\n\n\tfor (const info of fileInfos) {\n\t\tconst path = info.file.path;\n\t\tlet contentStr: string;\n\t\tlet fenceLang: string;\n\t\tlet type: \"text\" | \"binary\";\n\t\tlet syntax: string;\n\t\tlet encoding: \"base64\" | undefined;\n\n\t\t// File hash is always computed on the original raw bytes (per spec)\n\t\tconst hash = await computeHashBytes(info.file.content, hashAlgorithm);\n\n\t\tif (info.textContent !== null) {\n\t\t\t// Plain text file\n\t\t\tcontentStr = info.textContent;\n\t\t\ttype = \"text\";\n\t\t\tsyntax = getSyntax(path);\n\t\t\tfenceLang = syntax;\n\t\t\tencoding = undefined;\n\t\t} else {\n\t\t\t// Binary or base64-encoded text\n\t\t\tconst b64 = info.base64Content!;\n\t\t\tcontentStr = b64;\n\t\t\ttype = info.isTextFile ? \"text\" : \"binary\";\n\t\t\tsyntax = getSyntax(path);\n\t\t\tfenceLang = \"base64\";\n\t\t\tencoding = \"base64\";\n\t\t}\n\n\t\tconst block = `### ${path}\\n\\n${fence}${fenceLang}\\n${contentStr}\\n${fence}`;\n\t\tcontentBlocks.push(block);\n\n\t\tfileEntries.push({\n\t\t\tpath,\n\t\t\ttype,\n\t\t\tsize: info.file.content.length,\n\t\t\thash,\n\t\t\tsyntax,\n\t\t\t...(encoding ? { encoding } : {}),\n\t\t\tposition: { start: 0, fence: 0, content: 0, length: 0 }, // placeholder\n\t\t});\n\t}\n\n\tconst contentSection = contentBlocks.join(\"\\n\\n\");\n\n\t// Multi-pass position calculation\n\tconst manifest: Manifest = {\n\t\tmdmeld: {\n\t\t\tversion: FORMAT_VERSION,\n\t\t\tcreated,\n\t\t\thash_algorithm: hashAlgorithm,\n\t\t\tbacktick_count: backtickCount,\n\t\t\tfiles: fileEntries,\n\t\t},\n\t\tintegrity: {\n\t\t\tmanifest_hash: HASH_PLACEHOLDER,\n\t\t\tcontent_hash: HASH_PLACEHOLDER,\n\t\t},\n\t};\n\n\t// Pass 1: Estimate manifest size without position data\n\tconst entriesNoPos = fileEntries.map((f) => {\n\t\tconst { position: _, ...rest } = f;\n\t\treturn rest;\n\t});\n\tconst prelimYaml = yaml.dump({\n\t\t...manifest,\n\t\tmdmeld: { ...manifest.mdmeld, files: entriesNoPos },\n\t});\n\tconst prelimFrontmatter = `${ARCHIVE_HEADER}\\n---\\n${prelimYaml}---\\n`;\n\tconst prelimLines = countLines(prelimFrontmatter);\n\n\t// Pass 2: Calculate positions with estimated offset\n\tconst positions1 = calculatePositions(contentSection, prelimLines);\n\tapplyPositions(fileEntries, positions1);\n\n\t// Pass 3: Re-serialize with real positions, check if line count changed\n\tconst withPosYaml = yaml.dump(manifest);\n\tconst withPosFrontmatter = `${ARCHIVE_HEADER}\\n---\\n${withPosYaml}---\\n`;\n\tconst finalLines = countLines(withPosFrontmatter);\n\n\t// Pass 4: If line count changed, recalculate with actual offset\n\tif (finalLines !== prelimLines) {\n\t\tconst positions2 = calculatePositions(contentSection, finalLines);\n\t\tapplyPositions(fileEntries, positions2);\n\t}\n\n\t// Compute integrity hashes\n\t// Content hash covers everything after closing \\n--- delimiter\n\t// In the output format: ...yaml\\n---\\n\\n{content}, so after \\n--- we get \\n\\n{content}\n\tconst rawContentForHash = `\\n\\n${contentSection}`;\n\tconst contentHash = await computeHash(rawContentForHash, hashAlgorithm);\n\tmanifest.integrity.content_hash = contentHash;\n\n\t// Manifest hash includes the `mdmeld:` key line per spec\n\tconst manifestYamlForHash = yaml.dump({ mdmeld: manifest.mdmeld });\n\tconst manifestHash = await computeHash(manifestYamlForHash, hashAlgorithm);\n\tmanifest.integrity.manifest_hash = manifestHash;\n\n\t// Final serialization\n\tconst outputYaml = yaml.dump(manifest);\n\treturn `${ARCHIVE_HEADER}\\n---\\n${outputYaml}---\\n\\n${contentSection}`;\n}\n\n/**\n * Parse the content section and calculate position metadata for each file block.\n * Returns an array of positions, one per file block found.\n */\nfunction calculatePositions(\n\tcontentSection: string,\n\tmanifestLineCount: number,\n): Array<{ start: number; fence: number; content: number; length: number }> {\n\tconst lines = contentSection.split(\"\\n\");\n\tconst positions: Array<{ start: number; fence: number; content: number; length: number }> = [];\n\t// Content starts after manifest + 1 blank line separator\n\tconst offset = manifestLineCount + 1;\n\n\tlet i = 0;\n\twhile (i < lines.length) {\n\t\tconst line = lines[i]!;\n\n\t\tif (line.startsWith(\"### \")) {\n\t\t\tconst start = offset + i + 1; // 1-indexed\n\n\t\t\t// Skip blank line after header\n\t\t\ti++;\n\t\t\tif (i < lines.length && lines[i]!.trim() === \"\") {\n\t\t\t\ti++;\n\t\t\t}\n\n\t\t\t// Expect fence line\n\t\t\tif (i < lines.length && isFenceLine(lines[i]!)) {\n\t\t\t\tconst fenceLine = offset + i + 1;\n\t\t\t\ti++;\n\t\t\t\tconst contentStart = offset + i + 1;\n\n\t\t\t\t// Count content lines until closing fence\n\t\t\t\tlet contentLength = 0;\n\t\t\t\twhile (i < lines.length && !isFenceLine(lines[i]!)) {\n\t\t\t\t\tcontentLength++;\n\t\t\t\t\ti++;\n\t\t\t\t}\n\t\t\t\t// Skip closing fence\n\t\t\t\tif (i < lines.length) i++;\n\n\t\t\t\tpositions.push({\n\t\t\t\t\tstart,\n\t\t\t\t\tfence: fenceLine,\n\t\t\t\t\tcontent: contentStart,\n\t\t\t\t\tlength: contentLength,\n\t\t\t\t});\n\t\t\t}\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n\n\treturn positions;\n}\n\n/** Apply calculated positions to file entries. */\nfunction applyPositions(\n\tentries: FileEntry[],\n\tpositions: Array<{ start: number; fence: number; content: number; length: number }>,\n): void {\n\tfor (let i = 0; i < entries.length; i++) {\n\t\tentries[i]!.position = positions[i]!;\n\t}\n}\n\n/** Count the number of lines in a string (trailing newline doesn't add extra). */\nfunction countLines(s: string): number {\n\tif (s.length === 0) return 0;\n\tlet count = 0;\n\tfor (let i = 0; i < s.length; i++) {\n\t\tif (s[i] === \"\\n\") count++;\n\t}\n\t// If the string doesn't end with a newline, add 1 for the last line\n\tif (s[s.length - 1] !== \"\\n\") count++;\n\treturn count;\n}\n\n/** Check if a line is a code fence (4+ backticks). */\nfunction isFenceLine(line: string): boolean {\n\treturn /^`{4,}/.test(line.trim());\n}\n","import { decodeBase64 } from \"./base64.js\";\nimport { FORMAT_VERSION, HASH_PLACEHOLDER } from \"./constants.js\";\nimport { computeHash, computeHashBytes } from \"./hash.js\";\nimport type { CheckResult, Manifest, UnpackResult, VirtualFile } from \"./types.js\";\nimport * as yaml from \"./yaml.js\";\n\nconst TEXT_ENCODER = new TextEncoder();\n\n/**\n * Unpack an mdmeld archive string into virtual files.\n */\nexport async function unpack(content: string): Promise<UnpackResult> {\n\tconst { manifest, contentSection } = parseArchive(content);\n\tconst files = extractFiles(contentSection, manifest);\n\treturn { files, manifest };\n}\n\n/**\n * Validate an mdmeld archive's integrity without extracting files.\n */\nexport async function check(content: string): Promise<CheckResult> {\n\tconst errors: string[] = [];\n\n\tlet manifest: Manifest;\n\tlet contentSection: string;\n\tlet rawContentAfterDelimiter: string;\n\ttry {\n\t\tconst parsed = parseArchive(content);\n\t\tmanifest = parsed.manifest;\n\t\tcontentSection = parsed.contentSection;\n\t\trawContentAfterDelimiter = parsed.rawContentAfterDelimiter;\n\t} catch (e) {\n\t\treturn { valid: false, errors: [`Parse error: ${(e as Error).message}`] };\n\t}\n\n\tconst algorithm = manifest.mdmeld.hash_algorithm;\n\n\t// Verify content hash (hashed on raw content after closing ---, per spec)\n\tif (\n\t\tmanifest.integrity.content_hash !== HASH_PLACEHOLDER &&\n\t\tmanifest.integrity.content_hash !== \"\"\n\t) {\n\t\tconst actual = await computeHash(rawContentAfterDelimiter, algorithm);\n\t\tif (actual !== manifest.integrity.content_hash) {\n\t\t\terrors.push(\n\t\t\t\t`Content hash mismatch: expected ${manifest.integrity.content_hash}, got ${actual}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// Verify manifest hash\n\tif (\n\t\tmanifest.integrity.manifest_hash !== HASH_PLACEHOLDER &&\n\t\tmanifest.integrity.manifest_hash !== \"\"\n\t) {\n\t\t// Manifest hash includes the `mdmeld:` key line per spec\n\t\tconst manifestYaml = yaml.dump({ mdmeld: manifest.mdmeld });\n\t\tconst actual = await computeHash(manifestYaml, algorithm);\n\t\tif (actual !== manifest.integrity.manifest_hash) {\n\t\t\terrors.push(\n\t\t\t\t`Manifest hash mismatch: expected ${manifest.integrity.manifest_hash}, got ${actual}`,\n\t\t\t);\n\t\t}\n\t}\n\n\t// Verify individual file hashes (hashed on original bytes per spec)\n\tconst files = extractFiles(contentSection, manifest);\n\tfor (let i = 0; i < manifest.mdmeld.files.length; i++) {\n\t\tconst entry = manifest.mdmeld.files[i]!;\n\t\tconst file = files[i];\n\n\t\tif (!file) {\n\t\t\terrors.push(`Missing content block for file: ${entry.path}`);\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (entry.hash === HASH_PLACEHOLDER) continue;\n\n\t\tconst actual = await computeHashBytes(file.content, algorithm);\n\t\tif (actual !== entry.hash) {\n\t\t\terrors.push(`File hash mismatch for ${entry.path}: expected ${entry.hash}, got ${actual}`);\n\t\t}\n\t}\n\n\treturn { valid: errors.length === 0, errors };\n}\n\n/**\n * Parse the archive into its manifest and content section.\n * Returns both the raw content after --- (for hashing) and the normalized\n * content section (leading newlines stripped, for file extraction).\n */\nfunction parseArchive(content: string): {\n\tmanifest: Manifest;\n\tcontentSection: string;\n\trawContentAfterDelimiter: string;\n} {\n\t// Strip optional header comment (<!-- ... -->)\n\tlet input = content;\n\tif (input.startsWith(\"<!--\")) {\n\t\tconst commentEnd = input.indexOf(\"-->\");\n\t\tif (commentEnd !== -1) {\n\t\t\tinput = input.slice(commentEnd + 3).trimStart();\n\t\t}\n\t}\n\n\t// Find YAML frontmatter\n\tif (!input.startsWith(\"---\")) {\n\t\tthrow new Error(\"Missing YAML frontmatter (expected --- delimiter)\");\n\t}\n\n\tconst secondDelim = input.indexOf(\"\\n---\", 3);\n\tif (secondDelim === -1) {\n\t\tthrow new Error(\"Missing closing --- delimiter for YAML frontmatter\");\n\t}\n\n\tconst yamlStr = input.slice(4, secondDelim); // skip initial \"---\\n\"\n\tconst rawContentAfterDelimiter = input.slice(secondDelim + 4); // skip \"\\n---\"\n\tconst contentSection = rawContentAfterDelimiter.replace(/^\\n+/, \"\");\n\n\tconst parsed = yaml.load(yamlStr) as Record<string, unknown>;\n\tif (!parsed || typeof parsed !== \"object\" || !(\"mdmeld\" in parsed)) {\n\t\tthrow new Error(\"Invalid manifest: missing 'mdmeld' key\");\n\t}\n\n\t// Reject unsupported major versions per spec\n\tconst manifest = parsed as unknown as Manifest;\n\tconst archiveMajor = String(manifest.mdmeld.version).split(\".\")[0];\n\tconst supportedMajor = FORMAT_VERSION.split(\".\")[0];\n\tif (archiveMajor !== supportedMajor) {\n\t\tthrow new Error(\n\t\t\t`Unsupported archive version: ${manifest.mdmeld.version} (this parser supports major version ${supportedMajor})`,\n\t\t);\n\t}\n\n\treturn { manifest, contentSection, rawContentAfterDelimiter };\n}\n\n/**\n * Extract files from the content section using the manifest metadata.\n */\nfunction extractFiles(contentSection: string, manifest: Manifest): VirtualFile[] {\n\tconst backtickCount = manifest.mdmeld.backtick_count;\n\tconst fence = \"`\".repeat(backtickCount);\n\tconst files: VirtualFile[] = [];\n\n\t// Build regex to extract file blocks\n\t// Format: ### path\\n\\n````lang\\ncontent\\n````\n\tconst pattern = new RegExp(\n\t\t`### (.+?)\\\\s*\\\\n\\\\s*${fence}([^\\\\n]*)\\\\n([\\\\s\\\\S]*?)\\\\n?${fence}(?=\\\\s|$)`,\n\t\t\"g\",\n\t);\n\n\tfor (const match of contentSection.matchAll(pattern)) {\n\t\tconst path = match[1]!.trim();\n\t\tconst lang = match[2]!.trim();\n\t\tconst rawContent = match[3]!;\n\n\t\t// Find the corresponding manifest entry\n\t\tconst entry = manifest.mdmeld.files.find((f) => f.path === path);\n\t\tconst isEncoded = lang === \"base64\" || entry?.encoding === \"base64\";\n\n\t\tlet content: Uint8Array;\n\t\tif (isEncoded) {\n\t\t\tcontent = decodeBase64(rawContent);\n\t\t} else {\n\t\t\tcontent = TEXT_ENCODER.encode(rawContent);\n\t\t}\n\n\t\tfiles.push({ path, content });\n\t}\n\n\treturn files;\n}\n"],"mappings":"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","names":["xxhashInit","jsYaml"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** A file represented as a path and raw bytes — the universal exchange type. */
|
|
2
|
+
interface VirtualFile {
|
|
3
|
+
/** POSIX relative path, e.g. "src/index.ts" */
|
|
4
|
+
path: string;
|
|
5
|
+
/** Raw file bytes */
|
|
6
|
+
content: Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
type HashAlgorithm = "xxh64" | "sha256";
|
|
9
|
+
interface FileEntry {
|
|
10
|
+
path: string;
|
|
11
|
+
type: "text" | "binary";
|
|
12
|
+
size: number;
|
|
13
|
+
hash: string;
|
|
14
|
+
syntax: string;
|
|
15
|
+
/** Only present for binary/encoded files */
|
|
16
|
+
encoding?: "base64";
|
|
17
|
+
position: PositionMetadata;
|
|
18
|
+
}
|
|
19
|
+
interface PositionMetadata {
|
|
20
|
+
/** Line where the file section header (### path) starts (1-indexed) */
|
|
21
|
+
start: number;
|
|
22
|
+
/** Line where the code fence begins */
|
|
23
|
+
fence: number;
|
|
24
|
+
/** Line where the actual content begins */
|
|
25
|
+
content: number;
|
|
26
|
+
/** Number of lines of content */
|
|
27
|
+
length: number;
|
|
28
|
+
}
|
|
29
|
+
interface Manifest {
|
|
30
|
+
mdmeld: {
|
|
31
|
+
version: string;
|
|
32
|
+
created: string;
|
|
33
|
+
hash_algorithm: HashAlgorithm;
|
|
34
|
+
backtick_count: number;
|
|
35
|
+
files: FileEntry[];
|
|
36
|
+
};
|
|
37
|
+
integrity: {
|
|
38
|
+
manifest_hash: string;
|
|
39
|
+
content_hash: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
interface PackOptions {
|
|
43
|
+
/** Hash algorithm to use. Default: "xxh64" */
|
|
44
|
+
hashAlgorithm?: HashAlgorithm;
|
|
45
|
+
/** ISO timestamp override (for deterministic output in tests) */
|
|
46
|
+
created?: string;
|
|
47
|
+
}
|
|
48
|
+
interface UnpackResult {
|
|
49
|
+
files: VirtualFile[];
|
|
50
|
+
manifest: Manifest;
|
|
51
|
+
}
|
|
52
|
+
interface CheckResult {
|
|
53
|
+
valid: boolean;
|
|
54
|
+
errors: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Pack an array of virtual files into an mdmeld archive string.
|
|
59
|
+
*/
|
|
60
|
+
declare function pack(files: VirtualFile[], options?: PackOptions): Promise<string>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Unpack an mdmeld archive string into virtual files.
|
|
64
|
+
*/
|
|
65
|
+
declare function unpack(content: string): Promise<UnpackResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Validate an mdmeld archive's integrity without extracting files.
|
|
68
|
+
*/
|
|
69
|
+
declare function check(content: string): Promise<CheckResult>;
|
|
70
|
+
|
|
71
|
+
export { type CheckResult, type FileEntry, type HashAlgorithm, type Manifest, type PackOptions, type PositionMetadata, type UnpackResult, type VirtualFile, check, pack, unpack };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/** A file represented as a path and raw bytes — the universal exchange type. */
|
|
2
|
+
interface VirtualFile {
|
|
3
|
+
/** POSIX relative path, e.g. "src/index.ts" */
|
|
4
|
+
path: string;
|
|
5
|
+
/** Raw file bytes */
|
|
6
|
+
content: Uint8Array;
|
|
7
|
+
}
|
|
8
|
+
type HashAlgorithm = "xxh64" | "sha256";
|
|
9
|
+
interface FileEntry {
|
|
10
|
+
path: string;
|
|
11
|
+
type: "text" | "binary";
|
|
12
|
+
size: number;
|
|
13
|
+
hash: string;
|
|
14
|
+
syntax: string;
|
|
15
|
+
/** Only present for binary/encoded files */
|
|
16
|
+
encoding?: "base64";
|
|
17
|
+
position: PositionMetadata;
|
|
18
|
+
}
|
|
19
|
+
interface PositionMetadata {
|
|
20
|
+
/** Line where the file section header (### path) starts (1-indexed) */
|
|
21
|
+
start: number;
|
|
22
|
+
/** Line where the code fence begins */
|
|
23
|
+
fence: number;
|
|
24
|
+
/** Line where the actual content begins */
|
|
25
|
+
content: number;
|
|
26
|
+
/** Number of lines of content */
|
|
27
|
+
length: number;
|
|
28
|
+
}
|
|
29
|
+
interface Manifest {
|
|
30
|
+
mdmeld: {
|
|
31
|
+
version: string;
|
|
32
|
+
created: string;
|
|
33
|
+
hash_algorithm: HashAlgorithm;
|
|
34
|
+
backtick_count: number;
|
|
35
|
+
files: FileEntry[];
|
|
36
|
+
};
|
|
37
|
+
integrity: {
|
|
38
|
+
manifest_hash: string;
|
|
39
|
+
content_hash: string;
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
interface PackOptions {
|
|
43
|
+
/** Hash algorithm to use. Default: "xxh64" */
|
|
44
|
+
hashAlgorithm?: HashAlgorithm;
|
|
45
|
+
/** ISO timestamp override (for deterministic output in tests) */
|
|
46
|
+
created?: string;
|
|
47
|
+
}
|
|
48
|
+
interface UnpackResult {
|
|
49
|
+
files: VirtualFile[];
|
|
50
|
+
manifest: Manifest;
|
|
51
|
+
}
|
|
52
|
+
interface CheckResult {
|
|
53
|
+
valid: boolean;
|
|
54
|
+
errors: string[];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Pack an array of virtual files into an mdmeld archive string.
|
|
59
|
+
*/
|
|
60
|
+
declare function pack(files: VirtualFile[], options?: PackOptions): Promise<string>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Unpack an mdmeld archive string into virtual files.
|
|
64
|
+
*/
|
|
65
|
+
declare function unpack(content: string): Promise<UnpackResult>;
|
|
66
|
+
/**
|
|
67
|
+
* Validate an mdmeld archive's integrity without extracting files.
|
|
68
|
+
*/
|
|
69
|
+
declare function check(content: string): Promise<CheckResult>;
|
|
70
|
+
|
|
71
|
+
export { type CheckResult, type FileEntry, type HashAlgorithm, type Manifest, type PackOptions, type PositionMetadata, type UnpackResult, type VirtualFile, check, pack, unpack };
|