{"id":18999804,"url":"https://github.com/yowainwright/stdouttojson","last_synced_at":"2025-04-22T16:51:21.078Z","repository":{"id":37046733,"uuid":"468646703","full_name":"yowainwright/stdoutToJSON","owner":"yowainwright","description":"Transforms stdout (standard out) to JSON 📇  ","archived":false,"fork":false,"pushed_at":"2025-04-07T06:14:45.000Z","size":703,"stargazers_count":5,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-07T07:24:55.836Z","etag":null,"topics":["cli","cli-testing","exec","json","stdout","testing","typescript"],"latest_commit_sha":null,"homepage":"","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/yowainwright.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":"CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-03-11T07:11:21.000Z","updated_at":"2025-04-07T06:14:48.000Z","dependencies_parsed_at":"2024-01-20T02:38:48.053Z","dependency_job_id":"7da7a9f1-05d1-48ff-9a54-4aa797ae26cb","html_url":"https://github.com/yowainwright/stdoutToJSON","commit_stats":{"total_commits":172,"total_committers":2,"mean_commits":86.0,"dds":"0.15697674418604646","last_synced_commit":"e8b01f997ee966da1bc0897647ebab911c84c5c5"},"previous_names":["yowainwright/stdoutjson"],"tags_count":27,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yowainwright%2FstdoutToJSON","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yowainwright%2FstdoutToJSON/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yowainwright%2FstdoutToJSON/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yowainwright%2FstdoutToJSON/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yowainwright","download_url":"https://codeload.github.com/yowainwright/stdoutToJSON/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249279407,"owners_count":21242920,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["cli","cli-testing","exec","json","stdout","testing","typescript"],"created_at":"2024-11-08T18:05:04.961Z","updated_at":"2025-04-16T21:31:34.677Z","avatar_url":"https://github.com/yowainwright.png","language":"TypeScript","readme":"# stdoutToJSON 📇\n\n![Typed with TypeScript](https://flat.badgen.net/badge/icon/Typed?icon=typescript\u0026label\u0026labelColor=blue\u0026color=555555)\n[![npm version](https://badge.fury.io/js/stdouttojson.svg)](https://badge.fury.io/js/stdouttojson)\n![ci](https://github.com/yowainwright/stdouttojson/actions/workflows/ci.yml/badge.svg)\n[![Github](https://badgen.net/badge/icon/github?icon=github\u0026label\u0026color=grey)](https://github.com/yowainwright/stdouttojson)\n![Twitter](https://img.shields.io/twitter/url?url=https%3A%2F%2Fgithub.com%2Fyowainwright%2Fstdouttojson)\n\n**stdoutToJSON** is JavaScript utility for converting [stdout](https://en.wikipedia.org/wiki/Standard_streams#Standard_output_(stdout)) to JSON.\u003cbr\u003e\nThis is useful for using `JSON` which has been output as `stdout`.\n\n---\n\n## Why Use _stdoutToJSON_?\n\n**stdoutToJSON** takes in a `stdout` string of **JSON-like** shape and reconstructs to be parsable by `JSON.parse`. 👌\u003cbr\u003e\n**_This is very useful for testing CLI stdout outputs!_**\n\n**_For example, say you have some `stdout` like so._**\n\n```typescript\n  \"{\\n\" +\n  \"  options: { isTestingCLI: true },\\n\" +\n  \"  urls: [ 'https://example.com?gclid=test-clickid' ],\\n\" +\n  \"  cookies: [ { name: 'foo', value: '1' } ]\\n\" +\n  \"}\\n\";\n```\n\n**_With **stdoutToJSON**, you can pass that `stdout` as an argument!_**\n\n```typescript\nconst stdout = \"{\\n\" +\n  \"  options: { isTestingCLI: true },\\n\" +\n  \"  urls: [ 'https://example.com?gclid=test-clickid' ],\\n\" +\n  \"  cookies: [ { name: 'foo', value: '1' } ]\\n\" +\n  \"}\\n\";\nconst json = stdoutToJSON(stdout);\n```\n\n**_And you will be get usable JSON!!_**\n\n```typescript\n{\n  options: { isTestingCLI: \"true\" },\n  urls: [\"https://example.com?gclid=test-clickid\"],\n  cookies: [{ name: \"foo\", value: \"1\" }],\n}\n```\n\nFor more detail, here's an [architectural gist](https://gist.github.com/yowainwright/ba8164ad5d968f35ae86e2ba6c91c592) for reference.\n\n---\n\n## Basic Usage\n\nThe following snippet (a CLI unit test) represents a basic use-case and what the **stdoutToJSON** does.\n\n```typescript\nimport { exec } from 'child_process';\nimport { stdoutToJSON } from 'stdoutToJSON';\n// or, const stdoutToJSON from 'stdoutToJSON';\n// or, const { stdoutToJSON } = require('stdoutToJSON')\n// or, const stdoutToJSON = require('stdoutToJSON').default\n\ndescribe('cli', () =\u003e {\n  it('returns stdout of an expected shape', (done) =\u003e {\n    exec(`${\u003csme-cmd\u003e --someJSONKey 'foo' }`, (_, stdout) =\u003e {\n      const { someJSONKey } = stdoutToJSON(stdout); // where \"someJSONKey\" could be any expected key\n      expect(someJSONkey).toEqual('foo');\n    });\n  });\n});\n\n```\n\n---\n\n## Arguments\n\n| argument | required or optional | description |\n| --- | --- | --- |\n| **`stdout`** | `required` | a string of JSON-like shape |\n| **`matchers`** | `optional` | an optional array to perform further string operations \\***or** `null` |\n| **`debug`** | `optional` | an optional boolean to enable debugging |\n\n\\***nullish matcher** arguments can be used to enable debugging with the default matchers.\n\n```typescript\nstoutToJSON('{\"foo\": \"bar\"}', null, true); // enables debugging with standard matchers\n```\n\n---\n\n## Advanced Usage\n\nThis example provides insight into using the `matchers` argument.\n\n```typescript\nimport { exec } from 'child_process';\nimport stdoutToJSON, { INITIAL_MATCHERS } from 'stdoutToJSON';\n// import stdoutJSON from 'stdoutJSON'; (also works)\n\ndescribe('cli', () =\u003e {\n  it('returns stdout of an expected shape', (done) =\u003e {\n    exec(`${\u003ccmd\u003e --someJSONKey 'foo'}`, (_, stdout) =\u003e {\n      const UPDATED_MATCHERS = INITIAL_MATCHERS.concat([{ value: '\u003csome-matcher-rgx', edit: '\u003csome-new-value' }])\n      /*\n       * where \"some-matcher-rgx\" is a regex pattern and the edit is the expected new value\n       */\n      const { someJSONKey } = stdoutToJSON(stdout, matchers); // where \"someJSONKey\" could be any expected key\n      expect(someJSONkey).toEqual('foo');\n    });\n  });\n});\n\n```\n\n---\n\n## Exposed Functions\n\nIn the section below, a description table and code blocks are provide to describe each function's usage.\n\n| function | shape |\n| --- | --- |\n| [`stdoutToJSON`](#stdoutToJSON) | returns a JSON object from a string of JSON-like shape |\n| [`matcher`](#matcher) | returns an iterated string based on a Matcher array's `value` and `edit` value replacements |\n\n### `stdoutToJSON`\n\nImporting and function shape detail\n```typescript\nimport { stdoutToJSON } from 'stdoutToJSON';\n// or, const stdoutToJSON from 'stdoutToJSON';\n// or, const { stdoutToJSON } = require('stdoutToJSON')\n// or, const stdoutToJSON = require('stdoutToJSON').default\n\n// view type details below\nstdoutToJSON(stdout: string, matchers: Matcher[] = INITIAL_MATCHERS);\n// returns JSON\n```\n\n### `matcher`\n\nImporting and function shape detail\n\n```typescript\nimport { matcher } from 'stdoutToJSON';\n\n// view type details below\nmatcher(str: string, matchers: Matcher[] = INITIAL_MATCHERS);\n//returns replaced JSON-like string\n```\n#### Updating or Creating a Matcher Array (`Matcher[]`)\n\n[Matchers](https://github.com/yowainwright/stdoutToJSON/blob/master/src/index.ts#L23-L56) can be exposed, overridden, and replaced.\n\nTo create and use your own Matcher array, import whatever `constants` you want and add to or update them as needed.\n\nMatchers are written in simple regex string format making it easy to update and modify matchers.\n\n```typescript\nimport { INITIAL_MATCHERS, stdoutToJSON } from \"stdoutToJSON\";\n\n// concat your own matcher (can also be done with spread, etc\nconst MY_MATCHER = INITIAL_MATCHERS.concat([{ value: '\u003csome-matcher-rgx', edit: '\u003csome-new-value' }])\n\n// execute your customer matchers\nstdoutToJSON(stdout, MY_MATCHER);\n```\n\n---\n\n## Types\n\nListed below are both types used to describe `stdoutToJSON` input and output\n\n### `WithWildcards`\n\nA generic type matching any JSON-like output\n\n```typescript\n/**\n * @description matches a JSON-like shape of unknown keys and values\n */\nexport type WithWildcards\u003cT\u003e = T \u0026 { [key: string]: unknown };\n```\n\n### `Matcher`\n\nA type used to describe a **\"matcher\"** which is an input **value** (a regex string to match) and an output **edit** (a string to be output for each match within a string)\n\n```typescript\n/**\n * @description the Matcher shape matches a regex input string and expected output string, useful `String.prototype.replace`\n * @param {value} string a string contain a regex pattern to match\n * @param {edit} string\n */\nexport type Matcher = {\n  value: string;\n  edit: string;\n};\n```\n\n---\n\n## Synopsis\n\nBeing able to quickly test CLI commands is imperative to my daily workflow.\n\n`stdoutToJSON` allows me to hack CLI programs and quickly test the `stdout` ouput within tests. See the end-to-end example below for the full picture.\n## End-to-end Example\n\nThe example below displays a CLI program code block and a code block which tests the CLI program.\n### Example CLI Program\n\nUsing a boolean flag (`--isTestingCLI`), the CLI program is able to exited before actually executing it's purpose (the `script`).\n\nAdding a `console.log` in the if block of the flag check produces an `stdout` output which can be tested.\n\n```typescript\n#!/usr/bin/env node\nconst { program } = require(\"commander\");\nconst { cosmiconfigSync } = require(\"cosmiconfig\");\nconst { script } = require(\"./script\");\nconst version = \"VERSION\";\n\n/**\n * @notes\n * This config name is intentionally not specific to this pragram.\n * Hopefully, more scripts can be added!\n */\nconst explorer = cosmiconfigSync(\"config\");\n\n/**\n * action\n * @param {Options} options\n * @notes\n * a default config is used by default\n * a config passed in via arguments trumps the default config\n * an individual config trumps the config passed in via arguments\n */\nexport function action(options: Options = {}): void {\n  const { config: defaultConfig = {} } = explorer.search() || {};\n  const urls = options?.urls || defaultConfig?.urls || [];\n  const config = options?.config || defaultConfig;\n  if (options.isTestingCLI) {\n    console.log({ urls, config });\n    return;\n  }\n  script({ options });\n}\n\nprogram\n  .version(version)\n  .description(\"tests cli\")\n  .option(\"-u, --urls [urls...]\", \"urls to run scripts on\")\n  .option(\"-c, --config \u003cconfig\u003e\", \"config file to use\")\n  .option(\"-t, --isTestingCLI\", \"enables CLI testing, no scripts are run\")\n  .action(action)\n  .parse(process.argv);\n\nexport { program };\n```\n\n### Example CLI Program Test\n\nBecause the CLI program exits and outputs `stdout`, the `stdout` output can be tested! However, `stdout` produces an awkward string if the `console.log` contains more than a simple string. This is the the big initial use-case for `stdoutToJSON`.\n\nUsing `stdoutToJSON` we can do a deep test of the `stdout` output!\n\nThis makes it easy the test the CLI itself in an efficient way!\n\n```typescript\nimport { exec } from \"child_process\";\nimport { stdoutToJSON } from \"stdoutToJSON\";\n\ndescribe(\"program\", () =\u003e {\n  it(\"works with defaults\", (done) =\u003e {\n    exec(\n      `ts-node ../src/program.ts --isTestingCLI`,\n      (err, stdout) =\u003e {\n        if (err) {\n          done();\n          return;\n        }\n\n        const { config, url } =\n          convertStdoutToJson(stdout);\n        expect(url).toEqual([]);\n        expect(config).toEqual({});\n        done();\n      }\n    );\n  });\n\n  it(\"prefers config urls to an empty array\", (done) =\u003e {\n    exec(\n      `ts-node ../src/program.ts --isTestingCLI --config .configrc`,\n      (err, stdout) =\u003e {\n        if (err) {\n          done();\n          return;\n        }\n\n        const { config, urls } =\n          convertStdoutToJson(stdout);\n        expect(urls).toEqual(['https://localhost:3000/', 'https://test.com']);\n        expect(config.urls).toEqual(['https://localhost:3000/', 'https://test.com']);\n        done();\n      }\n    );\n  });\n\n  it(\"prefers urls options over config.urls or an empty array\", (done) =\u003e {\n    exec(\n      `ts-node ../src/program.ts --isTestingCLI --config .configrc --urls 'https://foo.com' 'https://bar.com'`,\n      (err, stdout) =\u003e {\n        if (err) {\n          done();\n          return;\n        }\n\n        const { config, urls } =\n          convertStdoutToJson(stdout);\n        expect(urls).toEqual(['https://foo.com', 'https://bar.com']);\n        expect(config.urls).toEqual(['https://localhost:3000/', 'https://test.com']);\n        done();\n      }\n    );\n  });\n});\n```\n\n---\n\n## Debugging\n\nListed below are some issue with using this tool and how to fix them.\n\n### Types Errors with the returned result\n\n```typescript\nimport { Options } from '../types'\n\n...\n\nconst { options } = stdoutToJSON(stdout)\nconst optionsResults = (options as Options)\n// should be good to go!\n\n...\n\n```\n\n---\n\n## Security\n\n**stdoutToJSON** has no dependencies and is meant to be installed as a `devDependency`.\u003cbr\u003e\nAKA if you're testing a CLI's interface it's a no-brainer to use for unit testing! Its tiny and secure. 🛡\n\n---\n\n## Local Setup\n\n1. Clone\n```\ngit clone git@github.com:yowainwright/stdoutToJSON.git\n```\n\n2. Setup\n```\nnvm i \u0026\u0026 pnpm i -g \u0026\u0026 pnpm i \u0026\u0026 pnpm prepare\n# nvm or equivalent\n```\n\n3. Write awesomeness + a test. 🚀\n\n---\n\n## Videos\n\n[![Loom video](https://cdn.loom.com/sessions/thumbnails/6b7082bd802b43618646242477701def-with-play.gif)](https://loom.com/share/6b7082bd802b43618646242477701def)\n\n---\n\nThe name was changed from `stdoutJSON` to `stdoutToJSON`. Thanks to [OolongHell](https://www.reddit.com/r/node/comments/tx8sxo/stdoutjson_a_simple_node_js_utility_to_make/i3njq3w/?context=3) for assistance in making the reasoning and use case of this utility clearer.\n\nFeel free to reach/fork with improvements—or if I can help clarify the docs. If you have a stdout string that doesn't work, please make an [issue](/issues), or submit a [pull request](/pulls) with a [test](src/__tests__/index.test.ts) and an updated [matcher](src/index.ts).  See [the setup](#local-setup) instructions. Thanks! 🤝\n\n---\n\nMade by [@yowainwright](https://github.com/yowainwright), MIT 2022\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyowainwright%2Fstdouttojson","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyowainwright%2Fstdouttojson","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyowainwright%2Fstdouttojson/lists"}