{"id":16019477,"url":"https://github.com/cawfree/propeteer","last_synced_at":"2025-08-29T21:33:38.903Z","repository":{"id":35207118,"uuid":"215821323","full_name":"cawfree/propeteer","owner":"cawfree","description":"🧸Config comes in, React comes out.","archived":false,"fork":false,"pushed_at":"2023-01-05T07:20:32.000Z","size":445,"stargazers_count":7,"open_issues_count":12,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-26T12:25:47.619Z","etag":null,"topics":["config","dynamic","json","jsx","native","react","react-native"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/cawfree.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":"2019-10-17T15:06:11.000Z","updated_at":"2025-02-06T18:06:33.000Z","dependencies_parsed_at":"2023-01-15T16:12:02.855Z","dependency_job_id":null,"html_url":"https://github.com/cawfree/propeteer","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cawfree/propeteer","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Fpropeteer","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Fpropeteer/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Fpropeteer/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Fpropeteer/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cawfree","download_url":"https://codeload.github.com/cawfree/propeteer/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cawfree%2Fpropeteer/sbom","scorecard":{"id":268217,"data":{"date":"2025-08-11","repo":{"name":"github.com/cawfree/propeteer","commit":"3969353fa3ffcec4760a31d3a37f7ac6dcfc12e2"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Code-Review","score":0,"reason":"Found 0/15 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":0,"reason":"36 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-67hx-6x53-jw92","Warn: Project is vulnerable to: GHSA-6chw-6frg-f759","Warn: Project is vulnerable to: GHSA-v88g-cgmw-v5xw","Warn: Project is vulnerable to: GHSA-93q8-gq69-wqmw","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw","Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg","Warn: Project is vulnerable to: GHSA-w8qv-6jwh-64r5","Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275","Warn: Project is vulnerable to: GHSA-gxpj-cx7g-858c","Warn: Project is vulnerable to: GHSA-w573-4hg7-7wgq","Warn: Project is vulnerable to: GHSA-2j2x-2gpw-g8fm","Warn: Project is vulnerable to: GHSA-fjxv-7rqg-78g4","Warn: Project is vulnerable to: GHSA-896r-f27r-55mw","Warn: Project is vulnerable to: GHSA-9c47-m6qq-7p4h","Warn: Project is vulnerable to: GHSA-p6mc-m468-83gw","Warn: Project is vulnerable to: GHSA-29mw-wpgm-hmr9","Warn: Project is vulnerable to: GHSA-35jh-r3h4-6jhm","Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv","Warn: Project is vulnerable to: GHSA-f8q6-p94x-37v3","Warn: Project is vulnerable to: GHSA-vh95-rmgr-6w4m","Warn: Project is vulnerable to: GHSA-xvch-5gv4-984h","Warn: Project is vulnerable to: GHSA-5fw9-fq32-wv5p","Warn: Project is vulnerable to: GHSA-hj48-42vr-x3v9","Warn: Project is vulnerable to: GHSA-hrpp-h998-j3pp","Warn: Project is vulnerable to: GHSA-p8p7-x288-28g6","Warn: Project is vulnerable to: GHSA-c2qf-rxjj-qqgw","Warn: Project is vulnerable to: GHSA-44c6-4v22-4mhx","Warn: Project is vulnerable to: GHSA-4x5v-gmq8-25ch","Warn: Project is vulnerable to: GHSA-jgrx-mgxx-jf9v","Warn: Project is vulnerable to: GHSA-72xf-g2v4-qvf3","Warn: Project is vulnerable to: GHSA-j8xg-fqg3-53r7","Warn: Project is vulnerable to: GHSA-6fc8-4gx4-v693","Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q","Warn: Project is vulnerable to: GHSA-c4w7-xm78-47vh","Warn: Project is vulnerable to: GHSA-p9pc-299p-vxgp"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-17T12:43:02.636Z","repository_id":35207118,"created_at":"2025-08-17T12:43:02.636Z","updated_at":"2025-08-17T12:43:02.636Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":272338714,"owners_count":24917024,"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","status":"online","status_checked_at":"2025-08-27T02:00:09.397Z","response_time":76,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["config","dynamic","json","jsx","native","react","react-native"],"created_at":"2024-10-08T17:04:32.731Z","updated_at":"2025-08-29T21:33:38.872Z","avatar_url":"https://github.com/cawfree.png","language":"JavaScript","funding_links":["https://www.buymeacoffee.com/cawfree"],"categories":[],"sub_categories":[],"readme":"# propeteer\n🧸Config comes in, React comes out. This project adheres to [Semantic Versioning](https://docs.npmjs.com/about-semantic-versioning).\n\n\u003ca href=\"#badge\"\u003e\n    \u003cimg alt=\"code style: prettier\" src=\"https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square\"\u003e\u003c/a\u003e\n\n## 🚀 Getting Started\n\nUsing [`npm`]():\n\n```sh\nnpm install --save propeteer\n```\n\nUsing [`yarn`]():\n\n```sh\nyarn add propeteer\n```\n\n## 🤔 Why does this exist?\n\nSome interfaces we present to the user are defined by pure config; this is especially true for [white label applications](https://www.quora.com/What-are-white-label-apps), where the presentation value of your solution directly correlates against how configurable it can be.\n\nIn traditional frontend development, an API serves you some JSON which you're expected to translate into your frontend. This normally means that whenever future enhancements are made to the response, clients are required to update parsers and manage the propagation of this data into their DOM.\n\nBy defining props using _config_, we have the entire breadth of React at our disposal, since your config _is_ exactly what is presented, and all the meaningful intepretations are already made possible to you by React and the custom components you deploy. Meanwhile, any referenced components in config can themselves can define the sensible default values, or be internally wrapped using operation-critical components.\n\nIn addition, bespoke customization of deeply-nested components in React can also be very tricky. It's not often that you import a project dependency that fits your application theme. Similarly, they require you to _trust_ the implemetor to expose the correct configuration properties for all levels, for each component, or accept a lot of your [pull requests](). This can be particularly obstructive to development when all you care about is the intrinsic _capabilities_ of library, but not the subjective presentation that you're forced to use alongside it.\n\n[Propeteer]() aims to solve these problems:\n\n  - Presented components are a function of serializable, transportable config objects.\n  - Libraries created using Propeteer permit arbitrary bespoke configuration of the graphical frontend, whilst maintaining the functionality that matters to implemetors.\n  - Dynamic components rendered using Propeteer may have a working knowledge of application state, so it is possible to achieve stateful operations, or full working applications, just while using conventional config.\n  - Runtime JSX props propagate as you'd expect into the evaluatde content.\n\n## 🔤 Syntax Rules\n\nPropeteer is pretty straight forward. Anything in your config is treated as a component prop, apart from the following reserved keys:\n\n#### `_`\nDefines a Component reference, i.e.\n\n```json\n{\n  \"_\": \"Fragment\",\n  \"key\": \"someFragmentKey\"\n}\n```\n\n#### `$`\nDefines an array of children, who are themselves defined using config.\n\n```json\n{\n  \"_\": \"Fragment\",\n  \"$\": [\n    {\n      \"_\": \"View\",\n      \"style\": {\n        \"flex\": 1,\n        \"backgroundColor\": \"green\",\n      }\n    }\n  ]\n}\n```\n\n#### `children`\nAny config prop declared using the key `children` will be _ignored_.\n\n## ✍️ Examples\n\n## Hello, world!\n\nTo get started, let's take a look at what a \"Hello, world!\" looks like in Propeteer.\n\n  - Implementors define a `LookUpTable` of React elements which can be referenced by config.\n    - Any elements referred to in config which do not exist will not be rendered, and will instead trigger a warning.\n  - A `\u003cPropeteer /\u003e` is passed a configuration object via the `children` prop.\n    - This config is evaluated into an equivalent React layout. In this example, we draw a blue box with a `\u003cTextInput /\u003e` in the center.\n\n```javascript\nimport React from 'react';\nimport { View, TextInput, StyleSheet } from 'react-native';\nimport Propeteer from 'propeteer';\n\nexport default () =\u003e (\n  \u003cPropeteer\n    LookUpTable={{\n      View,\n      TextInput,\n    }}\n    children={{\n      _: 'View',\n      style: [\n        StyleSheet.absoluteFill,\n        {\n          backgroundColor: 'skyblue',\n          alignItems: 'center',\n          justifyContent: 'center',\n        },\n      ],\n      $: [\n        {\n          _: 'TextInput',\n          placeholder: 'Hello, world!',\n        },\n      ],\n    }}\n  /\u003e\n);\n\n```\n\n## Overriding and Application State\n\nIn the example below, we can demonstrate that the components that config JSON refer to can be dynamically implemented on the runtime.\n\nThis means that:\n  - We can inject useful properties and behaviours with client-side awareness in place of standard references.\n    - This way, it is easy to apply application-specific properties in addition to, or in lieu of, the config-defined ones.\n  - We can connect these components to sources of global application state.\n    - Self managing components, such as those that `useEffect`, can begin to manage, manipulate and respond to the runtime state.\n\n```javascript\nimport React from 'react';\nimport { View } from 'react-native';\nimport Provider, { connect } from 'react-redux';\nimport Propeteer from 'propeteer';\n\nimport configureStore from './configureStore';\n\nconst ReduxConnectedComponent = connect()(View);\n\nconst store = configureStore();\n\nexport default () =\u003e (\n  \u003cProvider\n    store={store}\n  \u003e\n    \u003cPropeteer\n      LookUpTable={{\n        ReduxConnectedComponent: ({ ...extraProps }) =\u003e (\n          \u003cReduxConnectedComponent\n            {...extraProps}\n            someClientSpecificProp\n          /\u003e\n        ),\n      }}\n      children={{\n        _: 'ReduxConnectedComponent',\n      }}\n    /\u003e\n  \u003c/Provider\u003e\n);\n\n```\n\n## Portable Libraries\n\nThis demonstration is a little more involved, but it covers all the basic techniques you need to create an unopinionated frontend library whose functionality is overridable.\n\nThe important themes to note are:\n\n  - We don't have decide which properties or components should be overridable.\n    - Conventionally, React developers must put forethought into deciding which components should be configurable, or which properties should be passed around, or whether a `\u003cProvider /\u003e` should be deployed. Using Propeteer, anything expressed as config is inherently overridable.\n  - Components can be stateful.\n    - In this demonstration, we utilise `SomeUsefulComponent` to perform some abstract functionality intended to be served by the library. Below we prove this functionality can still be maintained, even when the surrounding presentation context has changed.\n  - We can decide which properties we _don't_ want to be overrided.\n    - Elements that are mission critical, we don't want to be overrided. In these cases, we can prioritize the input `LookUpTable` to ensure core components  are always persisted.\n\n```javascript\nimport React, { useState } from 'react';\nimport PropTypes from 'prop-types';\nimport { StyleSheet, TouchableOpacity, Platform, View, Text } from 'react-native';\nimport Propeteer from 'propeteer';\nimport { merge } from 'lodash';\n\n// XXX: A simple component which has the ability to store and regenerate a random number.\nconst SomeUsefulComponent = ({ FrontEnd, ...extraProps }) =\u003e {\n  const [ secret, setSecret ] = useState(\n    Math.random(),\n  );\n  return (\n    \u003cFrontEnd\n      {...extraProps}\n      secret={secret}\n      regenerate={() =\u003e setSecret(Math.random())}\n    /\u003e\n  );\n};\n\nSomeUsefulComponent.propTypes = {\n  FrontEnd: PropTypes.elementType,\n};\n\nSomeUsefulComponent.defaultProps = {\n  // The default FrontEnd prop just renders text string, and hides the secret value.\n  FrontEnd: ({ secret, regenerate, ...extraProps }) =\u003e (\n    \u003cText\n      children=\"Hello!\"\n    /\u003e\n  ),\n};\n\n// XXX: The library defines the entire default configuration of the resulting component.\nconst Library = ({ LookUpTable: lut, children, aliases, ...extraProps }) =\u003e {\n  // XXX: Mix the default props of the Library with the supplied config.\n  const LookUpTable = {\n    ...Library.defaultProps.LookUpTable,\n    ...(lut || {}),\n  };\n  return (\n    \u003cPropeteer\n      LookUpTable={LookUpTable}\n      aliases={aliases}\n      children={merge(\n        {...Library.defaultProps.children},\n        (children || {}),\n      )}\n    /\u003e\n  );\n};\n\nLibrary.propTypes = {\n  ...Propeteer.propTypes,\n};\n\nLibrary.defaultProps = {\n  LookUpTable: {\n    SomeUsefulComponent,\n    GlobalLayout: ({ children, style, ...extraProps }) =\u003e (\n      \u003cView\n        style={[\n          StyleSheet.absoluteFill,\n          style,\n        ]}\n      \u003e\n        \u003cView\n          style={{\n            flex: 1,\n            backgroundColor: 'orange',\n          }}\n        /\u003e\n        {children}\n      \u003c/View\u003e\n    ),\n  },\n  // XXX: To ease nested references, you can optionally specify aliases that resolve\n  // to equivalent paths in your config.\n  aliases: {\n    'FrontEndHook': '$.0.FrontEnd',\n  },\n  // XXX: Renders the global layout with a single \u003cSomeUsefulComponent /\u003e\n  children: {\n    _: 'GlobalLayout',\n    $: [\n      { _: 'SomeUsefulComponent' },\n    ],\n  },\n};\n\n// XXX: As a library consumer, this is all you see:\nexport default () =\u003e (\n  \u003cLibrary\n    LookUpTable={{\n      ExposeSecret: ({ secret, regenerate, ...extraProps }) =\u003e (\n        \u003cTouchableOpacity\n          onPress={regenerate}\n        \u003e\n          \u003cText\n            {...extraProps}\n            children={`The secret is ${secret}!`}\n          /\u003e\n        \u003c/TouchableOpacity\u003e\n      ),\n    }}\n    children={{\n      // XXX: We choose to override the FrontEnd using our custom\n      //      ExposeSecret component. This has the ability to\n      //      render the secret, and regenerate a new secret onPress.\n      'FrontEndHook': 'ExposeSecret',\n    }} \n  /\u003e\n```\n\n## ✌️ License\n[MIT](https://opensource.org/licenses/MIT)\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.buymeacoffee.com/cawfree\"\u003e\n    \u003cimg src=\"https://cdn.buymeacoffee.com/buttons/default-orange.png\" alt=\"Buy @cawfree a coffee\" width=\"232\" height=\"50\" /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcawfree%2Fpropeteer","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcawfree%2Fpropeteer","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcawfree%2Fpropeteer/lists"}