{"id":15879175,"url":"https://github.com/keyz/codestamp","last_synced_at":"2026-02-13T18:32:34.232Z","repository":{"id":57202533,"uuid":"449674891","full_name":"keyz/codestamp","owner":"keyz","description":"A language-agnostic tool for signing and verifying your (codegen'd) files and contents.","archived":false,"fork":false,"pushed_at":"2022-01-25T09:08:21.000Z","size":1706,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"main","last_synced_at":"2026-02-01T14:44:11.945Z","etag":null,"topics":["codegen","codestamp","generate","generator","hash","integrity","language-agnostic","shasum","signature","signedsource","signing","stamp","validate","validation","verification","verify"],"latest_commit_sha":null,"homepage":"https://github.com/keyz/codestamp","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/keyz.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2022-01-19T12:05:16.000Z","updated_at":"2023-03-10T11:02:31.000Z","dependencies_parsed_at":"2022-09-15T13:21:04.966Z","dependency_job_id":null,"html_url":"https://github.com/keyz/codestamp","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"purl":"pkg:github/keyz/codestamp","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keyz%2Fcodestamp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keyz%2Fcodestamp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keyz%2Fcodestamp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keyz%2Fcodestamp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/keyz","download_url":"https://codeload.github.com/keyz/codestamp/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/keyz%2Fcodestamp/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29414278,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-13T06:24:03.484Z","status":"ssl_error","status_checked_at":"2026-02-13T06:23:12.830Z","response_time":78,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["codegen","codestamp","generate","generator","hash","integrity","language-agnostic","shasum","signature","signedsource","signing","stamp","validate","validation","verification","verify"],"created_at":"2024-10-06T03:01:10.548Z","updated_at":"2026-02-13T18:32:34.215Z","avatar_url":"https://github.com/keyz.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# codestamp - Stamp and verify your files and contents\n\n[![npm](https://img.shields.io/npm/v/codestamp)](https://www.npmjs.com/package/codestamp) [![main](https://github.com/keyz/codestamp/actions/workflows/main.yml/badge.svg)](https://github.com/keyz/codestamp/actions/workflows/main.yml)\n\n`codestamp` is a language-agnostic tool for signing and verifying the integrity of your files. It's most useful for guarding codegen'd files against unintentional manual edits, but it also can be used for signing individual files.\n\nIf you're new to codegen, [\"Writing code that writes code — with Hack Codegen\"](https://engineering.fb.com/2015/08/20/open-source/writing-code-that-writes-code-with-hack-codegen/) explains why it's important to sign and verify generated files.\n\n## Table of contents\n\n- [Getting Started](#getting-started)\n  - [TL;DR (CLI Example)](#tldr-cli-example)\n  - [Install](#install)\n  - [More Examples](#more-examples)\n  - [Recommended workflow](#recommended-workflow)\n- [Documentation](#documentation)\n  - [Command Line](#command-line)\n    - [CLI Usage](#cli-usage)\n    - [CLI Options](#cli-options)\n  - [Node.js API](#nodejs-api)\n    - [`runner(...)`](#runner)\n    - [`applyStamp(...)`](#applystamp)\n- [Acknowledgments](#acknowledgments)\n- [License](#license)\n\n## Getting Started\n\n### TL;DR (CLI Example)\n\n```diff\n$ ./your-script-that-generates-types --from ffi.rs,data.json\n# Generates `types.ts` from `ffi.rs` and `data.json`\n\n$ cat types.ts\ntype FFI = ...\n\n$ codestamp types.ts --deps ffi.rs,data.json\n+ /* @generated CodeStamp\u003c\u003c​c1aa4ff2ac747d1192773354ad64d122​\u003e\u003e */\ntype FFI = ...\n\n$ codestamp types.ts --deps ffi.rs,data.json --write\nCodeStamp: 🔏 Stamped `types.ts`.\n\n$ codestamp types.ts --deps ffi.rs,data.json\nCodeStamp: ✅ Verified `types.ts`.\n\n$ codestamp types.ts --deps ffi.rs,data.json --write\nCodeStamp: ✅ Verified `types.ts`.\n\n# If you updated `ffi.rs` but forgot to run the codegen script...\n$ codestamp types.ts --deps ffi.rs,data.json\n- /* @generated CodeStamp\u003c\u003c​c1aa4ff2ac747d1192773354ad64d122​\u003e\u003e */\n+ /* @generated CodeStamp\u003c\u003c​64adca472a2638d8c915fb5d83c688f7​\u003e\u003e */\ntype FFI = ...\n\n$ echo $?\n1\n```\n\n### Install\n\n`codestamp` comes with a simple CLI ([docs](#command-line)) and a Node.js API ([docs](#nodejs-api)).\n\n```bash\n# Install locally\n$ npm install --save-dev codestamp\n\n# Or use npx\n$ npx codestamp@latest\n\n# Or install globally\n$ npm install -g codestamp\n```\n\n### More Examples\n\n- [`examples/basic`](examples/basic/package.json): Simple stamping via the CLI.\n- [`examples/template-python`](examples/template-python/package.json): Use template string to add a Python banner comment via the CLI.\n- [`examples/dynamic-json`](examples/dynamic-json/stamp.js): Use the API to programmatically insert the stamp as a JSON field (via `initialStampPlacer`), and ignore insignificant spaces and new lines in JSON (via `fileTransformerForHashing`).\n- 🙋 [`scripts/generate-docs.ts`](scripts/generate-docs.ts): The README file you're reading is generated and verified by `codestamp`! \u003c!-- \u003cCODESTAMP START\u003e --\u003e\n  - And here's the stamp: `CodeStamp\u003c\u003cd508062897d6b3afb002faedb60e864e\u003e\u003e`\n  \u003c!-- \u003cCODESTAMP END\u003e --\u003e\n\n### Recommended workflow\n\n1/ Codegen your files as usual, then run `codestamp` to add a stamp.\n\n- `codestamp` computes a deterministic hash (e.g., `CodeStamp\u003c\u003c​c1aa4ff2ac747d1192773354ad64d122​\u003e\u003e`) from the contents of your target file and all dependencies.\n- By default, `codestamp` inserts the stamp as a banner comment. You can use the CLI (`--template`) to make small tweaks, or use the Node.js API to dynamically place the stamp (see [`examples/dynamic-json/stamp.js`](examples/dynamic-json/stamp.js)).\n\n2/ Run `codestamp` as a Git [pre-commit hook](https://github.com/typicode/husky) and on CI; treat it as a linter for your codegen'd files.\n\n- The `codestamp` CLI will verify the stamp against the files, and `exit(1)` when the stamp is invalid.\n\n3/ Profit\n\n## Documentation\n\n### Command Line\n\n#### CLI Usage\n\n```bash\n$ codestamp target_file [options]\n```\n\n#### CLI Options\n\n##### `-w, --write` (`boolean`)\n\nRewrite the file in-place. Without this flag, `codestamp` runs in verification mode: it prints the diff to `stderr` and `exit(1)` when the stamp is invalid.\n\n##### `-d, --deps` (comma-separated `string`)\n\nOne or more file paths or globs. The stamp hash is computed from the target file's content and all dependencies.\n\nMake sure to quote the globs to let `codestamp` expand the globs, rather than your shell.\n\nExample:\n\n```bash\n$ codestamp target.ts --deps 'data/foo.json'\n$ codestamp target.ts --deps 'data/foo.json,types/*.ts'\n$ codestamp target.ts --deps 'data/**/*.json,types/*.ts'\n```\n\n##### `-t, --template` (`string`)\n\nA template string for placing the stamp. `codestamp` will replace `%STAMP%` with the stamp, and `%CONTENT%` with the rest of content.\n\nUse the Node.js API (see `TStampPlacer`) to dynamically place the stamp.\n\nExample:\n\n```bash\n$ codestamp target.py --template '# @codegen %STAMP%\\\\n%CONTENT%'\n```\n\n##### `-h, --help`\n\nThis help guide.\n\n### Node.js API\n\n#### `runner(...)`\n\nReads contents from disk, then writes and verifies the stamp. The CLI is a thin wrapper around `runner(...)`.\n\n[`examples/dynamic-json`](examples/dynamic-json/stamp.js) is a simple example of using the `runner`. It uses the API to programmatically insert the stamp as a JSON field (via `initialStampPlacer`), and ignore insignificant spaces and new lines in JSON (via `fileTransformerForHashing`).\n\nYou can also reference how this README/documentation file is generated, stamped, and verified in [`scripts/generate-docs.ts`](scripts/generate-docs.ts).\n\n\u003c!-- \u003cDOCSTART TARGET runner\u003e --\u003e\n\n```typescript\n/**\n * Reads contents from disk and verifies the stamp.\n *\n * - When `shouldWrite` is true, the file will be rewritten in-place.\n * - Otherwise, `codestamp` will run in verification mode -- it won't\n *   write to disk.\n *\n * @throws On I/O errors\n * @param param - See {@link TRunnerParam}\n * @returns See {@link TRunnerResult}\n */\nexport async function runner({\n  targetFilePath,\n  dependencyGlobList,\n  shouldWrite,\n  initialStampPlacer,\n  initialStampRemover,\n  fileTransformerForHashing = ({ content }) =\u003e content,\n  cwd = process.cwd(),\n  silent = false,\n}: {\n  targetFilePath: string;\n  dependencyGlobList: Array\u003cstring\u003e;\n  shouldWrite: boolean;\n  initialStampPlacer?: TStampPlacer;\n  initialStampRemover?: TStampRemover;\n  fileTransformerForHashing?: TRunnerFileTransformerForHashing;\n  cwd?: string;\n  silent?: boolean;\n}): Promise\u003cTRunnerResult\u003e;\n```\n\n\u003c!-- \u003cDOCEND TARGET runner\u003e --\u003e\n\n##### `TRunnerParam`\n\nParameter for [`runner(...)`](#runner).\n\n\u003c!-- \u003cDOCSTART TARGET TRunnerParam\u003e --\u003e\n\n```typescript\n/**\n * Parameter for {@link runner}.\n */\nexport type TRunnerParam = {\n  /**\n   * Path to the target file that will be verified by `codestamp`.\n   */\n  targetFilePath: string;\n  /**\n   * A list of file paths and/or globs. The stamp hash is computed\n   * from the target file's content and all dependencies.\n   *\n   * Pass an empty array if you only want to stamp a standalone file.\n   */\n  dependencyGlobList: Array\u003cstring\u003e;\n  /**\n   * Whether the file should be rewritten in-place. Without this flag,\n   * `codestamp` will run in verification mode -- it won't write to\n   * disk.\n   */\n  shouldWrite: boolean;\n  /**\n   * Use it to specify where the stamp should be placed **initially**.\n   * See {@link TStampPlacer}.\n   */\n  initialStampPlacer?: TStampPlacer;\n  /**\n   * Use it to remove (and update) a previously applied **custom**\n   * stamp. See {@link TStampRemover} and {@link TStampPlacer}.\n   */\n  initialStampRemover?: TStampRemover;\n  /**\n   * Use it to ignore insignificant changes and make the stamp less\n   * sensitive. See {@link TRunnerFileTransformerForHashing}\n   *\n   * @defaultValue `({content}) =\u003e content`\n   */\n  fileTransformerForHashing?: TRunnerFileTransformerForHashing;\n  /**\n   * For `glob`: the current working directory in which to search.\n   *\n   * @defaultValue `process.cwd()`\n   */\n  cwd?: string;\n  /**\n   * Whether the runner should write to `stdout` and `stderr`.\n   *\n   * @defaultValue `false`\n   */\n  silent?: boolean;\n};\n```\n\n\u003c!-- \u003cDOCEND TARGET TRunnerParam\u003e --\u003e\n\n##### `TRunnerResult`\n\nReturn type for [`runner(...)`](#runner).\n\n\u003c!-- \u003cDOCSTART TARGET TRunnerResult\u003e --\u003e\n\n```typescript\ntype DistributiveIntersection\u003cUnion, T\u003e = Union extends {} ? Union \u0026 T : never;\n\n/**\n * The return type is based on {@link TApplyStampResult}, with the\n * addition of some runner-specific fields.\n */\nexport type TRunnerResult = DistributiveIntersection\u003c\n  TApplyStampResult,\n  {\n    /**\n     * Indicates whether the runner wrote to disk. Determined by\n     * `shouldWrite`.\n     *\n     * For \"OK\" and \"ERROR\" statuses, the result is always `false`.\n     */\n    didWrite: boolean;\n    /**\n     * Indicates whether the caller should `exit(1)` if desired.\n     * Useful for running on CI.\n     */\n    shouldFatalIfDesired: boolean;\n  }\n\u003e;\n```\n\n\u003c!-- \u003cDOCEND TARGET TRunnerResult\u003e --\u003e\n\n##### `TRunnerFileTransformerForHashing`\n\nIgnore insignificant changes and make the stamp less sensitive.\n\n\u003c!-- \u003cDOCSTART TARGET TRunnerFileTransformerForHashing\u003e --\u003e\n\n````typescript\n/**\n * Use it to ignore insignificant changes and make the stamp less\n * sensitive.\n *\n * Content will be transformed before hashing. The transformer only\n * applies to hashing (the stamp) and does not affect the final\n * content output.\n *\n * @example Ignore spacing and new lines in JSON\n *\n * ```typescript\n * ({content, absoluteFilePath}) =\u003e {\n *   if (path.extname(absoluteFilePath) === \".json\") {\n *     return JSON.stringify(JSON.parse(content));\n *   }\n *\n *   return content;\n * }\n * ```\n *\n * @example Always exclude the stamp line from hashing\n *\n * ```typescript\n * (param) =\u003e {\n *   if (param.type !== \"TARGET\") {\n *     return param.content;\n *   }\n *\n *   return param.content\n *     .split(\"\\n\")\n *     .filter((line) =\u003e !line.includes(param.stamp))\n *     .join(\"\\n\");\n * }\n * ```\n */\nexport type TRunnerFileTransformerForHashing = (\n  param: TRunnerFileTransformerParam\n) =\u003e string;\n\ntype TRunnerFileTransformerParam =\n  | {\n      type: \"DEPENDENCY\";\n      content: string;\n      absoluteFilePath: string;\n    }\n  | {\n      type: \"TARGET\";\n      content: string;\n      stamp: string;\n      absoluteFilePath: string;\n    };\n````\n\n\u003c!-- \u003cDOCEND TARGET TRunnerFileTransformerForHashing\u003e --\u003e\n\n#### `applyStamp(...)`\n\n`applyStamp(...)` works on strings, not file paths. In other words, it's a low-level building block for stamping and verification.\n\nYou likely won't need this function, unless you want to verify content without file system I/O.\n\n\u003c!-- \u003cDOCSTART TARGET applyStamp\u003e --\u003e\n\n```typescript\n/**\n * Given a list of dependencies and a target content,\n * deterministically add or update a stamp.\n *\n * @param param - See {@link TApplyStampParam}\n * @returns See {@link TApplyStampResult}\n */\nexport function applyStamp({\n  dependencyContentList,\n  targetContent,\n  initialStampPlacer,\n  initialStampRemover,\n  contentTransformerForHashing = ({ content }) =\u003e content,\n}: {\n  dependencyContentList: Array\u003cstring\u003e;\n  targetContent: string;\n  initialStampPlacer?: TStampPlacer;\n  initialStampRemover?: TStampRemover;\n  contentTransformerForHashing?: (param: {\n    content: string;\n    stamp: string;\n  }) =\u003e string;\n}): TApplyStampResult;\n```\n\n\u003c!-- \u003cDOCEND TARGET applyStamp\u003e --\u003e\n\n##### `TApplyStampParam`\n\nParameter for [`applyStamp(...)`](#applystamp).\n\n\u003c!-- \u003cDOCSTART TARGET TApplyStampParam\u003e --\u003e\n\n````typescript\n/**\n * Parameter for {@link applyStamp}.\n */\nexport type TApplyStampParam = {\n  /**\n   * A list of strings that are contents of dependencies.\n   *\n   * Pass an empty array if there are no dependencies.\n   *\n   * NOTE: order matters.\n   */\n  dependencyContentList: Array\u003cstring\u003e;\n  /**\n   * The content to stamp.\n   */\n  targetContent: string;\n  /**\n   * Use it to specify where the stamp should be placed **initially**.\n   * See {@link TStampPlacer} and {@link TStampRemover}.\n   *\n   * @defaultValue {@link defaultInitialStampPlacer}\n   */\n  initialStampPlacer?: TStampPlacer;\n  /**\n   * Use it to remove (and update) a previously applied **custom**\n   * stamp. See {@link TStampRemover} and {@link TStampPlacer}.\n   */\n  initialStampRemover?: TStampRemover;\n  /**\n   * Use it to ignore insignificant changes and make the stamp less\n   * sensitive.\n   *\n   * Content will be transformed before hashing. The transformer only\n   * applies to hashing (the stamp) and does not affect the final\n   * content output.\n   *\n   * @example Ignore spacing and new lines in JSON\n   *\n   * ```typescript\n   * ({ content }) =\u003e JSON.stringify(JSON.parse(content))\n   * ```\n   *\n   * @example Always exclude the stamp line from hashing\n   *\n   * ```typescript\n   * ({ content, stamp }) =\u003e\n   *   content.split(\"\\n\").filter((line) =\u003e !line.includes(stamp)).join(\"\\n\")\n   * ```\n   *\n   * @defaultValue `({content}) =\u003e content`\n   */\n  contentTransformerForHashing?: (param: {\n    content: string;\n    stamp: string;\n  }) =\u003e string;\n};\n````\n\n\u003c!-- \u003cDOCEND TARGET TApplyStampParam\u003e --\u003e\n\n##### `TStampPlacer`\n\nSpecify where the initial stamp should be placed.\n\n\u003c!-- \u003cDOCSTART TARGET TStampPlacer\u003e --\u003e\n\n````typescript\n/**\n * A function or a template string for placing the stamp. It's\n * recommended to use the function form when using the Node API.\n *\n * Use it to specify where the stamp should be placed **initially**.\n * If you update `initialStampPlacer` from another **custom** placer,\n * you must also define `initialStampRemover` ({@link TStampRemover})\n * to instruct how your previous stamp should be removed. Otherwise\n * the format change won't be applied (because `codestamp` doesn't\n * have enough context to make a safe change), although the stamp\n * itself will always be updated correctly.\n *\n * NOTE: A single and complete stamp must be returned as-is from the\n * function or included in the string.\n *\n * - Function type: `({content: string, stamp: string}) =\u003e string`.\n * - Template string: A string that contains two special formatters,\n *   `%STAMP%` and `%CONTENT%`. `codestamp` will replace `%STAMP%`\n *   with the stamp, and `%CONTENT%` with the rest of content.\n *\n * NOTE: When `initialStampPlacer` is invoked, it's guaranteed that\n * the value of `content` does not include `stamp`.\n *\n * ```typescript\n * content.indexOf(stamp) === -1 // guaranteed\n * ```\n *\n * @example Add a JS banner comment\n *\n * ```typescript\n * ({ content, stamp }) =\u003e `// @generated ${stamp} DO NOT EDIT BY HAND\\n${content}`;\n *\n * // Template string equivalent:\n * `// @generated %STAMP% DO NOT EDIT BY HAND\\n%CONTENT%`\n * ```\n *\n * @example Add a Python banner comment\n *\n * ```typescript\n * ({ content, stamp }) =\u003e `# @codegen ${stamp}\\n${content}`;\n *\n * // Template string equivalent:\n * `# @codegen %STAMP%\\n%CONTENT%`\n * ```\n *\n * @example Dynamically place the stamp as a JSON field\n *\n * ```typescript\n * ({ content, stamp }) =\u003e {\n *   const stampedObject = {...JSON.parse(content), stamp};\n *   return JSON.stringify(stampedObject, null, 2);\n * }\n * ```\n */\nexport type TStampPlacer = TFormatter | string;\n\nexport type TFormatter = ({\n  content,\n  stamp,\n}: {\n  content: string;\n  stamp: string;\n}) =\u003e string;\n\nconst defaultInitialStampPlacer: TFormatter = ({ content, stamp }) =\u003e\n  `/* @generated ${stamp} */\\n${content}`;\n````\n\n\u003c!-- \u003cDOCEND TARGET TStampPlacer\u003e --\u003e\n\n##### `TStampRemover`\n\nSpecify how the initial stamp should be removed and updated.\n\n\u003c!-- \u003cDOCSTART TARGET TStampRemover\u003e --\u003e\n\n````typescript\n/**\n * The inverse of `initialStampPlacer` ({@link TStampPlacer}). Use it\n * to remove (and update) a previously applied **custom** stamp (the\n * default stamp will always be removed automatically).\n *\n * If you update `initialStampPlacer` from another custom placer, you\n * must define this function to instruct how your previous stamp\n * should be removed. Otherwise the format change won't be applied,\n * although the stamp itself will always be updated correctly.\n *\n * NOTE: When `initialStampRemover` is invoked, it's guaranteed that\n * the value of `content` includes `stamp`.\n *\n * NOTE: In some scenarios, you might find it easier to just\n * completely regenerate the file. Instead of writing to and reading\n * from the same target file, introduce an intermediate representation\n * as your source of truth, and only write to the final target file.\n *\n * ```typescript\n * content.indexOf(stamp) !== -1 // guaranteed\n * ```\n *\n * In other words, `initialStampRemover` won't be called if `content`\n * doesn't contain a `stamp` yet.\n *\n * @example Update a custom Python multiline banner comment\n *\n * ```typescript\n * {\n *   initialStampPlacer: ({content, stamp}) =\u003e {\n *     return `# GENERATED ${stamp}\\n# DO NOT EDIT BY HAND\\n${content}`;\n *   },\n *   initialStampRemover: ({content, stamp}) =\u003e {\n *     const contentLineList = content.split('\\n');\n *     const indexOfStamp = contentLineList.findIndex(\n *       line =\u003e line.includes(stamp),\n *     );\n *\n *     contentLineList.splice(indexOfStamp, 2);\n *     return contentLineList.join('\\n');\n *   },\n * }\n * ```\n *\n * @example Recommended: place invisible markers to specify the range\n * of insertion and deletion. This is a much more robust approach.\n *\n * ```typescript\n * // First, put START and END markers in your target file, such as:\n * // \u003c!-- \u003cCODESTAMP START\u003e --\u003e\n * //\n * // \u003c!-- \u003cCODESTAMP END\u003e --\u003e\n *\n * {\n *   initialStampPlacer: ({ content, stamp }) =\u003e {\n *     const lineList = content.split(\"\\n\");\n *     const startIndex = lineList.findIndex((line) =\u003e\n *       line.includes(`\u003c!-- \u003cCODESTAMP START\u003e --\u003e`)\n *     );\n *     const endIndex = lineList.findIndex((line) =\u003e\n *       line.includes(`\u003c!-- \u003cCODESTAMP END\u003e --\u003e`)\n *     );\n *     // Assert both `startIndex` and `endIndex` !== -1\n *\n *     lineList.splice(\n *       startIndex + 1,\n *       0,\n *       `here's the stamp: ${stamp}`\n *     );\n *\n *     return lineList.join(\"\\n\");\n *   },\n *   initialStampRemover: ({ content, stamp }) =\u003e {\n *     const lineList = content.split(\"\\n\");\n *     const startIndex = lineList.findIndex((line) =\u003e\n *       line.includes(`\u003c!-- \u003cCODESTAMP START\u003e --\u003e`)\n *     );\n *     const endIndex = lineList.findIndex((line) =\u003e\n *       line.includes(`\u003c!-- \u003cCODESTAMP END\u003e --\u003e`)\n *     );\n *     // Assert both `startIndex` and `endIndex` !== -1\n *\n *     lineList.splice(startIndex + 1, endIndex - startIndex - 1);\n *     return lineList.join(\"\\n\");\n *   },\n * }\n * ```\n */\nexport type TStampRemover = TFormatter;\n````\n\n\u003c!-- \u003cDOCEND TARGET TStampRemover\u003e --\u003e\n\n##### `TApplyStampResult`\n\nReturn type for [`applyStamp(...)`](#applystamp).\n\n\u003c!-- \u003cDOCSTART TARGET TApplyStampResult\u003e --\u003e\n\n```typescript\n/**\n * Return type for {@link applyStamp}. This is a discriminated union.\n */\nexport type TApplyStampResult =\n  /**\n   * Content is legit.\n   */\n  | {\n      status: \"OK\";\n      /** The stamp extracted from content */\n      stamp: string;\n    }\n  /**\n   * Content didn't have a stamp; a new stamp is added.\n   */\n  | {\n      status: \"NEW\";\n      /** The new stamp being added */\n      newStamp: string;\n      /** Value of the updated content */\n      newContent: string;\n    }\n  /**\n   * Stamp needs an update.\n   */\n  | {\n      status: \"UPDATE\";\n      /** The new/expected stamp */\n      newStamp: string;\n      /** The old/current stamp */\n      oldStamp: string;\n      /** Value of the updated content */\n      newContent: string;\n    }\n  /**\n   * The content includes multiple stamps. This is likely because the\n   * content was manually updated. `codestamp` needs to bail out\n   * because it cannot guarantee a deterministic update.\n   */\n  | {\n      status: \"ERROR\";\n      errorType: \"MULTIPLE_STAMPS\";\n      errorDescription: string;\n      /** \u003e= 2 stamps */\n      stampList: Array\u003cstring\u003e;\n    }\n  /**\n   * Placer didn't return a string that contains the stamp, or\n   * returned multiple stamps.\n   */\n  | {\n      status: \"ERROR\";\n      errorType: \"STAMP_PLACER\";\n      errorDescription: string;\n      placer: string;\n      placerReturnValue: any;\n    };\n```\n\n\u003c!-- \u003cDOCEND TARGET TApplyStampResult\u003e --\u003e\n\n## Acknowledgments\n\n`codestamp` is inspired by similar code signing systems at Facebook.\n\n- [`signedsource` in `fbjs`](https://github.com/facebook/fbjs/blob/main/packages/signedsource/index.js): Used by Relay, but doesn't allow customization or tracking of dependencies\n- [`Hack Codegen`](https://github.com/hhvm/hack-codegen): Great abstraction and design, but only supports generating and signing Hack code\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeyz%2Fcodestamp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkeyz%2Fcodestamp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkeyz%2Fcodestamp/lists"}