{"id":13726716,"url":"https://github.com/esy/pesy","last_synced_at":"2025-04-09T22:10:46.672Z","repository":{"id":35206542,"uuid":"167138113","full_name":"esy/pesy","owner":"esy","description":"Project configuration for esy","archived":false,"fork":false,"pushed_at":"2024-06-07T02:45:01.000Z","size":12345,"stargazers_count":140,"open_issues_count":34,"forks_count":17,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-09T22:10:41.660Z","etag":null,"topics":["esy","hacktoberfest","native-development","project-template","reason","reasonml"],"latest_commit_sha":null,"homepage":"","language":"Reason","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/esy.png","metadata":{"files":{"readme":"README.html","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-01-23T07:30:21.000Z","updated_at":"2024-11-05T00:47:57.000Z","dependencies_parsed_at":"2024-01-06T07:44:16.820Z","dependency_job_id":"090a1950-8e97-46e9-ae75-0219c1f7d25e","html_url":"https://github.com/esy/pesy","commit_stats":{"total_commits":287,"total_committers":17,"mean_commits":16.88235294117647,"dds":"0.29268292682926833","last_synced_commit":"ec48d11732e80380e30ae18d21354cf07a967902"},"previous_names":[],"tags_count":28,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esy%2Fpesy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esy%2Fpesy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esy%2Fpesy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/esy%2Fpesy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/esy","download_url":"https://codeload.github.com/esy/pesy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248119294,"owners_count":21050755,"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":["esy","hacktoberfest","native-development","project-template","reason","reasonml"],"created_at":"2024-08-03T01:03:17.518Z","updated_at":"2025-04-09T22:10:46.644Z","avatar_url":"https://github.com/esy.png","language":"Reason","funding_links":[],"categories":["Reason"],"sub_categories":[],"readme":"[ vim: set filetype=Markdown: ]: # (\u003cscript src=\"site/Reload.js\"\u003e \u003c/script\u003e)\n\n\n# pesy\n\n`pesy` is a command line tool to assist with your native Reason/OCaml\nworkflow using the package.json itself.\n\n1. Quickly bootstrap a new Reason project\n2. Configure your builds\n\n## Installation\n\n`npm install -g pesy@next`\n\n## Why pesy?\n`esy` is driven by a package.json to bring the familiar NPM workflow\nto the native world of Reason/OCaml. `pesy` takes this further by\nadopting NPM conventions to configure the build.`esy`'s package.json\nfirst approach in creating developer sandboxes brings interesting\npossibilities to the table. `pesy` leverages on those features to make\nnative development both convenient and familiar. \n\n`pesy` provides \n\n1. A bootstrapper script to quickly create a project template\n2. An alternative JSON syntax around Dune that is NPM like\n3. Built in tasks that take full advantages of esy's capabilities\n\n## Getting started\n### Creating a new project\n\n`pesy` can quickly bootstrap a basic native Reason/OCaml project.\n\n```sh\ncd my-new-project/\npesy\n```\n\n### Adding project dependencies\n\nSay, we need `@opam/yojson` and `@reason-native/console` in `App.re`.\n\nPlace `App.re` in a folder (say, `bin/`?) and add the following to you\npackage.json. \n\n```json\n\"buildDirs\": {\n  \"bin\": {\n    \"imports\": [\n\t  \"Json = require('@opam/yojson')\",\n\t  \"Console = require('@reason-native/console/lib')\"\n\t]\n  }\n}\n```\n\u003ccontinueRight/\u003e\n\nRun `esy pesy` (once). Run `esy` after that to create builds.\n\n`pesy` abstracts library identifiers (in this case `yojson` and\n`console.lib`) in file paths conceptually. Use native dependencies\nfrom OCaml like you did with NPM!\n\n### Where pesy shines\nHowever, `pesy`'s is truly useful for frameworks that need\na lot dependencies and configuration. Ex: Morph and Revery \n\n```sh\npesy --template pesy/template-revery --directory my-new-project\n```\n\nIf you are authoring a framework, create a template like\n[`pesy/template-revery`](https://github.com/pesy/template-revery) and\nrun the project on the CI with a setup that `pesy` creates for you.\n\nYou users get cached CI artifacts that will let can hydrate their\nlocal `esy` cache avoid long wait times! See [Templates](#pesy-templates-experimental-creating-your-own-template) to see how\nto create such templates.\n\n### Looking for a tutorial?\nCheckout [A simple native Reason project with\npesy](#pesy-simple-native-example) to get an idea of what developing\nwith `pesy` feels like. \n\n\n## Relation to Dune\n\nPesy as accepts package.json as input and producing dune files as output.\n\n```\n                  +--------+\npackage.json ---\u003e |  pesy  | +---\u003e  dune files\n                  +--------+\n```\n\n\u003ccontinueRight/\u003e\n\nNote that not all of Dune's features are supported in pesy (PRs\nwelcome). `pesy` doesn't intend to duplicate Dune's efforts - it's\nmeant to compliment it. For simple use cases, `pesy` wants to provide\na NPM friendly interface to Dune so that newcomers can quickly get\nstarted, without confusing themselves with the library vs packages\nnuances.\n\n## Namespacing\n\nEvery library, as we know, exposes a namespace/module under which it's APIs are\navailable. However, as package authors, it can hard to make sure one\nis not using a namespace already taken by another package (Otherwise\nit could lead to collisions). Pesy works around this by assigning the library\nthe upper camelcase of the root package name and directory the\nlibrary/sub-package resides in. \n\nExample: if a package.json looks like this \n\n```json\n{\n  \"name\": \"@myscope/foo\",\n  \"buildDirs\": {\n    \"library\": { ... }\n  }\n}\n```\n\nThen, subpackage `library` takes a namespace of\n`MyScopeFooLibrary`. As a user, however, you shouldn't have to worry\nmuch about yourself, since you can specify how you'd like to import\nsubpackages (and packages). In the above example, another subpackage\nwould consume it as follows\n\n```json\n{\n  \"name\": \"@myscope/foo\",\n  \"buildDirs\": {\n    \"library\": { ... },\n    \"anotherLibrary\": {\n      \"imports\": [\n        \"ThatOtherLibrary = require('@myscope/foo/library')\"\n      ]\n    }\n  }\n}\n```\n\nAnd if you were consuming this package (after having published to\nnpm), you can import it as follows:\n\n```json\n{\n  \"name\": \"bar\",\n  \"buildDirs\": {\n    \"library\": {\n      \"imports\": [\n        \"ThatFooLibrary = require('@myscope/foo/library')\"\n      ]\n    }\n  }\n```\n\n\u003ccontinueRight/\u003e\n\nWith the new NPM like conventions, pesy automatically handles the\nnamespace for you so that we don't have to worry about the nuances of\na package and a library during development. \n\n\n## Dune files\n\n`pesy` generates dune files on fly behind the scenes. And to be able to so,\nit needs a static dune file that looks like the following\n\n```sexp\n(* -*- tuareg -*- *)\n\nopen Jbuild_plugin.V1\n\nlet () =\n  run_and_read_lines (\"pesy dune-file \" ^ Sys.getcwd ())\n  |\u003e String.concat \"\\n\"\n  |\u003e send\n```\n\nThese have to created only once - after that they never change (unless you decide\nto eject). Every bootstrapped project has them already and you dont have to create\nthem. In case, you need to re-generate them, run `esy pesy` (or `esy\n@mysandbox pesy` if you are using `esy` sandboxes). A common need for running `esy pesy`\nis when you add a new folder to your project.\n\n#### Ejecting:\n\nIt is always possible to eject out of pesy config by running `esy pesy\neject ./subpackage-path`\n\n## CI \u0026 local cache Warming up\n\nCompilation artifacts built on the CI can be downloaded by `pesy warmup`.  At\nthe moment, we only support Azure Pipelines can be configured as follows.\n\n```json\n{\n  \"pesy\": {\n    \"azure-project\":\n      \"\u003cazure-project\u003e/\u003cazure-pipeline-name\u003e\",\n    \"github\":\n      \"\u003cgithub-org\u003e/\u003cgithub-repo\u003e\"\n  }\n}\n```\n\u003ccontinueRight/\u003e\n\nThis will fetch appropriate artifacts compiled for the current\nmachine. Note however, the best way to get this feature to work is to\nuse cache publish and restore mechanism provided bootstrapped\nfiles. `pesy` assumes that the artifact zip file names and/or paths\nhaven't changed.\n\n\n#### How it works\nCompilation artifacts created by `esy` are relocatable - since `esy`\nsandboxes isolated, all the dependencies are accounted for and each\ndependency is loaded from a path with a fixed-length prefix. Such\nartifacts can be built on one machine and used on other provided the\nprefixes are rewritten to reflect the updated path on the new\nmachine. And `esy` provides all the low level command to do this out of\nthe box. `pesy` simply provides a convenient wrapper that drives them.\n\nWhen the project is bootstrapped for the first time, it is identical to a copy\nthat is run on the server. This is where the first set of cached builds come from,\nwhich is why the `azure-project` in the pesy config is set to `esy-dev/esy` and\n`github` to `esy/pesy-reason-template`.\n\nOnce the project sees changes, you most probably would add more dependencies \n(or remove some) which could change the build sandbox state. It is recommended that\nyou run the provided CI setup to cache builds on your own Azure Pipelines instance \nand update `github` and `azure-project` accordingly.\n\n## Publishing and Consuming from NPM\n#### Publishing libraries\n\nEasiest way to get started with distributing you library is to publish\nthe source to NPM. \n\nLet's take a look at an example.\n\nConsider a base package `foo` that you created and distributed on NPM. And let's assume, `bar` is the package that consumes `foo`.\n\n```sh\n$ pesy --directory foo-lib\n```\n\nThis would have bootstrapped a project with the default template with\n`Util.re` in the `library/` folder.\n\nLet's publish it\n\n```sh\n$ npm publish\n```\n\n#### Consuming `foo-lib`\n\nLet quickly create a new project, `bar` and add `foo`.\n\n```sh\n$ pesy --directory bar\n$ esy add foo-lib\n```\n\nWe can now require `foo`\n\n```diff\n  \"buildDirs\": {\n    \"library\": {\n      \"imports\": [\n+        \"FooLib = require('foo-lib/library')\"\n      ]\n    }\n  }\n```\n\nAnd then edit Utils.re\n\n```reason\nlet foo = () =\u003e {\n  FooLib.Util.foo();\n  print_endline(\"This is from bar\");\n};\n```\n\n```sh\n$ esy\n$ esy start\nHello from foo!\nThis is from bar\n```\n\n## Templates [experimental]\n\n#### Usage\n\nTo use a custom template run `pesy --template=github:your-name/your-pesy-template`\n  \n#### Creating your own template\n\n*This is a experimental feature that could see a lot of churn. We\nrequest you to watch the issue tracker for updates.*\n\nIt works by downloading a git repo and then replacing special strings\nin filenames and files inside the repo. The special strings are\ncurrently these: \n\n###### In file names:\n\n| In filename                      |  Replaced with                |\n| -------------------------------- |  ---------------------------- |\n| `__PACKAGE_NAME__`               |  `package_name`               |\n| `__PACKAGE_NAME_FULL__`          |  `package_name`               |\n| `__PACKAGE_NAME_UPPER_CAMEL__`   |  `PackageName`                |\n\n\n###### In file content:\n\n| In contents                  | Replaced with                |\n| ---------------------------- | ---------------------------- |\n| `\u003cPACKAGE_NAME\u003e`             | `package_name`               |\n| `\u003cPACKAGE_NAME_FULL\u003e`        | `package_name`               |\n| `\u003cPACKAGE_NAME_UPPER_CAMEL\u003e` | `PackageName`                |\n| `\u003cVERSION\u003e`                  | `version`                    |\n| `\u003cPUBLIC_LIB_NAME\u003e`          | `package_name/library`       |\n| `\u003cTEST_LIB_NAME\u003e`            | `package_name/test`          |\n\nBest way to get started creating a new template is to download\nhttps://github.com/esy/pesy-reason-template and work on it. Any\nchanges can be tested with `pesy test-template`.\n\n## Supported Dune Config\nThis is reference guide explaining the config `pesy` supports.\n\n### Binaries\n\nConfiguration that applies to subpackages that create binary\nexecutables. Note that these executables can be ocaml bytecode or\nnative binaries (ELF/Mach/PE) \n\n#### bin : `string`\nA subpackage produces binary when it contains a `bin` property. \n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n      \"bin\": \"Main.re\"\n    }\n  }\n}\n```\n\n\u003ccontinueRight/\u003e\n\nHere is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/simple-bin/package.json)\n\n#### modes : `array(string)`\nAn array of [advanced linking\nmodes](https://dune.readthedocs.io/en/latest/dune-files.html?highlight=modules_without_implementation#linking-modes). Each\nstring should be of the form \"`compilation-mode` `binary-kind`\" where\n`compilation-mode` is one byte, native or best and `binary-kind` is\none of c, exe, object, shared_object. \n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n\t  \"bin\": \"Foo.re\",\n\t  \"modes\": [ \"native\", \"exe\"] \n\t}\n  }\n}\n```\n\n\u003ccontinueRight/\u003e\n\nHere is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/bin-modes/package.json)\n\n#### name (override) : `string`\nA string that maps to Dune's `public_name`. Usually unnecessary (as\n`bin` property takes care of it) and must only be used to override. \n\n#### main (override) : `string`\nA string that maps to Dune's `name`. Usually unnecessary (as `bin`\nproperty takes care of it) and must only be used to override the entry\npoint. \n\n### Libraries\n\n#### modes : `array(string)`\n\n`modes` can be used to configure the compilation target - [native](https://caml.inria.fr/pub/docs/manual-ocaml/native.html) or\n[bytecode](https://caml.inria.fr/pub/docs/manual-ocaml/comp.html) An\narray of string, any of `byte`, `native`, `best` \n\n##### `\"byte\"` \nThis mode generates byte code output\n\n##### `\"native\"`\nThis mode generates native output\n\n##### `\"best\"`\nSometimes it may not be obvious if native compilation is supported on a\nmachine. In such circumstances, use `\"best\"` and `\"native\"` will be picked for\nyou if it's available. Else, it'll be `\"byte\"`\n\n#### cNames : `array(string)`\nWhen writing C stubs to FFI into a library, simply mention the file\nname (without the `.c` extension) in the `cNames` field.\n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n      \"cNames\": [\"my-stub1\", \"my-stub-2\"]\n\t}\n  }\n}\n```\n\n#### foreignStubs : `list(ForeignStub)`\nFrom dune version 2.0 onwards the cNames field was removed and foreignStubs field\nwas introduced to provide the FFI functionality, foreignStubs is a list of objects, \nwhere each foreignStub object should specify `language`, `names` \u0026 `flags`.\nIncase `names` \u0026 `flags` is not specified or empty, their default value will be considered\n[Refer this for default values for names \u0026 flags](https://dune.readthedocs.io/en/stable/concepts.html#foreign-stubs)\n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n      \"foreignStubs\": [\n        {\n          \"language\": \"c\",\n          \"names\": [\"my-stub1\", \"my-stub-2\"],\n          \"flags\": [\"-verbose\"]\n        } \n      ]\n\t}\n  }\n}\n```\n\n\n\u003ccontinueRight/\u003e\n\nHere is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/cNames/package.json)\n\n### Config supported by both libraries and binaries\n\n#### imports : `array(string)`\n\n`imports` can be used to import a library (from a subpackage or an\nexternal npm/opam package) and utilise the namespace of the imported\nlibrary.\n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n\t  \"imports\": [\n\t    \"Console = require('console')\"\n\t  ]\n\t}\n  }\n}\n```\n\nThe above config makes a namespace `Console` available inside the\nsubpackage `src`. Now any `.re` file inside `src` can use the\n`console` library.\n\n```reason\n// src/SomeFile.re\nlet foo = () =\u003e Console.log(\"Hello, world\")\n```\n\nWe can also import a package/subpackage under a different namespace\n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n\t  \"imports\": [\n\t    \"NotConsole = require('console')\"\n\t  ]\n\t}\n  }\n}\n```\n\nAnd we can import (oddly confusing) `NotConsole` from in `src`\n\n```reason\n// src/SomeFile.re\nlet foo = () =\u003e NotConsole.log(\"Hello, world\");\n```\n\nWe can also import other subpackages in the project.\n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n      \"bin\": \"Main.re\",\n      \"imports\": [\n        \"FooConsole = require('console/lib')\",\n        \"MyOwnLibrary = require('../my-own-lib')\"\n      ]\n    },\n    \"my-own-lib\": {}\n  }\n}\n```\n\n\u003ccontinueRight/\u003e\n\nHere is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/imports/package.json)\n\n#### Compiler flags\n###### All of type *list(string)*\n\n- `flags` - These flags will be passed to both the bytecode compiler and native compiler\n- `ocamlcFlags` - These will be passed to `ocamlc` - the bytecode compiler\n- `ocamloptFlags` - These will be passed to `ocamlopt` - the native compiler\n- `jsooFlags` - These will be passed to `jsoo` compiler - the [javascript compiler](http://ocsigen.org/js_of_ocaml/3.5.1/manual/overview). *Note: This is unrelated to [Bucklescript](https://bucklescript.github.io/)*\n\n\n\n#### Preprocessor flags : `list(string)`\n\n`preprocess` accepts options needed to pass the source files via a\npreprocessor first. When using custom syntax not natively supported in\nthe compiler, we pass the sources in a subpackage via a preprocessor\nfirst.\n\nFor example, suppose we'd like to use `let%lwt` syntax for our `Lwt` promises\n\n```reason\n\nlet%lwt foo = Lwt.return(\"foo\");\nprint_endline(foo);\n\n// instead of \nLwt.return \u003e\u003e=\n  (foo =\u003e print_endline(foo); Lwt.return())\n```\n\nWe specify `lwt_ppx` in `pps preprocess`\n\n```json\n{\n  \"buildDirs\": {\n    \"src\": {\n\t  \"bin\": \"Main.re\",\n\t  \"preprocess\": [\"pps\", \"lwt_ppx\"]\n\t}\n  }\n}\n```\n\n\u003ccontinueRight/\u003e\n\nHere is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/preprocess/package.json)\n\n#### includeSubdirs : `string = \"no\"|\"unqualified\"`\n\nDefault is \"no\", and changing to \"unqualified\" will compile modules at\ndeeper directories.\n\n#### Escape hatches for unsupported Dune config\n\nIt's always possible that there are features Dune offers are needed and\nthe options above are not enough. Use `rawBuildConfig` to add\noptions in a given library or binary. Use `rawBuildConfigFooter` to\nadd config to the footer.\n\n##### rawBuildConfig : `list(string)`\n\nExample\n\n```json\n{\n  \"src\": {\n    \"rawBuildConfig\": [ \"(libraries unix)\" ],\n    \"bin\": \"Main.re\"\n  }\n}\n```\n##### rawBuildConfigFooter : `list(string)`\n\nExample\n\n```json\n{\n  \"src\": {\n    \"rawBuildConfigFooter\": [\n      \"(install (section share_root) (files (plaintext.txt as asset.txt)))\"\n    ]\n  }\n}\n```\n\n\u003ccontinueRight/\u003e\n\nHere is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/raw/package.json)\n  \n## Tutorials\n  \n#### Simple native example\n#### CLI Apps\n#### Web Development with Morph\n  \n## Development\n\nClone the repo and run `esy` on it.\n\n#### e2e tests\n\n`./_build/install/default/bin` would contain (after running `esy`) `Runner.exe`. `Runner.exe` looks for `PESY_CLONE_PATH` variable in the environment to find `pesy` source. Set it to the path where the project was cloned.\n\nTo test if simple workflows work as expected. They assume both `esy` and `pesy` are installed\nglobally (as on user's machines).\n\n## Changelog\n**version 0.4.0 (12/21/2018)**\n\n- Allow `buildDirs` to contain deeper directories such as `\"path/to/my-lib\": {...}\"`.\n- Added support for `wrapped` property on libraries.\n- Added support for `virtualModules` and `implements` - properties for Dune\n  virtual libraries. (This will only be supported if you mark your project as\n  Dune 1.7 - not yet released).\n- Stopped using `ignore_subdirs` in new projects, instead using\n  `(dirs (:standard \\ _esy))` which only works in Dune `1.6.0+`, so made new\n  projects have a lower bound of Dune `1.6.0`.\n- Support new properties `rawBuildConfig` which will be inserted at the bottom\n  of the _target_ being configured (library/executable).\n  - It expects an array of strings, each string being a separate line in the\n    generated config.\n- Support new properties `rawBuildConfigFooter` which will be inserted at the\n  bottom of the entire Dune file for the _target_ being configured.\n  - It expects an array of strings, each string being a separate line in the\n    generated config.\n- Support new properties `modes` for binaries and libraries `list(string)`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesy%2Fpesy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fesy%2Fpesy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fesy%2Fpesy/lists"}