Showing preview only (1,177K chars total). Download the full file or copy to clipboard to get everything.
Repository: esy/pesy
Branch: master
Commit: 0f29cb3885f4
Files: 182
Total size: 1.1 MB
Directory structure:
gitextract_x9up97r8/
├── .ci/
│ ├── build-platform.yml
│ ├── checksum.js
│ ├── cross-release.yml
│ ├── esy-build-steps.yml
│ ├── opam-build-steps.yml
│ ├── pipelines-release.js
│ ├── release-platform-setup.yml
│ ├── release-postinstall.js
│ └── utils/
│ ├── create-docs.yml
│ ├── publish-build-cache.yml
│ ├── restore-build-cache.yml
│ ├── use-cache-esy.yml
│ ├── use-cache-npm.yml
│ ├── use-cache-yarn.yml
│ ├── use-esy.yml
│ └── use-node.yml
├── .gitignore
├── .npmignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── ORIGIN.md
├── PesyE2E.opam
├── README.html
├── azure-pipelines.yml
├── bin/
│ ├── Pesy.re
│ └── dune
├── dune
├── dune-project
├── e2e-tests/
│ ├── .TestPesyConfigure.re.swp
│ ├── .gitignore
│ ├── Rimraf.re
│ ├── Runner.re
│ ├── Utils.re
│ ├── dune
│ └── pending-tests.re
├── errors/
│ ├── Errors.re
│ └── dune
├── lib/
│ ├── Common.re
│ ├── Common.rei
│ ├── DuneFile.re
│ ├── DuneFile.rei
│ ├── DuneProject.re
│ ├── DuneProject.rei
│ ├── EsyCommand.re
│ ├── Executable.re
│ ├── Executable.rei
│ ├── ImportsParser.re
│ ├── Lexer.mll
│ ├── Lib.re
│ ├── Library.re
│ ├── Library.rei
│ ├── Mode.re
│ ├── Parser.mly
│ ├── PesyConf.re
│ ├── PesyModule.re
│ ├── Stanza.re
│ ├── Stanza.rei
│ ├── Stubs.re
│ ├── Stubs.rei
│ ├── Test.re
│ ├── Test.rei
│ └── dune
├── notes/
│ ├── benchmarking.md
│ ├── checksum-verification.md
│ ├── compiler-support.org
│ ├── e2e.org
│ └── release.org
├── npm-cli/
│ ├── .gitignore
│ ├── .npmignore
│ ├── .vscode/
│ │ └── tasks.json
│ ├── README.md
│ ├── __tests__/
│ │ └── utils_test.re
│ ├── bsconfig.json
│ ├── esy.json
│ ├── package.json
│ ├── pesy
│ ├── pesy.bundle.js.minisig
│ ├── pesy.js
│ ├── rollup.config.js
│ ├── scripts/
│ │ └── vendor-template.js
│ ├── src/
│ │ ├── AzurePipelines.re
│ │ ├── Bindings.re
│ │ ├── Bootstrapper.re
│ │ ├── Bootstrapper.rei
│ │ ├── Cmd.re
│ │ ├── DefaultTemplate.re
│ │ ├── Dir.re
│ │ ├── Dir.rei
│ │ ├── Esy.re
│ │ ├── Esy.rei
│ │ ├── EsyLock.re
│ │ ├── EsyLock.rei
│ │ ├── EsyStatus.re
│ │ ├── EsyStatus.rei
│ │ ├── Path.re
│ │ ├── Path.rei
│ │ ├── Pesy.re
│ │ ├── Pesy.rei
│ │ ├── PesyConfig.re
│ │ ├── PesyConfig.rei
│ │ ├── Project.re
│ │ ├── Project.rei
│ │ ├── Readline.re
│ │ ├── Result.re
│ │ ├── ResultPromise.re
│ │ ├── ResultPromise.rei
│ │ ├── Spinner.re
│ │ ├── Spinner.rei
│ │ ├── Template.re
│ │ ├── Template.rei
│ │ ├── Utils.re
│ │ └── Warmup.re
│ ├── stubs/
│ │ ├── crypto.js
│ │ ├── fs.js
│ │ └── resolve.js
│ ├── templates/
│ │ ├── .gitignore
│ │ ├── .npmignore
│ │ ├── ci/
│ │ │ ├── .ci/
│ │ │ │ ├── build-docker.yml
│ │ │ │ ├── build-platform.yml
│ │ │ │ ├── cross-release.yml
│ │ │ │ ├── esy-build-steps.yml
│ │ │ │ ├── opam-build-steps.yml
│ │ │ │ ├── pipelines-release.js
│ │ │ │ ├── release-platform-setup.yml
│ │ │ │ ├── release-postinstall.js
│ │ │ │ └── utils/
│ │ │ │ ├── create-docs.yml
│ │ │ │ ├── publish-build-cache.yml
│ │ │ │ ├── publish-sources.yml
│ │ │ │ ├── restore-build-cache.yml
│ │ │ │ ├── use-cache-esy.yml
│ │ │ │ ├── use-cache-yarn.yml
│ │ │ │ ├── use-esy.yml
│ │ │ │ └── use-node.yml
│ │ │ └── azure-pipelines-template.yml
│ │ └── docker/
│ │ └── docker/
│ │ ├── DevImage.Dockerfile
│ │ └── ProdImage.Dockerfile
│ └── v0.4.4/
│ ├── LICENSE
│ ├── ORIGIN.md
│ ├── README.md
│ ├── azure-ci-template/
│ │ ├── azure-pipelines.yml
│ │ ├── esy-build-steps.template.yml
│ │ ├── publish-build-cache.yml
│ │ └── restore-build-cache.yml
│ ├── esy-peasy
│ ├── notes/
│ │ └── benchmarking.md
│ ├── pesy
│ ├── pesy-JSON.sh
│ ├── pesy-README.template.md
│ ├── pesy-create.sh
│ ├── pesy-footer.template.sh
│ ├── pesy-genBin.template.sh
│ ├── pesy-genLib.template.sh
│ ├── pesy-gitignore.template
│ ├── pesy-header.sh
│ ├── pesy-name-utils.sh
│ └── pesy-package.template.json
├── package.json
├── pesy--esy-pesy.opam
├── scripts/
│ ├── bootstrap.sh
│ ├── run.bat
│ ├── run.sh
│ ├── simulate-latest.js
│ └── verdaccio.yaml
├── site/
│ ├── ORIGINS.md
│ ├── Reload.js
│ ├── fonts/
│ │ ├── CodingFont.css
│ │ ├── LICENSE-Fira
│ │ ├── LICENSE-Roboto
│ │ └── WordFont.css
│ ├── index.dev.html
│ ├── package.json
│ └── theme-white/
│ ├── theme.js
│ └── theme.styl.html
├── unit-tests/
│ └── runner/
│ ├── Lib.re
│ ├── RunUnitTests.re
│ ├── Utils.re
│ └── dune
└── utils/
├── FieldTypes.re
├── JSON.re
├── JSON.rei
├── Utils.re
└── dune
================================================
FILE CONTENTS
================================================
================================================
FILE: .ci/build-platform.yml
================================================
parameters:
platform: "macOS"
vmImage: "macOS-latest"
jobs:
- job: ${{ parameters.platform }}
pool:
vmImage: ${{ parameters.vmImage }}
demands: node.js
timeoutInMinutes: 120 # This is mostly for Windows
steps:
- powershell: $Env:Path
continueOnError: true
condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))))
displayName: "Print env in powershell"
# Needed so that the mingw tar doesn't shadow the system tar. See
# pipelines.yaml. We need windows bsdtar from system32, not the mingw
# one. Note powershell doesn't need escaping of backslashes.
- powershell: Write-Host "##vso[task.setvariable variable=PATH;]C:\Program Files\Git\bin;C:\Windows\system32;${env:PATH}"
continueOnError: true
condition: eq(variables['AGENT.OS'], 'Windows_NT')
displayName: "Make sure C:/Program Files/Git/bin and windows/system32 is at front of path if windows"
- powershell: $Env:Path
continueOnError: true
condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))))
displayName: "Print env in powershell"
- powershell: get-command tar
continueOnError: true
condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))))
displayName: "Print where tar is located"
- powershell: tar --help
continueOnError: true
condition: and(eq(variables['AGENT.OS'], 'Windows_NT'), and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))))
displayName: "Print tar help"
- powershell: Write-Host "##vso[task.setvariable variable=HOME;]D:\"
continueOnError: true
condition: eq(variables['AGENT.OS'], 'Windows_NT')
displayName: "Make sure $HOME is same is Agent.BuildDirectory"
- bash: |
# COMPUTE THE ESY INSTALL CACHE LOCATION AHEAD OF TIME
DESIRED_LEN="86"
# Note: This will need to change when upgrading esy version
# that reenables long paths on windows.
# if [ "$AGENT_OS" == "Windows_NT" ]; then
# DESIRED_LEN="33"
# fi
HOME_ESY3="$HOME/.esy/3"
HOME_ESY3_LEN=${#HOME_ESY3}
NUM_UNDERS=$(echo "$(($DESIRED_LEN-$HOME_ESY3_LEN))")
UNDERS=$(printf "%-${NUM_UNDERS}s" "_")
UNDERS="${UNDERS// /_}"
THE_ESY__CACHE_INSTALL_PATH=${HOME_ESY3}${UNDERS}/i
if [ "$AGENT_OS" == "Windows_NT" ]; then
THE_ESY__CACHE_INSTALL_PATH=$( cygpath --mixed --absolute "$THE_ESY__CACHE_INSTALL_PATH")
fi
echo "THE_ESY__CACHE_INSTALL_PATH: $THE_ESY__CACHE_INSTALL_PATH"
# This will be exposed as an env var ESY__CACHE_INSTALL_PATH, or an
# Azure var esy__cache_install_path
echo "##vso[task.setvariable variable=esy__cache_install_path]$THE_ESY__CACHE_INSTALL_PATH"
# - bash: |
# which esy
# echo "$( which esy )"
# echo "##vso[task.setvariable variable=esy_bin_location]$(which esy)"
# displayName: "Find esy binary"
# - bash: echo ${ESY_BIN_LOCATION}
# displayName: "Print esy bin location"
- bash: env
displayName: "Print environment"
- template: esy-build-steps.yml
# - task: PublishBuildArtifacts@1
# displayName: "Publish Artifact: ${{ parameters.platform }}"
# inputs:
# PathtoPublish: "_release"
# ArtifactName: ${{ parameters.platform }}
================================================
FILE: .ci/checksum.js
================================================
let crypto = require("crypto"),
path = require("path"),
fs = require("fs");
let algorithm = "sha1",
shasum = crypto.createHash(algorithm);
let cwd = process.cwd();
let pkg = require(path.join(cwd, "package.json")),
s = fs.ReadStream(
path.join(
cwd,
"_release",
`${pkg.name.replace("@", "").replace("/", "-")}-${pkg.version}.tgz`
)
);
s.on("data", function(data) {
shasum.update(data);
});
s.on("end", function() {
var hash = shasum.digest("hex");
console.log(`SHA1: ${hash}`);
});
================================================
FILE: .ci/cross-release.yml
================================================
steps:
- script: echo "No release steps. They are disabled"
displayName: "[Disabled] Release steps"
# - template: utils/use-node.yml
# - script: "mkdir _release"
# displayName: "Create _release dir"
# - template: release-platform-setup.yml
# parameters:
# platform: "Linux"
# folder: "platform-linux"
# - template: release-platform-setup.yml
# parameters:
# platform: "macOS"
# folder: "platform-darwin"
# - template: release-platform-setup.yml
# parameters:
# platform: "Windows"
# folder: "platform-windows-x64"
# - script: "node .ci/pipelines-release.js"
# displayName: "node .ci/pipelines-release.js"
# continueOnError: true
# - script: "npm pack ."
# displayName: "npm pack"
# workingDirectory: "_release"
# - bash: "node .ci/checksum.js _release/pesy-0.1.0-dev.21.tgz"
# displayName: "Calculating sha1"
# - task: PublishBuildArtifacts@1
# displayName: "Publish Artifact: Release"
# inputs:
# PathtoPublish: "_release"
# ArtifactName: Release
================================================
FILE: .ci/esy-build-steps.yml
================================================
# Cross-platform set of build steps for building esy projects
steps:
- template: utils/use-node.yml
- template: utils/use-cache-yarn.yml
- template: utils/use-esy.yml
- template: utils/use-cache-esy.yml
- script: "esy install"
displayName: "esy install"
- template: utils/restore-build-cache.yml # Run this to make sure cached prebuilts work.
- script: "esy b dune build --only-packages pesy--esy-pesy,PesyE2E"
displayName: "esy build"
- bash: "ls -lR _build"
displayName: "Contents of _build"
- template: utils/create-docs.yml
- script: mkdir $(System.DefaultWorkingDirectory)\..\tests-tmp-dir
displayName: 'Creating temporary workspace for tests'
condition: eq(variables['Agent.OS'], 'Windows_NT')
- script: .\_build\default\e2e-tests\Runner.exe
displayName: 'Running e2e tests'
condition: eq(variables['Agent.OS'], 'Windows_NT')
env:
OCAMLRUNPARAM: 'b'
PESY_CLONE_PATH: $(Build.SourcesDirectory)
TEMP: $(System.DefaultWorkingDirectory)\..\tests-tmp-dir
- script: ./_build/default/e2e-tests/Runner.exe
displayName: 'Running e2e tests'
condition: ne(variables['Agent.OS'], 'Windows_NT')
env:
PESY_CLONE_PATH: $(Build.SourcesDirectory)
OCAMLRUNPARAM: 'b'
- task: PublishBuildArtifacts@1
displayName: 'Upload sources'
condition: eq(variables['Agent.OS'], 'Linux')
inputs:
pathToPublish: './npm-cli/pesy-0.5.0-dev.23.tgz'
artifactName: 'pesy-npm-cli-source'
parallel: true
parallelCount: 8
# - script: "esy release"
# displayName: "esy release"
- script: "esy test"
displayName: "Test command"
- bash: rm -rf ~/.esy/3/b
displayName: 'Emptying ~/.esy/3/b to clear up space'
- template: utils/publish-build-cache.yml
# Run tests or any additional steps here
# - script: esy b dune runtest
================================================
FILE: .ci/opam-build-steps.yml
================================================
# Shared steps for building OPAM projects
steps:
- task: NodeTool@0
inputs:
versionSpec: '8.9'
- script: brew install gpatch
displayName: 'brew install gpatch'
- script: brew install opam
displayName: 'brew install opam'
- script: opam --version
displayName: 'Check opam version'
- script: opam init --auto-setup --dot-profile=~/.bash_profile
displayName: 'opam init'
- script: opam remote add ocamlorg https://opam.ocaml.org || true
displayName: 'opam remote add ocamlorg'
- script: opam remove default || true
displayName: 'opam remove default'
- script: opam update
displayName: 'opam update'
- script: opam switch create $(OCAML_VERSION)
displayName: 'Use OCaml version: $(OCAML_VERSION)'
- script: opam install -y dune
displayName: 'opam install -y dune'
- script: opam install -y menhir
displayName: 'opam install -y menhir'
- script: opam install -y utop
displayName: 'opam install -y utop'
- script: make clean-for-ci
displayName: 'make clean-for-ci'
- script: opam pin add -y reason .
displayName: "opam pin add -y reason ."
- script: opam pin add -y rtop .
displayName: "opam pin add -y rtop ."
- script: eval $(opam env); make test-ci
displayName: "make test-ci"
- script: git diff --exit-code
displayName: "Check git is clean"
================================================
FILE: .ci/pipelines-release.js
================================================
const fs = require("fs");
const path = require("path");
console.log("Creating package.json");
// From the project root pwd
const mainPackageJsonPath =
fs.existsSync('esy.json') ?
'esy.json' : 'package.json';
const exists = fs.existsSync(mainPackageJsonPath);
if (!exists) {
console.error("No package.json or esy.json at " + mainPackageJsonPath);
process.exit(1);
}
// Now require from this script's location.
const mainPackageJson = require(path.join('..', mainPackageJsonPath));
const bins =
Array.isArray(mainPackageJson.esy.release.bin) ?
mainPackageJson.esy.release.bin.reduce(
(acc, curr) => Object.assign({ [curr]: "bin/" + curr }, acc),
{}
) :
Object.keys(mainPackageJson.esy.release.bin).reduce(
(acc, currKey) => Object.assign({ [currKey]: "bin/" + mainPackageJson.esy.release.bin[currKey] }, acc),
{}
);
const rewritePrefix =
mainPackageJson.esy &&
mainPackageJson.esy.release &&
mainPackageJson.esy.release.rewritePrefix;
const packageJson = JSON.stringify(
{
name: mainPackageJson.name,
version: mainPackageJson.version,
license: mainPackageJson.license,
description: mainPackageJson.description,
repository: mainPackageJson.repository,
scripts: {
postinstall:
rewritePrefix ?
"ESY_RELEASE_REWRITE_PREFIX=true node ./postinstall.js" :
"node ./postinstall.js"
},
bin: bins,
files: [
"_export/",
"bin/",
"postinstall.js",
"esyInstallRelease.js",
"platform-linux/",
"platform-darwin/",
"platform-windows-x64/"
]
},
null,
2
);
fs.writeFileSync(
path.join(__dirname, "..", "_release", "package.json"),
packageJson,
{
encoding: "utf8"
}
);
try {
console.log("Copying LICENSE");
fs.copyFileSync(
path.join(__dirname, "..", "LICENSE"),
path.join(__dirname, "..", "_release", "LICENSE")
);
} catch (e) {
console.warn("No LICENSE found");
}
console.log("Copying README.md");
fs.copyFileSync(
path.join(__dirname, "..", "README.md"),
path.join(__dirname, "..", "_release", "README.md")
);
console.log("Copying postinstall.js");
fs.copyFileSync(
path.join(__dirname, "release-postinstall.js"),
path.join(__dirname, "..", "_release", "postinstall.js")
);
console.log("Creating placeholder files");
const placeholderFile = `:; echo "You need to have postinstall enabled"; exit $?
@ECHO OFF
ECHO You need to have postinstall enabled`;
fs.mkdirSync(path.join(__dirname, "..", "_release", "bin"));
Object.keys(bins).forEach(
name => {
if(bins[name]) {
const binPath = path.join(
__dirname,
"..",
"_release",
bins[name]
);
fs.writeFileSync(binPath, placeholderFile);
fs.chmodSync(binPath, 0777);
} else {
console.log("bins[name] name=" + name + " was empty. Weird.");
console.log(bins);
}
}
);
================================================
FILE: .ci/release-platform-setup.yml
================================================
parameters:
platform: "macOS"
folder: "platform-darwin"
steps:
- task: DownloadBuildArtifacts@0
displayName: "Download ${{ parameters.platform }} Artifacts"
inputs:
artifactName: ${{ parameters.platform }}
downloadPath: $(Build.StagingDirectory)
- script: "mkdir _release/${{ parameters.folder }}"
displayName: "Create _release/${{ parameters.folder }}"
- script: "cp -r $(Build.StagingDirectory)/${{ parameters.platform }}/ _release/${{ parameters.folder }}"
displayName: "cp ${{ parameters.platform }}"
================================================
FILE: .ci/release-postinstall.js
================================================
/**
* release-postinstall.js
*
* XXX: We want to keep this script installable at least with node 4.x.
*
* This script is bundled with the `npm` package and executed on release.
* Since we have a 'fat' NPM package (with all platform binaries bundled),
* this postinstall script extracts them and puts the current platform's
* bits in the right place.
*/
var path = require("path");
var cp = require("child_process");
var fs = require("fs");
var os = require("os");
var platform = process.platform;
var packageJson = require("./package.json");
var binariesToCopy = Object.keys(packageJson.bin)
.map(function(name) {
return packageJson.bin[name];
})
.concat(["esyInstallRelease.js"]);
var foldersToCopy = ["bin", "_export"];
function copyRecursive(srcDir, dstDir) {
var results = [];
var list = fs.readdirSync(srcDir);
var src, dst;
list.forEach(function(file) {
src = path.join(srcDir, file);
dst = path.join(dstDir, file);
var stat = fs.statSync(src);
if (stat && stat.isDirectory()) {
try {
fs.mkdirSync(dst);
} catch (e) {
console.log("directory already exists: " + dst);
console.error(e);
}
results = results.concat(copyRecursive(src, dst));
} else {
try {
fs.writeFileSync(dst, fs.readFileSync(src));
} catch (e) {
console.log("could't copy file: " + dst);
console.error(e);
}
results.push(src);
}
});
return results;
}
/**
* Since os.arch returns node binary's target arch, not
* the system arch.
* Credits: https://github.com/feross/arch/blob/af080ff61346315559451715c5393d8e86a6d33c/index.js#L10-L58
*/
function arch() {
/**
* The running binary is 64-bit, so the OS is clearly 64-bit.
*/
if (process.arch === "x64") {
return "x64";
}
/**
* All recent versions of Mac OS are 64-bit.
*/
if (process.platform === "darwin") {
return "x64";
}
/**
* On Windows, the most reliable way to detect a 64-bit OS from within a 32-bit
* app is based on the presence of a WOW64 file: %SystemRoot%\SysNative.
* See: https://twitter.com/feross/status/776949077208510464
*/
if (process.platform === "win32") {
var useEnv = false;
try {
useEnv = !!(
process.env.SYSTEMROOT && fs.statSync(process.env.SYSTEMROOT)
);
} catch (err) {}
var sysRoot = useEnv ? process.env.SYSTEMROOT : "C:\\Windows";
// If %SystemRoot%\SysNative exists, we are in a WOW64 FS Redirected application.
var isWOW64 = false;
try {
isWOW64 = !!fs.statSync(path.join(sysRoot, "sysnative"));
} catch (err) {}
return isWOW64 ? "x64" : "x86";
}
/**
* On Linux, use the `getconf` command to get the architecture.
*/
if (process.platform === "linux") {
var output = cp.execSync("getconf LONG_BIT", { encoding: "utf8" });
return output === "64\n" ? "x64" : "x86";
}
/**
* If none of the above, assume the architecture is 32-bit.
*/
return "x86";
}
// implementing it b/c we don't want to depend on fs.copyFileSync which appears
// only in node@8.x
function copyFileSync(sourcePath, destPath) {
var data;
try {
data = fs.readFileSync(sourcePath);
} catch (e) {
console.log("Couldn't find " + sourcePath + " trying with .exe");
data = fs.readFileSync(sourcePath + ".exe");
sourcePath = sourcePath + ".exe";
}
var stat = fs.statSync(sourcePath);
fs.writeFileSync(destPath, data);
fs.chmodSync(destPath, stat.mode);
}
var copyPlatformBinaries = platformPath => {
var platformBuildPath = path.join(__dirname, "platform-" + platformPath);
foldersToCopy.forEach(folderPath => {
var sourcePath = path.join(platformBuildPath, folderPath);
var destPath = path.join(__dirname, folderPath);
copyRecursive(sourcePath, destPath);
});
binariesToCopy.forEach(binaryPath => {
var sourcePath = path.join(platformBuildPath, binaryPath);
var destPath = path.join(__dirname, binaryPath);
if (fs.existsSync(destPath)) {
fs.unlinkSync(destPath);
}
copyFileSync(sourcePath, destPath);
fs.chmodSync(destPath, 0777);
});
};
try {
fs.mkdirSync("_export");
} catch (e) {
console.log("Could not create _export folder");
}
switch (platform) {
case "win32":
if (arch() !== "x64") {
console.warn("error: x86 is currently not supported on Windows");
process.exit(1);
}
copyPlatformBinaries("windows-x64");
break;
case "linux":
case "darwin":
copyPlatformBinaries(platform);
break;
default:
console.warn("error: no release built for the " + platform + " platform");
process.exit(1);
}
require("./esyInstallRelease");
================================================
FILE: .ci/utils/create-docs.yml
================================================
# These steps are only run on Linux
steps:
- script: "esy doc"
displayName: "Build docs"
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- script: echo '##vso[task.setvariable variable=docsPath]'$(esy echo '_build/default/_doc/_html')
displayName: "Save docsPath in variable"
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
- task: PublishBuildArtifacts@1
displayName: "Publish Artifact: Docs"
condition: and(succeeded(), eq(variables['Agent.OS'], 'Linux'))
inputs:
PathtoPublish: $(docsPath)
ArtifactName: Docs
================================================
FILE: .ci/utils/publish-build-cache.yml
================================================
# Steps for publishing project cache
steps:
- bash: 'mkdir -p $(Build.StagingDirectory)'
condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI'))
displayName: '[Cache][Publish] Create cache directory'
# continueOnError because on windows it has a permission denied error but the
# export succeeds.
- script: "esy export-dependencies"
continueOnError: true
condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI'))
displayName: "esy export-dependencies"
- bash: pwd && ls _export/* && mv _export '$(Build.StagingDirectory)' && ls '$(Build.StagingDirectory)/_export/'
condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI'))
displayName: '[Cache][Publish] move export to staging dir'
# - bash: cd $ESY__CACHE_INSTALL_PATH && tar -czf $(Build.StagingDirectory)/esy-cache.tar .
# workingDirectory: ''
# condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI'))
# displayName: '[Cache][Publish] Tar esy cache directory'
# - bash: 'cd $(ESY__NPM_ROOT) && tar -czf $(Build.StagingDirectory)/npm-cache.tar .'
# condition: and(succeeded(), eq(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))
# displayName: '[Cache][Publish] Tar npm cache directory'
- task: PublishBuildArtifacts@1
displayName: '[Cache][Publish] Upload tarball'
condition: and(succeeded(), eq(variables['Build.Reason'], 'IndividualCI'))
# TODO: The CI Build caches are pulled down by the last successful buildID
# for the target branch.
inputs:
pathToPublish: '$(Build.StagingDirectory)'
artifactName: 'cache-$(Agent.OS)-install-$(esy__ci_cache_version)'
parallel: true
parallelCount: 8
================================================
FILE: .ci/utils/restore-build-cache.yml
================================================
# Steps for restoring project cache
steps:
- bash: 'mkdir -p $(Build.StagingDirectory)'
condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))
displayName: '[Cache][Publish] Create cache directory'
# TODO: This can be done in parallel with installing node, and esy
# (which would save a bunch of time on windows)
- task: Bash@3
condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))
displayName: '[Cache][Restore] Restoring build cache using REST API'
continueOnError: true
inputs:
targetType: 'inline' # Optional. Options: filePath, inline
script: |
# If org name is reasonml then REST_BASE will be: https://dev.azure.com/reasonml/
REST_BASE="${SYSTEM_TEAMFOUNDATIONCOLLECTIONURI}"
PROJ="$SYSTEM_TEAMPROJECT"
ART_NAME="cache-${AGENT_OS}-install-${ESY__CI_CACHE_VERSION}"
fetchLatestBuild() {
PREFIX="branchName=refs%2Fheads%2F"
BRANCH=${PREFIX}${SYSTEM_PULLREQUEST_TARGETBRANCH}
FILTER='deletedFilter=excludeDeleted&statusFilter=completed&resultFilter=succeeded'
LATEST='queryOrder=finishTimeDescending&$top=1'
REST_BUILDS="$REST_BASE/$PROJ/_apis/build/builds?${FILTER}&${BRANCH}&${LATEST}&api-version=4.1"
echo "Rest call for builds: $REST_BUILDS"
REST_BUILDS_RESP=$(curl "$REST_BUILDS")
if [[ $REST_BUILDS_RESP =~ (\"web\":\{\"href\":\")([^\"]*) ]]; then LATEST_BUILD_PAGE="${BASH_REMATCH[2]}"; else LATEST_BUILD_PAGE=""; fi
if [[ $REST_BUILDS_RESP =~ (\"badge\":\{\"href\":\")([^\"]*) ]]; then LATEST_BUILD_BADGE="${BASH_REMATCH[2]}"; else LATEST_BUILD_BADGE=""; fi
if [[ $REST_BUILDS_RESP =~ (\"id\":)([^,]*) ]]; then LATEST_BUILD_ID="${BASH_REMATCH[2]}"; else LATEST_BUILD_ID=""; fi
}
fetchLatestBuild
fetchArtifactURL() {
REST_ART="$REST_BASE/$PROJ/_apis/build/builds/$LATEST_BUILD_ID/artifacts?artifactName=$ART_NAME&api-version=4.1"
echo "Rest call for artifacts: $REST_ART"
if [[ $(curl $REST_ART) =~ (downloadUrl\":\")([^\"]*) ]]; then LATEST_ART_URL="${BASH_REMATCH[2]}"; else LATEST_ART_URL=""; fi
}
downloadArtifactAndContinue() {
if [ -z "$LATEST_ART_URL" ]
then
echo "No latest artifact for merge-target branch found at URL $REST_ART"
else
curl "$LATEST_ART_URL" > "${BUILD_STAGINGDIRECTORY}/$ART_NAME.zip"
PROJECT_DIR=$PWD
cd $BUILD_STAGINGDIRECTORY
unzip "$ART_NAME.zip"
echo "Using Dependency cache for buildID: $LATEST_BUILD_ID"
echo "Build log for build that produced the cache: $LATEST_BUILD_PAGE"
echo "Build badge for build that produced the cache: $LATEST_BUILD_BADGE"
echo "Build artifact from build that produced the cache: $LATEST_ART_URL"
echo "Restoring build cache into:"
mkdir -p $ESY__CACHE_INSTALL_PATH
echo $ESY__CACHE_INSTALL_PATH
echo "##vso[task.setvariable variable=esy_export_dir_to_import]${BUILD_STAGINGDIRECTORY}/${ART_NAME}/_export"
if [[ -d ${ART_NAME}/_export ]]; then
mv ${ART_NAME}/_export ${PROJECT_DIR}/_import
cd $PROJECT_DIR
echo "Cached builds to import"
ls ./_import
# import-build allows importing before esy install runs.
# unlike import-dependencies
echo "Going to import builds from ${PROJECT_DIR}/_import in the next build step"
if [ "$AGENT_OS" == "Windows_NT" ]; then
echo "Trying the cmd"
# Have to import the builds one by one in windows because
# there is some file locking issue in esy on windows only when
# importing many in parallel.
cd ./_import
FILES=*
for f in $FILES
do
echo "Trying to import $f"
esy.cmd import-build "$f"
done
cd $PROJECT_DIR
else
esy import-dependencies "_import"
fi
else
echo "No _export directory to import from build cache"
echo "Here's the contents of build cache:"
find ${BUILD_STAGINGDIRECTORY}
fi
fi
}
fetchArtifactURL
downloadArtifactAndContinue
- bash: 'rm -rf _import'
continueOnError: true
condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))
displayName: 'Remove import directory'
- bash: 'rm -rf *'
continueOnError: true
workingDirectory: '$(Build.StagingDirectory)'
condition: and(eq(variables['Build.Reason'], 'PullRequest'), and(succeeded(), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch'])))
displayName: '[Cache][Restore] Clean up staging dir'
================================================
FILE: .ci/utils/use-cache-esy.yml
================================================
steps:
- task: Cache@2
condition: and(eq(variables['Build.Reason'], 'PullRequest'), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))
inputs:
key: 'v1 | esy-install-cache | "$(Agent.OS)" | "$(Build.SourcesDirectory)/esy.lock/index.json"' # vPrimary, here, is just a way to bust cache during debugging. Inspired from https://docs.microsoft.com/en-us/azure/devops/pipelines/caching/?view=azure-devops#can-i-clear-a-cache"
path: $(ESY__CACHE_INSTALL_PATH)
cacheHitVar: ESY_INSTALL_CACHE_RESTORED
displayName: "Caching $(ESY__CACHE_INSTALL_PATH)"
- task: Cache@2
condition: and(eq(variables['Build.Reason'], 'PullRequest'), ne(variables['Build.SourceBranch'], variables['System.PullRequest.TargetBranch']))
inputs:
key: 'v1 | esy-sources | "$(Agent.OS)" | "$(Build.SourcesDirectory)/esy.lock/index.json"' # vPrimary, here, is just a way to bust cache during debugging. Inspired from https://docs.microsoft.com/en-us/azure/devops/pipelines/caching/?view=azure-devops#can-i-clear-a-cache"
path: "$(ESY__CACHE_INSTALL_PATH)/../../source"
cacheHitVar: ESY_SOURCE_CACHE_RESTORED
displayName: "Caching $(ESY__CACHE_INSTALL_PATH)/../../source"
================================================
FILE: .ci/utils/use-cache-npm.yml
================================================
steps:
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | "$(Build.SourcesDirectory)/npm-cli/package-lock.json"'
restoreKeys: |
npm | "$(Agent.OS)"
npm
path: $(HOME)/.npm
displayName: Cache npm
condition: ne(variables['AGENT.OS'], 'Windows_NT')
- task: Cache@2
inputs:
key: 'npm | "$(Agent.OS)" | "$(Build.SourcesDirectory)/npm-cli/package-lock.json"'
restoreKeys: |
npm | "$(Agent.OS)"
npm
path: $(AppData)/npm-cache
displayName: Cache npm
condition: eq(variables['AGENT.OS'], 'Windows_NT')
================================================
FILE: .ci/utils/use-cache-yarn.yml
================================================
steps:
- bash: |
YARN_CACHE_DIR=$(yarn cache dir)
echo "##vso[task.setvariable variable=YARN_CACHE_DIR]$YARN_CACHE_DIR"
- task: Cache@2
inputs:
key: 'yarn | "$(Agent.OS)" | "$(Build.SourcesDirectory)/npm-cli/yarn.lock"'
restoreKeys: |
yarn | "$(Agent.OS)"
yarn
path: $(YARN_CACHE_DIR)
displayName: Cache yarn
================================================
FILE: .ci/utils/use-esy.yml
================================================
# steps to install @esy-nightly/esy globally
steps:
- script: "sudo npm install --prefix /usr/local --unsafe-perm -g @esy-nightly/esy"
displayName: "install @esy-nightly/esy"
condition: ne(variables['Agent.OS'], 'Windows_NT')
- script: "npm install -g @esy-nightly/esy"
displayName: "install @esy-nightly/esy"
condition: eq(variables['Agent.OS'], 'Windows_NT')
================================================
FILE: .ci/utils/use-node.yml
================================================
# steps to use node on agent
steps:
- task: NodeTool@0
displayName: "Use Node 10.x"
inputs:
versionSpec: 10.x
================================================
FILE: .gitignore
================================================
.DS_Store
npm-debug.log
.merlin
yarn-error.log
node_modules/
_build
_esy
_release
pesy.install
.DS_Store
*.install
*~
scripts/storage
vendor
coverage/
*.bs.js
site/index.html
site/index.rendered.html
================================================
FILE: .npmignore
================================================
.DS_Store
npm-debug.log
.merlin
yarn-error.log
node_modules/
_build
_esy
_release
pesy.install
.DS_Store
*.install
*~
scripts/storage
vendor
coverage/
*.bs.js
npm-cli/
esy.lock/
site/
images/
azure-pipelines.yml
.ci/
scripts/
================================================
FILE: CODE_OF_CONDUCT.md
================================================
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at prometheansacrifice@gmail.com. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq
================================================
FILE: LICENSE
================================================
MIT License
Copyright (c) 2019
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: ORIGIN.md
================================================
- Includes vendored copy of [JSON.sh](https://github.com/dominictarr/JSON.sh)
(under MIT LICENSE) which has been renamed to `pesy-JSON.sh` to avoid name
collisions.
================================================
FILE: PesyE2E.opam
================================================
================================================
FILE: README.html
================================================
[ vim: set filetype=Markdown: ]: # (<script src="site/Reload.js"> </script>)
# pesy
`pesy` is a command line tool to assist with your native Reason/OCaml
workflow using the package.json itself.
1. Quickly bootstrap a new Reason project
2. Configure your builds
## Installation
`npm install -g pesy@next`
## Why pesy?
`esy` is driven by a package.json to bring the familiar NPM workflow
to the native world of Reason/OCaml. `pesy` takes this further by
adopting NPM conventions to configure the build.`esy`'s package.json
first approach in creating developer sandboxes brings interesting
possibilities to the table. `pesy` leverages on those features to make
native development both convenient and familiar.
`pesy` provides
1. A bootstrapper script to quickly create a project template
2. An alternative JSON syntax around Dune that is NPM like
3. Built in tasks that take full advantages of esy's capabilities
## Getting started
### Creating a new project
`pesy` can quickly bootstrap a basic native Reason/OCaml project.
```sh
cd my-new-project/
pesy
```
### Adding project dependencies
Say, we need `@opam/yojson` and `@reason-native/console` in `App.re`.
Place `App.re` in a folder (say, `bin/`?) and add the following to you
package.json.
```json
"buildDirs": {
"bin": {
"imports": [
"Json = require('@opam/yojson')",
"Console = require('@reason-native/console/lib')"
]
}
}
```
<continueRight/>
Run `esy pesy` (once). Run `esy` after that to create builds.
`pesy` abstracts library identifiers (in this case `yojson` and
`console.lib`) in file paths conceptually. Use native dependencies
from OCaml like you did with NPM!
### Where pesy shines
However, `pesy`'s is truly useful for frameworks that need
a lot dependencies and configuration. Ex: Morph and Revery
```sh
pesy --template pesy/template-revery --directory my-new-project
```
If you are authoring a framework, create a template like
[`pesy/template-revery`](https://github.com/pesy/template-revery) and
run the project on the CI with a setup that `pesy` creates for you.
You users get cached CI artifacts that will let can hydrate their
local `esy` cache avoid long wait times! See [Templates](#pesy-templates-experimental-creating-your-own-template) to see how
to create such templates.
### Looking for a tutorial?
Checkout [A simple native Reason project with
pesy](#pesy-simple-native-example) to get an idea of what developing
with `pesy` feels like.
## Relation to Dune
Pesy as accepts package.json as input and producing dune files as output.
```
+--------+
package.json ---> | pesy | +---> dune files
+--------+
```
<continueRight/>
Note that not all of Dune's features are supported in pesy (PRs
welcome). `pesy` doesn't intend to duplicate Dune's efforts - it's
meant to compliment it. For simple use cases, `pesy` wants to provide
a NPM friendly interface to Dune so that newcomers can quickly get
started, without confusing themselves with the library vs packages
nuances.
## Namespacing
Every library, as we know, exposes a namespace/module under which it's APIs are
available. However, as package authors, it can hard to make sure one
is not using a namespace already taken by another package (Otherwise
it could lead to collisions). Pesy works around this by assigning the library
the upper camelcase of the root package name and directory the
library/sub-package resides in.
Example: if a package.json looks like this
```json
{
"name": "@myscope/foo",
"buildDirs": {
"library": { ... }
}
}
```
Then, subpackage `library` takes a namespace of
`MyScopeFooLibrary`. As a user, however, you shouldn't have to worry
much about yourself, since you can specify how you'd like to import
subpackages (and packages). In the above example, another subpackage
would consume it as follows
```json
{
"name": "@myscope/foo",
"buildDirs": {
"library": { ... },
"anotherLibrary": {
"imports": [
"ThatOtherLibrary = require('@myscope/foo/library')"
]
}
}
}
```
And if you were consuming this package (after having published to
npm), you can import it as follows:
```json
{
"name": "bar",
"buildDirs": {
"library": {
"imports": [
"ThatFooLibrary = require('@myscope/foo/library')"
]
}
}
```
<continueRight/>
With the new NPM like conventions, pesy automatically handles the
namespace for you so that we don't have to worry about the nuances of
a package and a library during development.
## Dune files
`pesy` generates dune files on fly behind the scenes. And to be able to so,
it needs a static dune file that looks like the following
```sexp
(* -*- tuareg -*- *)
open Jbuild_plugin.V1
let () =
run_and_read_lines ("pesy dune-file " ^ Sys.getcwd ())
|> String.concat "\n"
|> send
```
These have to created only once - after that they never change (unless you decide
to eject). Every bootstrapped project has them already and you dont have to create
them. In case, you need to re-generate them, run `esy pesy` (or `esy
@mysandbox pesy` if you are using `esy` sandboxes). A common need for running `esy pesy`
is when you add a new folder to your project.
#### Ejecting:
It is always possible to eject out of pesy config by running `esy pesy
eject ./subpackage-path`
## CI & local cache Warming up
Compilation artifacts built on the CI can be downloaded by `pesy warmup`. At
the moment, we only support Azure Pipelines can be configured as follows.
```json
{
"pesy": {
"azure-project":
"<azure-project>/<azure-pipeline-name>",
"github":
"<github-org>/<github-repo>"
}
}
```
<continueRight/>
This will fetch appropriate artifacts compiled for the current
machine. Note however, the best way to get this feature to work is to
use cache publish and restore mechanism provided bootstrapped
files. `pesy` assumes that the artifact zip file names and/or paths
haven't changed.
#### How it works
Compilation artifacts created by `esy` are relocatable - since `esy`
sandboxes isolated, all the dependencies are accounted for and each
dependency is loaded from a path with a fixed-length prefix. Such
artifacts can be built on one machine and used on other provided the
prefixes are rewritten to reflect the updated path on the new
machine. And `esy` provides all the low level command to do this out of
the box. `pesy` simply provides a convenient wrapper that drives them.
When the project is bootstrapped for the first time, it is identical to a copy
that is run on the server. This is where the first set of cached builds come from,
which is why the `azure-project` in the pesy config is set to `esy-dev/esy` and
`github` to `esy/pesy-reason-template`.
Once the project sees changes, you most probably would add more dependencies
(or remove some) which could change the build sandbox state. It is recommended that
you run the provided CI setup to cache builds on your own Azure Pipelines instance
and update `github` and `azure-project` accordingly.
## Publishing and Consuming from NPM
#### Publishing libraries
Easiest way to get started with distributing you library is to publish
the source to NPM.
Let's take a look at an example.
Consider a base package `foo` that you created and distributed on NPM. And let's assume, `bar` is the package that consumes `foo`.
```sh
$ pesy --directory foo-lib
```
This would have bootstrapped a project with the default template with
`Util.re` in the `library/` folder.
Let's publish it
```sh
$ npm publish
```
#### Consuming `foo-lib`
Let quickly create a new project, `bar` and add `foo`.
```sh
$ pesy --directory bar
$ esy add foo-lib
```
We can now require `foo`
```diff
"buildDirs": {
"library": {
"imports": [
+ "FooLib = require('foo-lib/library')"
]
}
}
```
And then edit Utils.re
```reason
let foo = () => {
FooLib.Util.foo();
print_endline("This is from bar");
};
```
```sh
$ esy
$ esy start
Hello from foo!
This is from bar
```
## Templates [experimental]
#### Usage
To use a custom template run `pesy --template=github:your-name/your-pesy-template`
#### Creating your own template
*This is a experimental feature that could see a lot of churn. We
request you to watch the issue tracker for updates.*
It works by downloading a git repo and then replacing special strings
in filenames and files inside the repo. The special strings are
currently these:
###### In file names:
| In filename | Replaced with |
| -------------------------------- | ---------------------------- |
| `__PACKAGE_NAME__` | `package_name` |
| `__PACKAGE_NAME_FULL__` | `package_name` |
| `__PACKAGE_NAME_UPPER_CAMEL__` | `PackageName` |
###### In file content:
| In contents | Replaced with |
| ---------------------------- | ---------------------------- |
| `<PACKAGE_NAME>` | `package_name` |
| `<PACKAGE_NAME_FULL>` | `package_name` |
| `<PACKAGE_NAME_UPPER_CAMEL>` | `PackageName` |
| `<VERSION>` | `version` |
| `<PUBLIC_LIB_NAME>` | `package_name/library` |
| `<TEST_LIB_NAME>` | `package_name/test` |
Best way to get started creating a new template is to download
https://github.com/esy/pesy-reason-template and work on it. Any
changes can be tested with `pesy test-template`.
## Supported Dune Config
This is reference guide explaining the config `pesy` supports.
### Binaries
Configuration that applies to subpackages that create binary
executables. Note that these executables can be ocaml bytecode or
native binaries (ELF/Mach/PE)
#### bin : `string`
A subpackage produces binary when it contains a `bin` property.
```json
{
"buildDirs": {
"src": {
"bin": "Main.re"
}
}
}
```
<continueRight/>
Here is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/simple-bin/package.json)
#### modes : `array(string)`
An array of [advanced linking
modes](https://dune.readthedocs.io/en/latest/dune-files.html?highlight=modules_without_implementation#linking-modes). Each
string should be of the form "`compilation-mode` `binary-kind`" where
`compilation-mode` is one byte, native or best and `binary-kind` is
one of c, exe, object, shared_object.
```json
{
"buildDirs": {
"src": {
"bin": "Foo.re",
"modes": [ "native", "exe"]
}
}
}
```
<continueRight/>
Here is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/bin-modes/package.json)
#### name (override) : `string`
A string that maps to Dune's `public_name`. Usually unnecessary (as
`bin` property takes care of it) and must only be used to override.
#### main (override) : `string`
A string that maps to Dune's `name`. Usually unnecessary (as `bin`
property takes care of it) and must only be used to override the entry
point.
### Libraries
#### modes : `array(string)`
`modes` can be used to configure the compilation target - [native](https://caml.inria.fr/pub/docs/manual-ocaml/native.html) or
[bytecode](https://caml.inria.fr/pub/docs/manual-ocaml/comp.html) An
array of string, any of `byte`, `native`, `best`
##### `"byte"`
This mode generates byte code output
##### `"native"`
This mode generates native output
##### `"best"`
Sometimes it may not be obvious if native compilation is supported on a
machine. In such circumstances, use `"best"` and `"native"` will be picked for
you if it's available. Else, it'll be `"byte"`
#### cNames : `array(string)`
When writing C stubs to FFI into a library, simply mention the file
name (without the `.c` extension) in the `cNames` field.
```json
{
"buildDirs": {
"src": {
"cNames": ["my-stub1", "my-stub-2"]
}
}
}
```
#### foreignStubs : `list(ForeignStub)`
From dune version 2.0 onwards the cNames field was removed and foreignStubs field
was introduced to provide the FFI functionality, foreignStubs is a list of objects,
where each foreignStub object should specify `language`, `names` & `flags`.
Incase `names` & `flags` is not specified or empty, their default value will be considered
[Refer this for default values for names & flags](https://dune.readthedocs.io/en/stable/concepts.html#foreign-stubs)
```json
{
"buildDirs": {
"src": {
"foreignStubs": [
{
"language": "c",
"names": ["my-stub1", "my-stub-2"],
"flags": ["-verbose"]
}
]
}
}
}
```
<continueRight/>
Here is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/cNames/package.json)
### Config supported by both libraries and binaries
#### imports : `array(string)`
`imports` can be used to import a library (from a subpackage or an
external npm/opam package) and utilise the namespace of the imported
library.
```json
{
"buildDirs": {
"src": {
"imports": [
"Console = require('console')"
]
}
}
}
```
The above config makes a namespace `Console` available inside the
subpackage `src`. Now any `.re` file inside `src` can use the
`console` library.
```reason
// src/SomeFile.re
let foo = () => Console.log("Hello, world")
```
We can also import a package/subpackage under a different namespace
```json
{
"buildDirs": {
"src": {
"imports": [
"NotConsole = require('console')"
]
}
}
}
```
And we can import (oddly confusing) `NotConsole` from in `src`
```reason
// src/SomeFile.re
let foo = () => NotConsole.log("Hello, world");
```
We can also import other subpackages in the project.
```json
{
"buildDirs": {
"src": {
"bin": "Main.re",
"imports": [
"FooConsole = require('console/lib')",
"MyOwnLibrary = require('../my-own-lib')"
]
},
"my-own-lib": {}
}
}
```
<continueRight/>
Here is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/imports/package.json)
#### Compiler flags
###### All of type *list(string)*
- `flags` - These flags will be passed to both the bytecode compiler and native compiler
- `ocamlcFlags` - These will be passed to `ocamlc` - the bytecode compiler
- `ocamloptFlags` - These will be passed to `ocamlopt` - the native compiler
- `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/)*
#### Preprocessor flags : `list(string)`
`preprocess` accepts options needed to pass the source files via a
preprocessor first. When using custom syntax not natively supported in
the compiler, we pass the sources in a subpackage via a preprocessor
first.
For example, suppose we'd like to use `let%lwt` syntax for our `Lwt` promises
```reason
let%lwt foo = Lwt.return("foo");
print_endline(foo);
// instead of
Lwt.return >>=
(foo => print_endline(foo); Lwt.return())
```
We specify `lwt_ppx` in `pps preprocess`
```json
{
"buildDirs": {
"src": {
"bin": "Main.re",
"preprocess": ["pps", "lwt_ppx"]
}
}
}
```
<continueRight/>
Here is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/preprocess/package.json)
#### includeSubdirs : `string = "no"|"unqualified"`
Default is "no", and changing to "unqualified" will compile modules at
deeper directories.
#### Escape hatches for unsupported Dune config
It's always possible that there are features Dune offers are needed and
the options above are not enough. Use `rawBuildConfig` to add
options in a given library or binary. Use `rawBuildConfigFooter` to
add config to the footer.
##### rawBuildConfig : `list(string)`
Example
```json
{
"src": {
"rawBuildConfig": [ "(libraries unix)" ],
"bin": "Main.re"
}
}
```
##### rawBuildConfigFooter : `list(string)`
Example
```json
{
"src": {
"rawBuildConfigFooter": [
"(install (section share_root) (files (plaintext.txt as asset.txt)))"
]
}
}
```
<continueRight/>
Here is a [complete, working example](https://github.com/prometheansacrifice/pesy-samples/blob/master/raw/package.json)
## Tutorials
#### Simple native example
#### CLI Apps
#### Web Development with Morph
## Development
Clone the repo and run `esy` on it.
#### e2e tests
`./_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.
To test if simple workflows work as expected. They assume both `esy` and `pesy` are installed
globally (as on user's machines).
## Changelog
**version 0.4.0 (12/21/2018)**
- Allow `buildDirs` to contain deeper directories such as `"path/to/my-lib": {...}"`.
- Added support for `wrapped` property on libraries.
- Added support for `virtualModules` and `implements` - properties for Dune
virtual libraries. (This will only be supported if you mark your project as
Dune 1.7 - not yet released).
- Stopped using `ignore_subdirs` in new projects, instead using
`(dirs (:standard \ _esy))` which only works in Dune `1.6.0+`, so made new
projects have a lower bound of Dune `1.6.0`.
- Support new properties `rawBuildConfig` which will be inserted at the bottom
of the _target_ being configured (library/executable).
- It expects an array of strings, each string being a separate line in the
generated config.
- Support new properties `rawBuildConfigFooter` which will be inserted at the
bottom of the entire Dune file for the _target_ being configured.
- It expects an array of strings, each string being a separate line in the
generated config.
- Support new properties `modes` for binaries and libraries `list(string)`.
================================================
FILE: azure-pipelines.yml
================================================
name: Build npm release
variables:
esy__ci_cache_version: v1
trigger:
branches:
include:
- master
- releases/*
paths:
exclude:
- README.html
- notes/*
- "*.md"
- LICENSE
jobs:
- template: .ci/build-platform.yml
parameters:
platform: Linux
vmImage: ubuntu-latest
- template: .ci/build-platform.yml
parameters:
platform: macOS
vmImage: macOS-latest
# Need windows-2019 to do esy import/export-dependencies
# which assumes you have bsdtar (tar.exe) in your system
# otherwise it will end up using the esy-bash tar which doesn't
# understand drives like D:/ (thinks it's an scp path).
- template: .ci/build-platform.yml
parameters:
platform: Windows
vmImage: windows-2019
# This job is kept here as we want to have the platform names in the same file
- job: Release
displayName: Release
dependsOn:
- Linux
- macOS
- Windows
pool:
vmImage: macOS-latest
demands: node.js
steps:
- template: .ci/cross-release.yml
================================================
FILE: bin/Pesy.re
================================================
module Lib = PesyEsyPesyLib.Lib;
module Utils = PesyEsyPesyUtils.Utils;
open Lib;
open Utils;
open Printf;
open Cmdliner;
open PesyEsyPesyErrors.Errors;
exception BootstrappingError(unit);
exception LsLibsError(string);
module EnvVars = {
let rootPackageConfigPath = Sys.getenv_opt("ESY__ROOT_PACKAGE_CONFIG_PATH");
};
let getManifestFile = projectRoot => {
switch (EnvVars.rootPackageConfigPath) {
| Some(x) => x
| None => Path.(projectRoot / "package.json")
};
};
let reconcile = projectRoot => {
/* use readFileOpt to read previously computed directory path */
let operations = projectRoot |> getManifestFile |> Lib.gen(projectRoot);
switch (operations) {
| [] => ()
| _ as operations => Lib.log(operations)
};
print_endline(
Pastel.(
<Pastel> "Ready for " <Pastel color=Green> "esy" </Pastel> </Pastel>
),
);
};
let main = () => {
Findlib.init();
ignore(
switch (Sys.getenv_opt("cur__root")) {
| Some(curRoot) =>
/**
* This means the user ran pesy in an esy environment.
* as esy pesy
* TODO: when run as esy b pesy, it must exit early with an error
*/
reconcile(curRoot)
| None =>
/**
* This mean pesy is being run naked on the shell.
* TODO: use readFileOpt to read previously computed directory path
*/
print_endline("Prebuilt Pesy must not be run globally.")
},
);
};
let main = () =>
try(main()) {
| BootstrappingError () =>
let message =
Pastel.(
<Pastel>
<Pastel> "You have " </Pastel>
<Pastel color=Red> "not installed" </Pastel>
<Pastel> " esy\n" </Pastel>
<Pastel>
"pesy works together with esy to simplify your workflow. Please install esy.\n"
</Pastel>
<Pastel> "You could try\n\n" </Pastel>
<Pastel bold=true> " npm install -g esy\n" </Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| InvalidBinProperty(pkgName) =>
let mStr =
sprintf("Invalid value in subpackage %s's bin property\n", pkgName);
let message =
Pastel.(
<Pastel>
<Pastel color=Red> mStr </Pastel>
<Pastel>
"'bin' property is usually of the form { \"target.exe\": \"sourceFilename.re\" } "
</Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| ShouldNotBeNull(e) =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Found null value for " </Pastel>
<Pastel bold=true> e </Pastel>
"\nExpected a non null value."
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| FatalError(e) =>
let message =
Pastel.(
<Pastel> <Pastel color=Red> "Fatal Error " </Pastel> e </Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| InvalidRootName(e) =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Invalid root name!\n" </Pastel>
<Pastel> "Expected package name of the form " </Pastel>
<Pastel bold=true> "@myscope/foo-bar" </Pastel>
<Pastel> " or " </Pastel>
<Pastel bold=true> "foo-bar\n" </Pastel>
<Pastel> "Instead found " </Pastel>
<Pastel bold=true> e </Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| GenericException(e) =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Error: " </Pastel>
<Pastel> e </Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| ImportsParserFailure(_e) =>
/* TODO: Be more specific about which imports */
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Could not understand the imports\n" </Pastel>
<Pastel>
"There seems to be a syntax error in one of the imports"
</Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| ResolveRelativePathFailure(e) =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Could not find the library\n" </Pastel>
<Pastel> e </Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| LocalLibraryPathNotFound(e) =>
let message =
<Pastel>
<Pastel>
<Pastel color=Red> "Error: " </Pastel>
"Could not find the library "
</Pastel>
<Pastel bold=true> e </Pastel>
<Pastel>
"\ncheck the require() in import of your package.json"
</Pastel>
</Pastel>;
fprintf(stderr, "%s\n", message);
exit(-1);
| x =>
/* let message = Pastel.(<Pastel color=Red> "Failed" </Pastel>); */
/* fprintf(stderr, "%s", message); */
/* exit(-1); */
raise(x)
};
let pesy_dune_file = dir => {
switch (Sys.getenv_opt("cur__root")) {
| None =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red>
"'pesy dune-file' must be run the build environment only\n"
</Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| Some(curRoot) =>
try(duneFile(curRoot, getManifestFile(curRoot), dir)) {
| LocalLibraryPathNotFound(e) =>
let message =
<Pastel>
<Pastel>
<Pastel color=Red> "Error: " </Pastel>
"Could not find the library "
</Pastel>
<Pastel bold=true> e </Pastel>
<Pastel>
"\ncheck the require() in import of your package.json"
</Pastel>
</Pastel>;
fprintf(stderr, "%s\n", message);
exit(-1);
}
};
};
let pesy_build = () =>
ignore(
switch (Sys.getenv_opt("cur__root")) {
| Some(curRoot) =>
let manifestFile = curRoot |> getManifestFile;
try(validateManifestFile(curRoot, manifestFile)) {
| LocalLibraryPathNotFound(e) =>
let message =
<Pastel>
<Pastel>
<Pastel color=Red> "Error: " </Pastel>
"Could not find the library "
</Pastel>
<Pastel bold=true> e </Pastel>
<Pastel>
"\ncheck the require() in import of your package.json"
</Pastel>
</Pastel>;
fprintf(stderr, "%s\n", message);
exit(-1);
| ForeignStubsIncorrectlyUsed =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Error: " </Pastel>
<Pastel>
<Pastel bold=true> "foreignStubs " </Pastel>
"is introduced since dune version"
<Pastel bold=true> " 2.0\n" </Pastel>
</Pastel>
<Pastel>
"Use "
<Pastel bold=true> "cNames" </Pastel>
" to specify stubs"
</Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| CNamesIncorrectlyUsed =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Error: " </Pastel>
<Pastel>
<Pastel bold=true> "cNames " </Pastel>
"is deprecated in dune version 2.x\n"
</Pastel>
<Pastel>
"Use "
<Pastel bold=true> "foreignStubs" </Pastel>
" to specify stubs"
</Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| InvalidDuneVersion(version) =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red> "Error: " </Pastel>
<Pastel>
"Invalid Dune version" <Pastel bold=true> {version} </Pastel>
</Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
};
let buildTarget = build(manifestFile);
Unix.(
switch (
create_process(
"dune",
[|"dune", "build", "-p", buildTarget|],
Unix.stdin,
Unix.stdout,
Unix.stderr,
)
|> waitpid([])
) {
| (_, WEXITED(c)) => c
| (_, WSIGNALED(c)) => c
| (_, WSTOPPED(c)) => c
}
);
| None =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red>
"'pesy build' must be run the build environment only\n"
</Pastel>
<Pastel> "Try esy b pesy build" </Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
},
);
let pesyLsLibs = () => {
let scopedIfDoubleKebabified = str =>
switch (Str.split(Str.regexp("\\-\\-"), str)) {
| [pkgName] => pkgName
| [scope, pkgName] => "@" ++ scope ++ "/" ++ pkgName
| _ =>
raise(
LsLibsError("findlib name of the library was had more than one --"),
)
};
Findlib.init();
let pkgs = Findlib.list_packages'();
List.iteri(
(i, x) => {
x
|> Str.global_replace(Str.regexp("\\."), "/")
|> scopedIfDoubleKebabified
|> sprintf(
"\x1b[2m%s──\x1b[0m require('\x1b[1m%s\x1b[0m')",
i < List.length(pkgs) - 1 ? "├" : "└",
)
|> print_endline
},
pkgs,
);
};
let version = "0.1.0-alpha.14";
let build = () => {
let cmd = "build";
let envs: list(Cmd.Env.info) = [];
let exits: list(Cmd.Exit.info) = [];
let doc = "build - builds your pesy managed project";
let build_t = Term.(const(pesy_build) $ const());
let info = Cmd.info(cmd, ~envs, ~exits, ~doc, ~version);
Cmd.v(info, build_t);
};
let subpkgTerm =
Cmdliner.Arg.(
required
& pos(0, some(string), None)
& info(
[],
~doc="Subpackage which needs the Dune file generated",
~docv="SUBPACKAGE",
)
);
let pesy_dune_file = () => {
let cmd = "dune-file";
let envs: list(Cmd.Env.info) = [];
let exits: list(Cmd.Exit.info) = [];
let doc = "dune-file - prints dune file for a given dir/subpackage";
let build_t = Term.(const(pesy_dune_file) $ subpkgTerm);
let info = Cmd.info(cmd, ~envs, ~exits, ~doc, ~version);
Cmd.v(info, build_t);
};
let pesy_eject = dir => {
switch (Sys.getenv_opt("cur__root")) {
| None =>
let message =
Pastel.(
<Pastel>
<Pastel color=Red>
"'pesy eject' must be run the build environment only\n"
</Pastel>
</Pastel>
);
fprintf(stderr, "%s\n", message);
exit(-1);
| Some(curRoot) => duneEject(curRoot, getManifestFile(curRoot), dir)
};
};
let eject = () => {
let subpkgTerm =
Arg.(
required
& pos(0, some(string), None)
& info(
[],
~doc="Subpackage which needs to get ejected",
~docv="SUBPACKAGE",
)
);
let cmd = "eject";
let envs: list(Cmd.Env.info) = [];
let exits: list(Cmd.Exit.info) = [];
let doc = "eject - eject dune file for a given dir/subpackage";
let build_t = Term.(const(pesy_eject) $ subpkgTerm);
let info = Cmd.info(cmd, ~envs, ~exits, ~doc, ~version);
Cmd.v(info, build_t);
};
let lsLibs = () => {
let cmd = "ls-libs";
let envs: list(Cmd.Env.info) = [];
let exits: list(Cmd.Exit.info) = [];
let doc = "ls-libs - lists installed packages";
let build_t = Term.(const(pesyLsLibs) $ const());
let info = Cmd.info(cmd, ~envs, ~exits, ~doc, ~version);
Cmd.v(info, build_t);
};
let cmd = "esy-pesy";
let envs: list(Cmd.Env.info) = [];
let exits: list(Cmd.Exit.info) = [];
let doc = "Esy Pesy - Your Esy Assistant.";
let cmd_t = Term.(const(main) $ const());
let cmd_info = Cmd.info(cmd, ~envs, ~exits, ~doc, ~version);
exit @@ Cmd.eval @@ Cmd.group(~default=cmd_t, cmd_info, [build(), pesy_dune_file(), lsLibs(), eject()]);
================================================
FILE: bin/dune
================================================
(executable
(package pesy--esy-pesy)
(name Pesy)
(modules (:standard))
(public_name pesy)
(libraries pesy--esy-pesy.errors pesy--esy-pesy.lib pesy--esy-pesy.utils
cmdliner str findlib))
================================================
FILE: dune
================================================
(dirs :standard \ node_modules tmp npm-cli)
(vendored_dirs vendor)
================================================
FILE: dune-project
================================================
(lang dune 1.11)
(name pesy)
================================================
FILE: e2e-tests/.gitignore
================================================
esy.lock
node_modules
_esy
library/dune
executable/dune
test/dune
================================================
FILE: e2e-tests/Rimraf.re
================================================
type result =
| Ok
| Failure;
let is_directory = p => {
let stats = Unix.lstat(p);
switch (stats.st_kind) {
| S_DIR => true
| S_LNK
| S_SOCK
| S_REG
| S_CHR
| S_BLK
| S_FIFO => false
};
};
let rec del_file = p =>
try(
{
Unix.unlink(p);
Ok;
}
) {
| Unix.Unix_error(Unix.ENOENT, _, _) => Ok
| Unix.Unix_error(Unix.EISDIR | Unix.EPERM, _, _) => Ok
| Unix.Unix_error(Unix.EACCES, _, _) when Sys.win32 => Ok
| Unix.Unix_error(Unix.EINTR, _, _) => del_file(p)
| Unix.Unix_error(_e, _, _) => Failure
};
let rec del_dir = p => {
let rec del_members = (dHandle, p) => {
switch (
try(Some(Unix.readdir(dHandle))) {
| End_of_file => None
}
) {
| None => Ok
| Some(".." | ".") => del_members(dHandle, p)
| Some(f) =>
switch (run(Filename.concat(p, f))) {
| _ => del_members(dHandle, p)
}
};
};
let dh = Unix.opendir(p);
switch (del_members(dh, p)) {
| Ok =>
Unix.closedir(dh);
if (is_directory(p)) {
Unix.rmdir(p);
} else {
Unix.unlink(p);
};
Ok;
| Failure => Failure
};
}
and run: string => result =
p =>
try(is_directory(p) ? del_dir(p) : del_file(p)) {
| Unix.Unix_error(e, _, _) =>
switch (e) {
| Unix.ENOENT => Ok
| _ => Failure
}
| _ => Failure
};
================================================
FILE: e2e-tests/Runner.re
================================================
open Yojson.Basic;
open Unix;
open Utils;
open Bos;
open Printf;
/** TODO: Cleanup **/
let () = {
let run = (~env=?, c, args) => {
let env_vars =
switch (env) {
| Some(v) => v
| None => Unix.environment()
};
let pid =
create_process_env(
c,
Array.append([|c|], args),
env_vars,
stdin,
stdout,
stderr,
);
switch (Unix.waitpid([], pid)) {
| (_, WEXITED(c)) => c
| (_, WSIGNALED(c)) => c
| (_, WSTOPPED(c)) => c
};
};
let run = (cmd, args) => {
let exitCode = run(cmd, args);
if (exitCode != 0) {
printf(
"%s failed. Exit code relayed to the shell\n Exiting with (%d)...\n",
cmd,
exitCode,
);
exit(exitCode);
};
};
let cwd = Sys.getcwd();
printf("Running bootstrapper test");
print_newline();
chdir(Path.(cwd / "npm-cli"));
printf("Installing pesy globally...");
print_newline();
run(makeCommand("yarn"), [||]);
run(makeCommand("yarn"), [|"run", "package"|]);
run(makeCommand("npm"), [|"pack"|]);
run(
makeCommand("npm"),
[|"i", "-g", Path.(cwd / "npm-cli" / "pesy-0.5.0-dev.23.tgz")|],
);
chdir(cwd);
};
open Rresult.R.Infix;
module Log = {
open Printf;
let log = OS.File.(writef(dash));
let warn = printf;
let error = printf;
let withLog = (name, f) => {
log("Running '%s'", name)
>>= (
() => {
print_newline();
f();
}
)
>>= (() => log("Finished '%s'", name))
>>= (
() => {
print_newline();
Ok();
}
);
};
};
module L = Log;
let withCurrentWorkingDirectory =
(path, f: string => result(unit, [> Rresult.R.msg] as 'a)) => {
let fpath = Fpath.v(path);
OS.Dir.current()
>>= (
cwd => {
L.log("Entering %s\n", path)
>>= (
() =>
L.withLog(
Printf.sprintf("rimraf(%s)", path),
() => {
rimraf(path);
Ok();
},
)
)
>>= (() => OS.Dir.create(~path=true, fpath))
>>= (_ => OS.Dir.set_current(fpath))
>>= (() => f(path))
>>= (() => L.log("Leaving %s\n", path))
>>= (_ => OS.Dir.set_current(cwd));
}
);
};
module Path = {
let (/) = Filename.concat;
};
let createTestWorkspace = workspaceFolderName => {
let tmpDir = Filename.get_temp_dir_name();
Path.(tmpDir / workspaceFolderName);
};
let pesyBinPath = makeCommand("pesy");
let esyBinPath = makeCommand("esy");
let failIfNotZeroExit = taskName =>
fun
| `Exited(code) =>
if (code == 0) {
Ok();
} else {
Error(`Msg(sprintf("'%s' failed: non exit (%d)", taskName, code)));
}
| `Signaled(code) =>
Error(`Msg(sprintf("'%s' failed: signaled (%d)", taskName, code)));
module Config: {
type a;
let ofString: string => result(a, [ | `Msg(string)]);
let ofFile: Fpath.t => result(a, [ | `Msg(string)]);
/** Basically a toString but results a string in a result **/
let serialize:
result(a, [ | `Msg(string)]) => result(string, [ | `Msg(string)]);
let createError: string => result('a, [ | `Msg(string)]);
} = {
type a =
| FilePath(Fpath.t)
| Contents(string);
let ofString = x => Ok(Contents(x));
let ofFile = x => Ok(FilePath(x));
let createError = msg => Error(`Msg(msg));
let serialize:
result(a, [ | `Msg(string)]) => result(string, [ | `Msg(string)]) =
fun
| Ok(t) =>
switch (t) {
| FilePath(p) => OS.File.read(p)
| Contents(s) => Ok(s)
}
| Error(`Msg(msg)) => createError(msg);
};
let mergeJsons = (j1, j2) => {
switch (j1, j2) {
| (`Assoc(kvs1), `Assoc(kvs2)) => Ok(`Assoc(kvs1 @ kvs2))
| _ =>
Error(
`Msg(
sprintf("Merging %s and %s failed", to_string(j1), to_string(j2)),
),
)
};
};
let addToManifest = (jsonString, manifest) => {
OS.File.read(Fpath.v(manifest))
>>= (
manifestJsonString => {
let manifestJson =
try(Ok(from_string(manifestJsonString))) {
| _ =>
Error(
`Msg(
sprintf("Input manifest was not a valid json: %s", jsonString),
),
)
};
let editJson =
try(Ok(from_string(jsonString))) {
| _ =>
Error(
`Msg(
sprintf(
"Input pesy config was not a valid json: %s",
jsonString,
),
),
)
};
editJson
>>= (edit => manifestJson >>= (manifest => mergeJsons(manifest, edit)))
>>= (
mergedJson =>
mergedJson |> to_string |> OS.File.write(Fpath.v(manifest))
);
}
);
};
module PesyConfig = {
let edit = (config, manifest) => {
Config.serialize(config)
>>= (
jsonString => {
OS.File.read(Fpath.v(manifest))
>>= (
manifestJsonString =>
try(
switch (from_string(manifestJsonString)) {
| `Assoc(jsonKVPairs) =>
let pesyConfigJson =
try(Ok(from_string(jsonString))) {
| _ =>
Error(
`Msg(
sprintf(
"Input pesy config was not a valid json: %s",
jsonString,
),
),
)
};
List.map(
((k, v)) =>
if (k == "buildDirs") {
pesyConfigJson >>= (json => Ok((k, json)));
} else {
Ok((k, v));
},
jsonKVPairs,
)
|> List.fold_left(
(acc, v) =>
switch (acc, v) {
| (Ok(l), Ok(e)) => Ok([e, ...l])
| (_, Error(_) as e) => e
| (Error(_) as e, _) => e
},
Ok([]),
)
>>= (
kvs =>
`Assoc(kvs)
|> to_string
|> OS.File.write(Fpath.v(manifest))
);
| _ => Error(`Msg("Manifest file was invalid json"))
}
) {
| _ => Error(`Msg("Manifest file could not be parsed"))
}
);
}
);
};
let add = (jsonString, manifest) => {
OS.File.read(Fpath.v(manifest))
>>= (
manifestJsonString =>
try(
switch (from_string(manifestJsonString)) {
| `Assoc(jsonKVPairs) =>
let pesyConfigJson =
try(Ok(from_string(jsonString))) {
| _ =>
Error(
`Msg(
sprintf(
"Input pesy config was not a valid json: %s",
jsonString,
),
),
)
};
List.map(
((k, v)) =>
if (k == "buildDirs") {
pesyConfigJson
>>= (json => mergeJsons(v, json))
>>= (mergedConfig => Ok((k, mergedConfig)));
} else {
Ok((k, v));
},
jsonKVPairs,
)
|> List.fold_left(
(acc, v) =>
switch (acc, v) {
| (Ok(l), Ok(e)) => Ok([e, ...l])
| (_, Error(_) as e) => e
| (Error(_) as e, _) => e
},
Ok([]),
)
>>= (
kvs =>
`Assoc(kvs) |> to_string |> OS.File.write(Fpath.v(manifest))
);
| _ => Error(`Msg("Manifest file was invalid json"))
}
) {
| _ => Error(`Msg("Manifest file could not be parsed"))
}
);
};
};
let checkProject = (msg, ()) =>
L.withLog(Printf.sprintf("esy start (%s)", msg), () =>
OS.Cmd.run_status(Cmd.(v(esyBinPath) % "start"))
>>= failIfNotZeroExit("esy start: " ++ msg)
);
let checkPesyConfig = (msg, editPesyConfig, ()) =>
L.withLog("Editing pesy config: " ++ msg, () => editPesyConfig())
>>= (
() =>
L.withLog("esy pesy: " ++ msg, () =>
OS.Cmd.run_status(Cmd.(v(esyBinPath) % "pesy"))
>>= failIfNotZeroExit("esy pesy")
)
)
>>= (
() =>
L.withLog(Printf.sprintf("esy build"), () =>
OS.Cmd.run_status(Cmd.(v(esyBinPath) % "build"))
>>= failIfNotZeroExit("esy build")
)
)
>>= checkProject(msg);
let checkBootstrapper = cwd => {
OS.Cmd.(run_status(Cmd.(v(pesyBinPath) % "--fetch-cache=false")))
>>= failIfNotZeroExit("pesy --force-cache-fetch")
>>= checkProject("checking if bootstrapper works")
>>= (
() =>
addToManifest(
Str.global_replace(
Str.regexp("<RESOLUTION_LINK>"),
"link:"
++ (
(
switch (Sys.getenv_opt("PESY_CLONE_PATH")) {
| Some(path) => path
| None => Sys.getcwd()
}
)
|> Str.(global_replace(regexp("\\"), "/"))
),
{|
{
"resolutions": {
"@pesy/esy-pesy": "<RESOLUTION_LINK>"
}
}
|},
),
Path.(cwd / "package.json"),
)
>>= (
() =>
L.withLog(Printf.sprintf("esy"), () =>
OS.Cmd.run_status(
Cmd.(v(esyBinPath) % "--skip-repository-update"),
)
>>= failIfNotZeroExit("esy")
)
)
)
>>= checkPesyConfig("try old (4.x) pesy syntax", () =>
PesyConfig.edit(
Config.ofString(
{|
{
"library": {
"require": [
"console.lib",
"pastel.lib"
]
},
"bin": {
"imports": [
"Library = require('test-project/library')"
],
"bin": {
"TestProjectApp": "TestProjectApp.re"
}
}
}
|},
),
Path.(cwd / "package.json"),
)
)
>>= (
() =>
L.withLog("Editing source: Add file stubs.c", () =>
OS.File.write(
Fpath.(v(cwd) / "library" / "stubs.c"),
{|
#include <caml/mlvalues.h>
#include <stdio.h>
CAMLprim value
caml_foo(value a) {
int c_a = Int_val(a);
printf("foo received: %d", c_a);
return Val_unit;
}
|},
)
)
>>= (() => OS.File.read(Fpath.(v(cwd) / "library" / "Util.re")))
>>= (
utilRe =>
OS.File.write(
Fpath.(v(cwd) / "library" / "Util.re"),
utilRe ++ "\n" ++ "external foo: int => unit = \"caml_foo\";",
)
)
)
>>= checkPesyConfig("add foreignStubs for stubs", () =>
PesyConfig.edit(
Config.ofString(
{|
{
"library": {
"require": [
"console.lib",
"pastel.lib"
],
"foreignStubs": [{
"language": "c",
"names": ["stubs"]
}]
},
"bin": {
"imports": [
"Library = require('test-project/library')"
],
"bin": {
"TestProjectApp": "TestProjectApp.re"
}
}
}
|},
),
Path.(cwd / "package.json"),
)
)
>>= checkPesyConfig("add byte mode compilation", () =>
PesyConfig.edit(
Config.ofString(
{|
{
"library": {
"public": true,
"require": [
"console.lib",
"pastel.lib"
],
"modes": [
"byte",
"native"
],
"foreignStubs": [{
"language": "c",
"names": ["stubs"]
}]
},
"bin": {
"modes": [
"byte",
"exe"
],
"imports": [
"Library = require('test-project/library')"
],
"bin": {
"TestProjectApp": "TestProjectApp.re"
}
}
}
|},
),
Path.(cwd / "package.json"),
)
)
>>= (
_ =>
L.withLog(
"Editing source: Add file library-with-relative-imports/Index.re", () =>
OS.Dir.create(
~path=true,
Fpath.(v(cwd) / "library-with-relative-imports"),
)
>>= (
_ =>
OS.File.write(
Fpath.(v(cwd) / "library-with-relative-imports" / "Index.re"),
{|
open Foo
let foo = () => {
print_endline("relatively imported" ++ Foo.Util.hello());
};
|},
)
)
)
>>= (
_ =>
L.withLog(
"Editing source: Add file executable-with-relative-imports/Main.re",
() =>
OS.Dir.create(
~path=true,
Fpath.(v(cwd) / "executable-with-relative-imports"),
)
>>= (
_ =>
OS.File.write(
Fpath.(
v(cwd) / "executable-with-relative-imports" / "Main.re"
),
{|
open Foo
Foo.foo();
|},
)
)
)
)
)
>>= checkPesyConfig("add relatively imported modules", () =>
PesyConfig.add(
{|
{
"library-with-relative-imports": {
"imports": [
"Foo = require('../library')"
]
},
"executable-with-relative-imports": {
"imports": [
"Foo = require('../library-with-relative-imports')"
],
"bin": { "TestRelativelyImportedLib": "Main.re" }
}
}
|},
Path.(cwd / "package.json"),
)
)
>>= (
() =>
L.withLog(Printf.sprintf("esy add @opam/lwt @opam/lwt_ppx"), () =>
OS.Cmd.run_status(
Cmd.(v(esyBinPath) % "add" % "@opam/lwt" % "@opam/lwt_ppx"),
)
>>= failIfNotZeroExit("esy add @opam/lwt @opam/lwt_ppx")
)
)
>>= (
() =>
L.withLog(Printf.sprintf("esy build"), () =>
OS.Cmd.run_status(Cmd.(v(esyBinPath) % "build"))
>>= failIfNotZeroExit("esy build")
)
)
>>= (
_ =>
L.withLog(
"Editing source: Add file executable-with-lwt-preprocess/Main.re", () =>
OS.Dir.create(
~path=true,
Fpath.(v(cwd) / "executable-with-lwt-preprocess"),
)
>>= (
_ =>
OS.File.write(
Fpath.(v(cwd) / "executable-with-lwt-preprocess" / "Main.re"),
{|
open Lwt;
Console.log("Testing..");
let foo = {
let%lwt foo = Lwt.return("hello world from lwt");
Lwt.return(foo ++ " blah");
};
print_endline(Lwt_main.run(foo));
|},
)
)
)
)
>>= checkPesyConfig("add preprocessor", () =>
PesyConfig.add(
{|
{
"executable-with-lwt-preprocess": {
"imports": [
"Console = require('@reason-native/console/lib')",
"Lwt = require('lwt')",
"LwtUnix = require('lwt/unix')"
],
"preprocess": [
"pps",
"lwt_ppx"
],
"bin": { "TestLwtPreprocessor": "Main.re" }
}
}
|},
Path.(cwd / "package.json"),
)
)
>>= (
_ =>
L.withLog(
"Editing source: Add file executable-with-raw-config/Main.re", () =>
OS.Dir.create(
~path=true,
Fpath.(v(cwd) / "executable-with-raw-config"),
)
>>= (
_ =>
OS.File.write(
Fpath.(v(cwd) / "executable-with-raw-config" / "Main.re"),
{|
print_endline(Unix.getenv("PATH"));
|},
)
)
)
)
>>= checkPesyConfig("add raw build config", () =>
PesyConfig.add(
{|
{
"executable-with-raw-config": {
"rawBuildConfig": [ "(libraries unix)" ],
"bin": { "TestRawBuildConfig": "Main.re" }
}
}
|},
Path.(cwd / "package.json"),
)
)
>>= (
_ =>
L.withLog(
"Editing source: Add file raw-footer-config-assets/plaintext.txt", () =>
OS.Dir.create(
~path=true,
Fpath.(v(cwd) / "raw-footer-config-assets"),
)
>>= (
_ =>
OS.File.write(
Fpath.(v(cwd) / "raw-footer-config-assets" / "plaintext.txt"),
{|
Some random text here.
|},
)
)
)
)
>>= checkPesyConfig("add raw build config footer", () =>
PesyConfig.add(
{|
{
"raw-footer-config-assets" : {
"namespace": "RawAssets",
"name": "test-project.assets",
"rawBuildConfigFooter": [ "(install (section share_root) (files (plaintext.txt as asset.txt)))" ]
}
}
|},
Path.(cwd / "package.json"),
)
);
};
switch (
withCurrentWorkingDirectory(
createTestWorkspace("test-project"),
checkBootstrapper,
)
) {
| Ok () => ()
| Error(`Msg(msg)) =>
print_endline("Runner failed with: " ++ msg);
exit(-1);
};
================================================
FILE: e2e-tests/Utils.re
================================================
open Printf;
module Path = {
let (/) = Filename.concat;
};
let makeCommand = cmd =>
Sys.unix
? cmd
: {
let pathVars =
Array.to_list(Unix.environment())
|> List.map(e =>
switch (Str.split(Str.regexp("="), e)) {
| [k, v, ..._rest] => Some((k, v))
| _ => None
}
)
|> List.filter(
fun
| None => false
| _ => true,
)
|> List.filter(e =>
switch (e) {
| Some((k, _)) => String.lowercase_ascii(k) == "path"
| _ => false
}
)
|> List.map(
fun
| Some(x) => x
| None => ("", "") /* Why not filter_map? */
);
let v =
List.fold_right(
(e, acc) => {
let (_, v) = e;
acc ++ (Sys.unix ? ":" : ";") ++ v;
},
pathVars,
"",
);
let paths = Str.split(Str.regexp(Sys.unix ? ":" : ";"), v);
let npmPaths =
List.filter(
path =>
Sys.file_exists(Filename.concat(path, sprintf("%s.cmd", cmd))),
paths,
);
switch (npmPaths) {
| [] =>
fprintf(stderr, "No %s bin path found", cmd);
exit(-1);
| [h, ..._] => Filename.concat(h, sprintf("%s.cmd", cmd))
};
};
let rimraf = p => ignore(Rimraf.run(p));
let esyCommand = makeCommand("esy");
let buffer_size = 8192;
let buffer = Bytes.create(buffer_size);
let runCommandWithEnv = (command, args) => {
let attach =
Unix.create_process_env(
command,
Array.append([|command|], args),
Unix.environment(),
);
let pid = attach(Unix.stdin, Unix.stdout, Unix.stderr);
switch (Unix.waitpid([], pid)) {
| (_, WEXITED(c)) => c
| (_, WSIGNALED(c)) => c
| (_, WSTOPPED(c)) => c
};
};
let mkdir = (~perms=?, p) =>
switch (perms) {
| Some(x) => Unix.mkdir(p, x)
| None => Unix.mkdir(p, 0o755)
};
let file_copy = (input_name, output_name) => {
open Unix;
let fd_in = openfile(input_name, [O_RDONLY], 0);
let fd_out = openfile(output_name, [O_WRONLY, O_CREAT, O_TRUNC], 438);
let rec copy_loop = () =>
switch (read(fd_in, buffer, 0, buffer_size)) {
| 0 => ()
| r =>
ignore(write(fd_out, buffer, 0, r));
copy_loop();
};
copy_loop();
close(fd_in);
close(fd_out);
};
module IO = {
let write = (file, str) => {
let oc = open_out(file);
fprintf(oc, "%s", str);
close_out(oc);
};
let read = file => {
let buf = ref("");
let breakOut = ref(false);
let ic = open_in(file);
while (! breakOut^) {
let line =
try(input_line(ic)) {
| End_of_file =>
breakOut := true;
"";
};
buf := buf^ ++ "\n" ++ line;
};
buf^;
};
};
let contains = (s1, s2) => {
let re = Str.regexp_string(s2);
try(Str.search_forward(re, s1, 0)) {
| Not_found => (-1)
};
};
let rec fetch_files = (f, p) => {
let rec walk = (dh, acc) => {
let read_dir = dh =>
try(Some(Unix.readdir(dh))) {
| End_of_file => None
};
switch (read_dir(dh)) {
| Some("..")
| Some(".") => walk(dh, acc)
| Some(entry) =>
walk(dh, [Path.(p / entry), ...acc])
@ fetch_files(f, Path.(p / entry))
| None =>
Unix.closedir(dh);
acc;
};
};
if (try(Sys.is_directory(p)) {
| _ => false
}) {
let dh = Unix.opendir(p);
List.filter(f, walk(dh, []));
} else {
[];
};
};
================================================
FILE: e2e-tests/dune
================================================
(executable
(name Runner)
(package PesyE2E)
(public_name Runner.exe)
(libraries unix bos yojson str))
================================================
FILE: e2e-tests/pending-tests.re
================================================
/* OS.Dir.create(~path=true, Fpath.(v(cwd) / "virtual-foo")) */ /* >>= ( */ /* L.withLog("Editing source: Add file virtual-foo/VirtualFoo.rei", () => */ /* () => */ /* >>= ( */
/* _ => */
/* OS.File.write( */
/* Fpath.(v(cwd) / "virtual-foo" / "VirtualFoo.rei"), */
/* {| */
/* let thisWillBeImplementedLater: unit => unit; */
/* |}, */
/* ) */
/* ) */
/* ) */
/* >>= ( */
/* () => */
/* L.withLog( */
/* "Editing source: Add file implementation-bar/VirtualFoo.re", () => */
/* OS.Dir.create(~path=true, Fpath.(v(cwd) / "implementation-bar")) */
/* >>= ( */
/* _ => */
/* OS.File.write( */
/* Fpath.(v(cwd) / "implementation-bar" / "VirtualFoo.re"), */
/* {| */
/* let thisWillBeImplementedLater = () => { */
/* print_endline("From implementation bar..."); */
/* }; */
/* |}, */
/* ) */
/* ) */
/* ) */
/* >>= ( */
/* _ => */
/* L.withLog( */
/* "Editing source: Add file implementation-baz/VirtualFoo.re", () => */
/* OS.Dir.create( */
/* ~path=true, */
/* Fpath.(v(cwd) / "implementation-baz"), */
/* ) */
/* >>= ( */
/* _ => */
/* OS.File.write( */
/* Fpath.(v(cwd) / "implementation-baz" / "VirtualFoo.re"), */
/* {| */
/* let thisWillBeImplementedLater = () => { */
/* print_endline("From implementation baz..."); */
/* }; */
/* |}, */
/* ) */
/* ) */
/* ) */
/* >>= ( */
/* _ => */
/* L.withLog( */
/* "Editing source: Add file executable-virutal-bar/PesyVirtualApp.re", */
/* () => */
/* OS.Dir.create( */
/* ~path=true, */
/* Fpath.(v(cwd) / "executable-virtual-bar"), */
/* ) */
/* >>= ( */
/* _ => */
/* OS.File.write( */
/* Fpath.( */
/* v(cwd) */
/* / "executable-virtual-bar" */
/* / "PesyVirtualApp.re" */
/* ), */
/* {| */
/* Bar.thisWillBeImplementedLater(); */
/* |}, */
/* ) */
/* ) */
/* ) */
/* ) */
/* >>= ( */
/* _ => */
/* L.withLog( */
/* "Editing source: Add file executable-virtual-baz/PesyVirtualApp.re", */
/* () => */
/* OS.Dir.create( */
/* ~path=true, */
/* Fpath.(v(cwd) / "executable-virtual-baz"), */
/* ) */
/* >>= ( */
/* _ => */
/* OS.File.write( */
/* Fpath.( */
/* v(cwd) */
/* / "executable-virtual-baz" */
/* / "PesyVirtualApp.re" */
/* ), */
/* {| */
/* Baz.thisWillBeImplementedLater(); */
/* |}, */
/* ) */
/* ) */
/* ) */
/* ) */
/* >>= checkPesyConfig("add virtual modules", () => */
/* PesyConfig.add( */
/* {| */
/* { */
/* "virtual-foo": { */
/* "virtualModules": [ */
/* "VirtualFoo" */
/* ] */
/* }, */
/* "implementation-bar": { */
/* "implements": [ */
/* "test-project/virtual-foo" */
/* ] */
/* }, */
/* "implementation-baz": { */
/* "implements": [ */
/* "test-project/virtual-foo" */
/* ] */
/* }, */
/* "executable-virtual-bar": { */
/* "imports": [ */
/* "Bar = require('test-project/implementation-bar')" */
/* ], */
/* "bin": { */
/* "PesyVirtualAppBar.exe": "PesyVirtualApp.re" */
/* } */
/* }, */
/* "executable-virtual-baz": { */
/* "imports": [ */
/* "Baz = require('test-project/implementation-baz')" */
/* ], */
/* "bin": { */
/* "PesyVirtualAppBaz.exe": "PesyVirtualApp.re" */
/* } */
/* } */
/* } */
/* |}, */
/* Path.(cwd / "package.json"), */
/* ) */
/* ) */
/* ) */
/* ) */
/* ); */
================================================
FILE: errors/Errors.re
================================================
type validationError =
| StaleDuneFile(string)
| StaleOpamFile((string, string));
exception ShouldNotBeNull(string);
exception FatalError(string);
exception ShouldNotBeHere(unit);
exception InvalidRootName(string);
exception GenericException(string);
exception ResolveRelativePathFailure(string);
exception InvalidBinProperty(string);
exception BuildValidationFailures(list(validationError));
exception ImportsParserFailure(unit);
exception LocalLibraryPathNotFound(string);
exception ForeignStubsIncorrectlyUsed;
exception CNamesIncorrectlyUsed;
exception InvalidDuneVersion(string);
================================================
FILE: errors/dune
================================================
(library (name PesyEsyPesyErrors) (public_name pesy--esy-pesy.errors)
(modules (:standard)))
================================================
FILE: lib/Common.re
================================================
exception InvalidSubDirs(string);
type include_subdirs =
| No
| Unqualified;
type t = {
path: string,
require: list(string),
flags: option(list(string)), /* TODO: Use a variant instead since flags only accept a set of values and not any list of strings */
ocamlcFlags: option(list(string)),
ocamloptFlags: option(list(string)),
jsooFlags: option(list(string)),
preprocess: option(list(string)),
includeSubdirs: option(include_subdirs),
rawBuildConfig: option(list(string)),
rawBuildConfigFooter: option(list(string)),
pesyModules: PesyModule.t,
};
let create =
(
path,
require,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
includeSubdirs,
rawBuildConfig,
rawBuildConfigFooter,
pesyModules,
) => {
let includeSubDirsSafe =
switch (includeSubdirs) {
| Some(x) =>
switch (x) {
| "no" => Some(No)
| "unqualified" => Some(Unqualified)
| _ => raise(InvalidSubDirs(x))
}
| None => None
};
{
path,
require,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
includeSubdirs: includeSubDirsSafe,
rawBuildConfig,
rawBuildConfigFooter,
pesyModules,
};
};
let toDuneStanzas = c => {
let {
require,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
includeSubdirs,
rawBuildConfig,
rawBuildConfigFooter,
pesyModules,
_,
} = c;
(
/* libraries: */
switch (require) {
| [] =>
switch (pesyModules) {
| Some(x) =>
Some(
Stanza.createExpression([
Stanza.createAtom("libraries"),
Stanza.createAtom(PesyModule.getNamespace(x)),
]),
)
| None => None
}
| libs =>
Some(
Stanza.createExpression([
Stanza.createAtom("libraries"),
...List.map(
r => Stanza.createAtom(r),
libs
@ (
switch (pesyModules) {
| Some(x) => [PesyModule.getNamespace(x)]
| None => []
}
),
),
]),
)
},
/* flags: */
switch (flags) {
| None =>
switch (pesyModules) {
| Some(x) =>
Some(
Stanza.createExpression([
Stanza.createAtom("flags"),
Stanza.createAtom("-open"),
Stanza.createAtom(PesyModule.getNamespace(x)),
]),
)
| None => None
}
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("flags"),
...List.map(
f => Stanza.createAtom(f),
l
@ (
switch (pesyModules) {
| Some(x) => ["-open", PesyModule.getNamespace(x)]
| None => []
}
),
),
]),
)
},
/* ocamlcFlags */
switch (ocamlcFlags) {
| None => None
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("ocamlc_flags"),
...List.map(f => Stanza.createAtom(f), l),
]),
)
},
/* ocamloptFlags */
switch (ocamloptFlags) {
| None => None
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("ocamlopt_flags"),
...List.map(f => Stanza.createAtom(f), l),
]),
)
},
/* jsooFlags */
switch (jsooFlags) {
| None => None
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("js_of_ocaml"),
...List.map(f => Stanza.createAtom(f), l),
]),
)
},
/* preprocess */
switch (preprocess) {
| None => None
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("preprocess"),
Stanza.createExpression(List.map(f => Stanza.createAtom(f), l)),
]),
)
},
/* includeSubdirs */
switch (includeSubdirs) {
| None => None
| Some(v) =>
Some(
Stanza.createExpression([
Stanza.createAtom("include_subdirs"),
Stanza.createAtom(
switch (v) {
| No => "no"
| Unqualified => "unqualified"
},
),
]),
)
},
/* rawBuildConfig */
switch (rawBuildConfig) {
| None => None
| Some(l) => Some(l |> List.map(Stanza.ofString))
},
/* rawBuildConfig */
switch (rawBuildConfigFooter) {
| None => None
| Some(l) => Some(l |> List.map(Stanza.ofString))
},
/* pesy modules */
PesyModule.generateLibraryStanza(preprocess, pesyModules),
PesyModule.generateAliasModuleStanza(pesyModules),
);
};
let getPath = c => c.path;
let getPesyModules = c => c.pesyModules;
================================================
FILE: lib/Common.rei
================================================
type include_subdirs;
type t;
let toDuneStanzas:
t =>
(
option(Stanza.t),
option(Stanza.t),
option(Stanza.t),
option(Stanza.t),
option(Stanza.t),
option(Stanza.t),
option(Stanza.t),
option(list(Stanza.t)),
option(list(Stanza.t)),
option(Stanza.t),
option(Stanza.t),
);
let getPath: t => string;
let getPesyModules: t => PesyModule.t;
let create:
(
/* path */ string,
/* require */ list(string),
/* flags */ option(list(string)),
/* Ocamlc flags */ option(list(string)),
/* Ocamlopt flags */ option(list(string)),
/* jsoo flags */ option(list(string)),
/* preprocess */ option(list(string)),
/* include subdirs */ option(string),
/* raw config */ option(list(string)),
/* raw config footer */ option(list(string)),
/* pesy modules */ PesyModule.t
) =>
t;
================================================
FILE: lib/DuneFile.re
================================================
module Utils = PesyEsyPesyUtils.Utils;
open Sexplib.Sexp;
open Printf;
exception InvalidDuneFile(unit);
let toString = (stanzas: list(Stanza.t)) =>
List.fold_right(
(s, acc) =>
Str.global_replace(
Str.regexp("\"\\\\\\\\\""),
"\\\\",
to_string_hum(~indent=4, Stanza.toSexp(s)),
)
++ "\n"
++ acc,
stanzas,
"",
);
let ofFile = n => {
open Sexplib;
let contentStr =
try (Utils.readFile(n)) {
| _ => ""
};
Sexp.(
switch (Sexp.of_string(sprintf("(%s)", contentStr))) {
| List(l) => List.map(Stanza.ofSexp, l)
| _ => raise(InvalidDuneFile())
}
);
};
================================================
FILE: lib/DuneFile.rei
================================================
let toString: list(Stanza.t) => string;
let ofFile: string => list(Stanza.t);
================================================
FILE: lib/DuneProject.re
================================================
open Sexplib.Sexp;
exception InvalidDuneProjectFile;
let findLangVersion =
fun
| [x, ..._] =>
switch (Stanza.toSexp(x)) {
| List([Atom("lang"), Atom("dune"), Atom(version)]) => version
| _ => raise(InvalidDuneProjectFile)
}
| _ => raise(InvalidDuneProjectFile);
================================================
FILE: lib/DuneProject.rei
================================================
let findLangVersion: list(Stanza.t) => string;
================================================
FILE: lib/EsyCommand.re
================================================
open Printf;
let esy_command = "esy";
/* @esy-ocaml/foo-package -> foo-package */
let resolveEsyCommand = () =>
Sys.unix
? try(
Sys.command(sprintf("%s --version &>/dev/null", esy_command)) == 0
? Some(esy_command) : None
) {
| _ => None
}
: {
let pathVars =
Array.to_list(Unix.environment())
|> List.map(e =>
switch (Str.split(Str.regexp("="), e)) {
| [k, v, ..._rest] => Some((k, v))
| _ => None
}
)
|> List.filter(
fun
| None => false
| _ => true,
)
|> List.filter(e =>
switch (e) {
| Some((k, _)) => String.lowercase_ascii(k) == "path"
| _ => false
}
)
|> List.map(
fun
| Some(x) => x
| None => ("", "") /* Why not filter_map? */
);
let paths =
Str.split(
Str.regexp(Sys.unix ? ": " : ";"),
String.concat(
Sys.unix ? ": " : ";",
List.map(
e => {
let (_, v) = e;
v;
},
pathVars,
),
),
);
/* Unix.putenv("PATH", v); /\* This didn't work! *\/ */
let npmPaths =
List.filter(
path => Sys.file_exists(Filename.concat(path, "esy.cmd")),
paths,
);
switch (npmPaths) {
| [] => None
| [h, ..._] => Some(Filename.concat(h, "esy.cmd"))
};
};
================================================
FILE: lib/Executable.re
================================================
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
module Mode = {
exception InvalidCompilationMode(unit);
exception InvalidBinaryKind(unit);
module Compilation: {
type t;
let toString: t => string;
let ofString: string => t;
} = {
type t =
| Byte
| Native
| Best;
let toString =
fun
| Byte => "byte"
| Native => "native"
| Best => "best";
let ofString =
fun
| "byte" => Byte
| "native" => Native
| "best" => Best
| _ => raise(InvalidCompilationMode());
};
module BinaryKind: {
type t =
| C
| Exe
| Object
| Shared_object
| JS
| Plugin;
let toString: t => string;
let ofString: string => t;
} = {
type t =
| C
| Exe
| Object
| Shared_object
| JS
| Plugin;
let toString =
fun
| C => "c"
| Exe => "exe"
| Object => "object"
| Shared_object => "shared_object"
| JS => "js"
| Plugin => "plugin";
let ofString =
fun
| "c" => C
| "exe" => Exe
| "object" => Object
| "shared_object" => Shared_object
| "js" => JS
| "plugin" => Plugin
| _ => raise(InvalidBinaryKind());
};
type t =
| Atom(BinaryKind.t)
| Tuple(Compilation.t, BinaryKind.t)
| Array(list(t));
exception InvalidExecutableMode(string);
let rec ofFieldTypes = parts =>
switch (parts) {
| FieldTypes.String(bs) => {
let parts = bs
|> Str.split(Str.regexp("[ \n\r\x0c\t]+"))
if (List.length(parts) > 1) {
Array(parts |> List.map(b => Atom(BinaryKind.ofString(b))))
} else {
Atom(BinaryKind.ofString(List.hd(parts)))
}
}
| FieldTypes.List([FieldTypes.String(c), FieldTypes.String(b)]) =>
try(Tuple(Compilation.ofString(c), BinaryKind.ofString(b))) {
| InvalidCompilationMode () =>
Array([
Atom(BinaryKind.ofString(c)),
Atom(BinaryKind.ofString(b)),
])
| _ =>
raise(
InvalidExecutableMode(
"Invalid executable mode: expected of the form [(<compilation mode>, <binary_kind>)'s | <binary_kind>'s] or <binary_kind>'s",
),
)
}
| FieldTypes.List(xs) => Array(List.map(ofFieldTypes, xs))
| _ =>
raise(
InvalidExecutableMode(
"Invalid executable mode: expected of the form [(<compilation mode>, <binary_kind>)'s | <binary_kind>'s] or <binary_kind>'s",
),
)
};
let rec toStanzas = m =>
switch (m) {
| Array(x) => List.fold_left((acc, s) => acc @ toStanzas(s), [], x)
| Tuple(c, b) => [
Stanza.createExpression([
Stanza.createAtom(Compilation.toString(c)),
Stanza.createAtom(BinaryKind.toString(b)),
]),
]
| Atom(b) => [Stanza.createAtom(BinaryKind.toString(b))]
};
};
type t = {
binKVs: list((string, string)),
modes: option(Mode.t),
};
let create = (binKVs, modes) => {binKVs, modes};
let toDuneStanza = (common: Common.t, e) => {
/* let {name: pkgName, require, path} = common; */
let {binKVs, modes: modesP} = e;
let (
libraries,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
includeSubdirs,
rawBuildConfig,
rawBuildConfigFooter,
pesyModulesLibrary,
pesyModulesAliasModuleGen,
) =
Common.toDuneStanzas(common);
let path = Common.getPath(common);
let (mains, publicNames) = List.fold_right((tuple, acc) => {
let (main, publicName) = tuple;
let (mains, publicNames) = acc;
(mains @ [main], publicNames @ [publicName]);
}, binKVs,([], []));
let name = Stanza.createExpression([Stanza.createAtom("names"), ...(mains |> List.map(x => x|> moduleNameOf |> Stanza.createAtom))])
let public_name = Stanza.createExpression([Stanza.createAtom("public_names"), ...(publicNames |> List.map(Stanza.createAtom))]);
let modules =
Stanza.createExpression([
Stanza.createAtom("modules"),
Stanza.createExpression(
[Stanza.createAtom(":standard")]
@ (
switch (Common.getPesyModules(common)) {
| Some(x) => [
Stanza.createAtom("\\"),
Stanza.createAtom(PesyModule.getNamespace(x)),
]
| None => []
}
),
),
]);
/* let public_name = */
/* Stanza.create("public_name", Stanza.createAtom(pkgName)); */
/* let libraries = */
/* switch (require) { */
/* | [] => None */
/* | libs => */
/* Some( */
/* Stanza.createExpression([ */
/* Stanza.createAtom("libraries"), */
/* ...List.map(r => Stanza.createAtom(r), libs), */
/* ]), */
/* ) */
/* }; */
let modesD =
switch (modesP) {
| None => None
| Some(m) =>
Some(
Stanza.createExpression([
Stanza.createAtom("modes"),
...Mode.toStanzas(m),
]),
)
};
let mandatoryExpressions = switch(modesP) {
| Some(Mode.Atom(Mode.BinaryKind.JS)) => [name, modules]
| _ => [name, modules, public_name]
}
let optionalExpressions = [
libraries,
modesD,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
];
let rawBuildConfig =
switch (rawBuildConfig) {
| Some(l) => l
| None => []
};
let rawBuildConfigFooter =
switch (rawBuildConfigFooter) {
| Some(l) => l
| None => []
};
let optionalRootStanzas =
rawBuildConfigFooter
@ (
switch (pesyModulesLibrary) {
| Some(s) => [s]
| None => []
}
)
@ (
switch (pesyModulesAliasModuleGen) {
| Some(s) => [s]
| None => []
}
)
@ (
switch (includeSubdirs) {
| Some(s) => [s]
| None => []
}
);
let executable =
Stanza.createExpression([
Stanza.createAtom("executables"),
...mandatoryExpressions
@ filterNone(optionalExpressions)
@ rawBuildConfig,
]);
(path, [executable, ...optionalRootStanzas]);
};
================================================
FILE: lib/Executable.rei
================================================
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
type t;
module Mode: {
type t;
let ofFieldTypes: FieldTypes.t => t;
let toStanzas: t => list(Stanza.t);
};
let create: (list((string, string)), option(Mode.t)) => t;
let toDuneStanza: (Common.t, t) => (string, list(Stanza.t));
================================================
FILE: lib/ImportsParser.re
================================================
let parse = str => {
let lexbuf = Lexing.from_string(str);
Parser.main(Lexer.read, lexbuf);
};
================================================
FILE: lib/Lexer.mll
================================================
(* File lexer.mll *)
{
open Parser (* The type token is defined in parser.mli *)
}
rule read = parse
[' ' '\t'] { read lexbuf } (* skip blanks *)
| ['('] { LPAREN }
| [')'] { RPAREN }
| [';'] { SEMICOLON }
| ['='] { ASSN }
| "require" { REQUIRE }
| ['\''] { SQUOTE }
| ['A' - 'Z' ] [ 'A' - 'Z' 'a' - 'z' '_' '\'' '0'-'9'] * as lxm { MODULE_NAME(lxm) }
| ['@'] ? ['A'-'Z' 'a'-'z' '.' '/' '-' '0'-'9' '_'] + as lxm { MODULE_PATH(lxm) }
| ['\n'] {EOL}
| eof {EOF}
================================================
FILE: lib/Lib.re
================================================
module Mode = Mode;
module EsyCommand = EsyCommand;
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
open NoLwt;
type fileOperation =
| UPDATE(string)
| CREATE(string);
let gen = (projectPath, pkgPath) => {
let conf = PesyConf.get(pkgPath);
let pkgs = PesyConf.pkgs(conf);
let rootName = PesyConf.rootName(conf);
let rootNameOpamFile = rootName ++ ".opam";
let operations = ref([]);
pkgs
|> List.iter(((subpackage, _)) => {
mkdirp(Path.(projectPath / subpackage));
let duneFilePath = Path.(projectPath / subpackage / "dune");
write(
duneFilePath,
{|(* -*- tuareg -*- *)
open Jbuild_plugin.V1
let () =
run_and_read_lines ("pesy dune-file " ^ Sys.getcwd ())
|> String.concat "\n"
|> send
|},
);
operations :=
[
CREATE(
Str.global_replace(
Str.regexp(Path.(projectPath / "")),
"",
duneFilePath,
),
),
...operations^,
];
});
let foundAnOpamFile = ref(false);
let dirForEachEntry = (f, dirname) => {
let d = Unix.opendir(dirname);
try(
while (true) {
f(Unix.readdir(d));
}
) {
| End_of_file => Unix.closedir(d)
};
};
let contains = (n, s) =>
try(Str.search_forward(Str.regexp(s), n, 0) != (-1)) {
| Not_found => false
};
dirForEachEntry(
n =>
if (contains(n, ".opam") && ! foundAnOpamFile^) {
foundAnOpamFile := true;
if (n != rootNameOpamFile) {
copyFile(
Path.(projectPath / n),
Path.(projectPath / rootNameOpamFile),
);
Unix.unlink(Path.(projectPath / n));
operations := [CREATE(rootNameOpamFile), ...operations^];
};
},
projectPath,
);
operations^;
};
let log = operations => {
print_newline();
List.iter(
o => {
switch (o) {
| CREATE(x) =>
print_endline(
<Pastel> " Created " <Pastel bold=true> x </Pastel> </Pastel>,
)
| UPDATE(x) =>
print_endline(
<Pastel> " Updated " <Pastel bold=true> x </Pastel> </Pastel>,
)
};
();
},
operations,
);
print_newline();
};
let generateBuildFiles = projectRoot => {
let packageJSONPath = Path.(projectRoot / "package.json");
gen(projectRoot, packageJSONPath);
};
let build = manifestFile => PesyConf.get(manifestFile) |> PesyConf.rootName;
let normalize = x =>
x
|> Str.global_replace(Str.regexp("\\"), "/")
|> Str.global_replace(Str.regexp("[/|\\]$"), "")
|> (x => Sys.unix ? x : String.lowercase_ascii(x));
exception InvalidPackagePath(string);
let duneFile = (projectPath, manifestFile, subpackagePath) => {
let normalizedSubpackagePath = normalize(subpackagePath);
let conf = PesyConf.get(manifestFile);
let pkgs = PesyConf.pkgs(conf);
let duneProjectPath = Path.(projectPath / "dune-project");
let duneVersion =
DuneFile.ofFile(duneProjectPath) |> DuneProject.findLangVersion;
let rootName = PesyConf.rootName(conf);
let pesyPackages =
try(
pkgs
|> List.map(PesyConf.toPesyConf(projectPath, rootName, ~duneVersion))
) {
| x => raise(x)
};
/** TODO: Why compute for every subpackage? */
PesyConf.(
switch (
pesyPackages
|> List.find_opt(pesyPackage =>
normalize(pesyPackage.pkg_path) == normalizedSubpackagePath
)
) {
| None =>
raise(
InvalidPackagePath(
"No package found with path: " ++ normalizedSubpackagePath,
),
)
| Some(pesyPackage) =>
let (_, duneFile) =
PesyConf.toDunePackages(projectPath, rootName, pesyPackage);
DuneFile.toString(duneFile) |> print_endline;
}
);
};
let duneEject = (projectPath, manifestFile, subpackageNameOrPath) => {
let normalizedSubpackageNameOrPath = normalize(subpackageNameOrPath);
let conf = PesyConf.get(manifestFile);
let pkgs = PesyConf.pkgs(conf);
let duneProjectPath = Path.(projectPath / "dune-project");
let duneVersion =
DuneFile.ofFile(duneProjectPath) |> DuneProject.findLangVersion;
let rootName = PesyConf.rootName(conf);
let pesyPackages =
pkgs
|> List.map(((pkgName, _) as pkg) =>
(
pkgName,
PesyConf.toPesyConf(projectPath, rootName, pkg, ~duneVersion),
)
);
switch (
pesyPackages
|> List.find_opt(((pkgName, pesyPackage)) => {
PesyConf.(
normalize(pesyPackage.pkg_path) == normalizedSubpackageNameOrPath
|| pkgName == normalizedSubpackageNameOrPath
)
})
) {
| None =>
raise(
InvalidPackagePath(
"No package found with path or name: "
++ normalizedSubpackageNameOrPath,
),
)
| Some((pkgNameToEject, pesyPackage)) =>
let (_, duneFile) =
PesyConf.toDunePackages(projectPath, rootName, pesyPackage);
let newPkgs =
pkgs |> List.filter(((pkgName, _)) => pkgName != pkgNameToEject);
let newConf =
switch (conf) {
| `Assoc(newJson) =>
`Assoc(
newJson
|> List.map(((fieldName, _) as field) =>
fieldName != "buildDirs"
? field : (fieldName, `Assoc(newPkgs))
),
)
| _ => conf
};
write(
Path.(projectPath / "package.json"),
JSON.pretty_to_string(newConf),
);
write(Path.(pesyPackage.pkg_path / "dune"), DuneFile.toString(duneFile));
};
};
let validateManifestFile = (projectPath, manifestFile) => {
let conf = PesyConf.get(manifestFile);
let pkgs = PesyConf.pkgs(conf);
let duneProjectPath = Path.(projectPath / "dune-project");
let duneVersion =
DuneFile.ofFile(duneProjectPath) |> DuneProject.findLangVersion;
let rootName = PesyConf.rootName(conf);
ignore(
pkgs
|> List.map(PesyConf.toPesyConf(projectPath, rootName, ~duneVersion)),
);
};
================================================
FILE: lib/Library.re
================================================
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
module Mode = {
exception InvalidLibraryMode(string);
type t =
| Native
| Byte
| Best;
let ofString =
fun
| "best" => Best
| "native" => Native
| "byte" => Byte
| x => raise(InvalidLibraryMode(x));
let toString =
fun
| Best => "best"
| Native => "native"
| Byte => "byte";
};
type t = {
name: string,
public: bool,
namespace: string,
modes: option(list(Mode.t)),
ffi: option(Stubs.t),
virtualModules: option(list(string)),
implements: option(list(string)),
wrapped: option(bool),
};
let create =
(name, public, namespace, modes, ffi, virtualModules, implements, wrapped) => {
name,
public,
namespace,
modes,
ffi,
virtualModules,
implements,
wrapped,
};
let toDuneStanza = (common, lib) => {
/* let {name: pkgName, require, path} = common */
let {
name,
public,
namespace,
modes: modesP,
ffi: stubsP,
virtualModules: virtualModulesP,
implements: implementsP,
wrapped: wrappedP,
} = lib;
let (
libraries,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
includeSubdirs,
rawBuildConfig,
rawBuildConfigFooter,
pesyModulesLibrary,
pesyModulesAliasModuleGen,
) =
Common.toDuneStanzas(common);
let path = Common.getPath(common);
let public_name = if (public) { Stanza.createAtom(name) |> Stanza.create("public_name") |> Option.some } else { None }; // pesy's name is Dune's public_name
let name = Stanza.create("name", Stanza.createAtom(namespace));
let modules =
Stanza.createExpression([
Stanza.createAtom("modules"),
Stanza.createExpression(
[Stanza.createAtom(":standard")]
@ (
switch (Common.getPesyModules(common)) {
| Some(x) => [
Stanza.createAtom("\\"),
Stanza.createAtom(PesyModule.getNamespace(x)),
]
| None => []
}
),
),
]);
let modesD =
switch (modesP) {
| None => None
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("modes"),
...List.map(m => m |> Mode.toString |> Stanza.createAtom, l),
]),
)
};
let stubsD = Stubs.toDuneStanza(stubsP);
let virtualModulesD =
switch (virtualModulesP) {
| None => None
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("virtual_modules"),
...List.map(Stanza.createAtom, l),
]),
)
};
let implementsD =
switch (implementsP) {
| None => None
| Some(l) =>
Some(
Stanza.createExpression([
Stanza.createAtom("implements"),
...List.map(Stanza.createAtom, l),
]),
)
};
let wrappedD =
switch (wrappedP) {
| None => None
| Some(w) =>
Some(
Stanza.createExpression([
Stanza.createAtom("wrapped"),
Stanza.createAtom(string_of_bool(w)),
]),
)
};
let mandatoryExpressions = [name, modules];
let optionalExpressions = [
public_name,
libraries,
modesD,
virtualModulesD,
implementsD,
wrappedD,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
...stubsD,
];
let rawBuildConfig =
switch (rawBuildConfig) {
| Some(l) => l
| None => []
};
let rawBuildConfigFooter =
switch (rawBuildConfigFooter) {
| Some(l) => l
| None => []
};
let optionalRootStanzas =
rawBuildConfigFooter
@ (
switch (pesyModulesLibrary) {
| Some(s) => [s]
| None => []
}
)
@ (
switch (pesyModulesAliasModuleGen) {
| Some(s) => [s]
| None => []
}
)
@ (
switch (includeSubdirs) {
| Some(s) => [s]
| None => []
}
);
let library =
Stanza.createExpression([
Stanza.createAtom("library"),
...mandatoryExpressions
@ filterNone(optionalExpressions)
@ rawBuildConfig,
]);
(path, [library, ...optionalRootStanzas]);
};
================================================
FILE: lib/Library.rei
================================================
module Mode: {
exception InvalidLibraryMode(string);
type t;
let ofString: string => t;
let toString: t => string;
};
type t;
let create:
(
string,
bool,
string,
option(list(Mode.t)),
option(Stubs.t),
option(list(string)),
option(list(string)),
option(bool)
) =>
t;
let toDuneStanza: (Common.t, t) => (string, list(Stanza.t));
================================================
FILE: lib/Mode.re
================================================
module EsyEnv = {
type t' =
| UPDATE;
/* | BUILD; */
};
================================================
FILE: lib/Parser.mly
================================================
%token LPAREN RPAREN SEMICOLON
%token EOL ASSN SQUOTE EOF REQUIRE
%token <string> MODULE_NAME
%token <string> MODULE_PATH
%start main /* the entry point */
%type <string * string> main
%%
main:
MODULE_NAME ASSN REQUIRE LPAREN SQUOTE MODULE_PATH SQUOTE RPAREN EOF { ($1, $6) }
| MODULE_NAME ASSN REQUIRE LPAREN SQUOTE MODULE_PATH SQUOTE RPAREN SEMICOLON EOF { ($1, $6) }
;
================================================
FILE: lib/PesyConf.re
================================================
open Printf;
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
open PesyEsyPesyErrors.Errors;
/*****
localName ~~> dunePublicName
pesy_modules_namespace ~~> namespace
modules ~~> aliases
***/
type pkgType =
| ExecutablePackage(Executable.t)
| LibraryPackage(Library.t)
| TestPackage(Test.t);
type package = {
pkg_path: string,
common: Common.t,
pkgType,
};
module Version = {
type t =
| V1(int)
| V2(int)
| V3(int);
let ofString = version => {
let vs = String.split_on_char('.', version) |> List.map(int_of_string);
switch (vs) {
| [1, minorVersion] => V1(minorVersion)
| [2, minorVersion] => V2(minorVersion)
| [3, minorVersion] => V3(minorVersion)
| _ => raise(InvalidDuneVersion(version))
};
};
};
/* private */
exception ShouldHaveRaised(unit);
let findIndex = (s1, s2) => {
let re = Str.regexp_string(s2);
try(Str.search_forward(re, s1, 0)) {
| Not_found => (-1)
};
};
let resolveRelativePath = path => {
let separator = "/";
let revParts = List.rev(Str.split(Str.regexp(separator), path));
let rec resolve = (parts, skipCount, acc) => {
switch (parts) {
| ["..", ...r] => resolve(r, skipCount + 1, acc)
| [".", ...r] => resolve(r, skipCount, acc)
| [h, ...r] =>
resolve(
r,
skipCount > 0 ? skipCount - 1 : 0,
skipCount == 0 ? [h, ...acc] : acc,
)
| [] =>
if (skipCount == 0) {
acc;
} else {
raise(
ResolveRelativePathFailure(
sprintf("Failed resolving: %s Too many `../`", path),
),
);
}
};
};
let dirSepChar = Filename.dir_sep.[0];
(path.[0] == dirSepChar ? Filename.dir_sep : "")
++ String.concat(separator, resolve(revParts, 0, []));
};
let specialCaseOpamRequires = package =>
Str.global_replace(Str.regexp("^@opam/"), "", package);
module Result = {
let bind = (o, f) =>
switch (o) {
| Ok(x) => f(x)
| Error(e) => Error(e)
};
let ( let* ) = (x, f) => bind(x, f);
};
let isValidScopeName = n => {
n.[0] == '@';
};
let resolveDepNameToFindlib = depName => {
let skipEnvVar = envVar =>
Array.fold_left(
(acc, e) =>
if (Str.string_match(
Str.regexp(Printf.sprintf("%s=.*", envVar)),
e,
0,
)) {
acc;
} else {
[e, ...acc];
},
[],
);
let list_diff = (l1, l2) => {
l1 |> List.filter(e => !List.mem(e, l2));
};
/**
The above parses {|
foo (version: n/a)
foo.lib (version: n/a)
foo.bar.lib (version: n/a)
|}
to
[["foo"], ["foo", "lib"], ["foo", "bar", "lib"]]
*/
let parseRelevantFindlibOutput = relevantFindlibOutputLines =>
relevantFindlibOutputLines
|> List.map(x =>
Str.split(Str.regexp(" "), x)
|> List.hd
|> Str.split(Str.regexp("\\."))
);
let safeSeg = name => {
/* Taken from esy's source */
let replaceAt = Str.regexp("@");
let replaceUnderscore = Str.regexp("_+");
let replaceSlash = Str.regexp("\\/");
let replaceDash = Str.regexp("\\-");
let replaceColon = Str.regexp(":");
name
|> String.lowercase_ascii
|> Str.global_replace(replaceAt, "")
|> Str.global_replace(replaceUnderscore, "__")
|> Str.global_replace(replaceSlash, "__s__")
|> Str.global_replace(replaceColon, "__c__")
|> Str.global_replace(replaceDash, "_");
};
let fullDepNameParts = Str.split(Str.regexp("/"), depName);
let (depName, remainingParts) =
switch (fullDepNameParts) {
| [] => failwith("How can this be empty?")
| [x] => (x, [])
| [x, y, ...rest] =>
if (isValidScopeName(x)) {
(x ++ "/" ++ y, rest);
} else {
(x, [y, ...rest]);
}
};
let process_env =
Unix.environment() |> skipEnvVar("ESY__ROOT_PACKAGE_CONFIG_PATH");
let esyfiedDepName = safeSeg(depName);
open Result;
let* ocamlpath =
switch (Sys.getenv_opt("OCAMLPATH")) {
| Some(v) => Ok(v)
| None => Error("OCAMLPATH not found in env")
};
let candidates =
Str.split(Str.regexp(Sys.unix ? ":" : ";"), ocamlpath)
|> List.filter_map(v =>
if (Str.string_match(
Str.regexp(".*" ++ esyfiedDepName ++ "-.*"),
v,
0,
)) {
let (_exitCode, emptyOutput) =
run(
~env=
Array.of_list([
"OCAMLPATH=",
...process_env |> List.filter(x => x != "OCAMLPATH"),
]),
"ocamlfind",
[|"list"|],
);
let (_exitCode, depOutput) =
run(
~env=
Array.of_list([
Printf.sprintf("OCAMLPATH=%s", v),
...process_env |> List.filter(x => x != "OCAMLPATH"),
]),
"ocamlfind",
[|"list"|],
);
let relevantFindlibOutputLines = list_diff(depOutput, emptyOutput);
let listOfLibraryNameAsList =
parseRelevantFindlibOutput(relevantFindlibOutputLines);
/** Since the library base name (say foo) need not match the
npm package name, we are only going to compare the rest (["lib"], ["bar", "lib"] etc) */
let listOfCandidateLibraryNameAsList =
listOfLibraryNameAsList
|> List.filter_map(
fun
| [] => None
| [_, ...rest] as libraryNameAsList =>
if (rest == remainingParts) {
Some(libraryNameAsList);
} else {
None;
},
);
switch (listOfCandidateLibraryNameAsList) {
| [] => None
| candidateLibraryNameAsList =>
switch (candidateLibraryNameAsList) {
| [] => None
| x => Some(x |> List.map(String.concat("/")))
}
};
} else {
None;
}
)
|> List.flatten;
switch (candidates) {
| [] =>
Error(
Printf.sprintf(
"Library %s not found",
String.concat("/", [depName, ...remainingParts]),
),
)
| [candidate] => Ok(candidate)
| multipleCandidates =>
Error(
Printf.sprintf(
{|Multiple candidates found for %s
%s|},
depName,
multipleCandidates |> String.concat("\n"),
),
)
};
};
let isValidBinaryFileName = fileName =>
Str.string_match(Str.regexp("^.+\\.exe$"), fileName, 0);
let isValidSourceFile = fileName =>
Str.string_match(Str.regexp("^.+\\.\\(re\\|ml\\)$"), fileName, 0);
/* Turns "Foo.re as Foo.exe" => ("Foo.re", "Foo.exe") */
/* Turns "foo/bar/baz" => "foo.bar.baz" */
let pathToOCamlLibName = p => Str.global_replace(Str.regexp("/"), ".", p);
let stripAtTheRate = s => String.sub(s, 1, String.length(s) - 1);
/* doubleKebabifyIfScoped turns @myscope/pkgName => myscope--pkgName */
let doubleKebabifyIfScoped = n => {
switch (Str.split(Str.regexp("/"), n)) {
| [pkgName] => pkgName
| [scope, pkgName, ...rest] =>
isValidScopeName(scope)
? String.concat(
"/",
[stripAtTheRate(scope) ++ "--" ++ pkgName, ...rest],
)
: String.concat("/", [scope, pkgName, ...rest])
| _ => raise(InvalidRootName(n))
};
};
type t = (string, list(package));
let toPesyConf = (projectPath, rootName, pkg, ~duneVersion) => {
let (dir, conf) = pkg;
let binJSON = JSON.member(conf, "bin");
let bin =
try(
Some(
{
JSON.toKeyValuePairs(binJSON)
|> List.map(kv => {
let (k, v) = kv;
(
/* Value is the source .re file */
v |> JSON.toValue |> FieldTypes.toString,
/* Key is the target executable name */
k,
);
});
},
)
) {
| JSON.NullJSONValue(_) => None
/* If its a string and not a JSON */
| JSON.InvalidJSONValue(_) =>
let binaryMainFile =
try(binJSON |> JSON.toValue |> FieldTypes.toString) {
| _ => raise(InvalidBinProperty(dir))
};
if (!isValidSourceFile(binaryMainFile)) {
raise(InvalidBinProperty(dir));
};
Some([(binaryMainFile, moduleNameOf(binaryMainFile) ++ ".exe")]);
| e => raise(e)
};
/* Pesy'name is Dune's public_name */
/* If name is provided and binary's public_name is also provided in the bin property, name takes precedence */
let name =
try(JSON.member(conf, "name") |> JSON.toValue |> FieldTypes.toString) {
| JSON.NullJSONValue(_) =>
switch (bin) {
| Some(names) =>
names
|> List.map(((_mainFileName, installedBinaryName)) =>
installedBinaryName
)
|> List.hd
| None => rootName ++ "." ++ pathToOCamlLibName(dir)
}
| e => raise(e)
};
let public =
try(JSON.member(conf, "public") |> JSON.toValue |> FieldTypes.toBool) {
| JSON.NullJSONValue(_) => false
| e => raise(e)
};
let (<|>) = (f, g, x) => g(f(x));
/* "my-package/lib/here" => "my-package.lib.here" */
let require =
try(
JSON.member(conf, "require")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(
FieldTypes.toString
<|> (
x => x.[0] == '.' ? sprintf("%s/%s/%s", rootName, dir, x) : x
)
<|> resolveRelativePath
<|> (
x =>
x.[0] == '@'
? if (findIndex(doubleKebabifyIfScoped(x), rootName) != 0) {
switch (x |> resolveDepNameToFindlib) {
| Ok(x) => x
| Error(msg) => raise(Failure(msg))
};
} else {
x |> doubleKebabifyIfScoped;
}
: x
)
<|> pathToOCamlLibName,
)
) {
| JSON.NullJSONValue(_) => []
| e => raise(e)
};
let imports =
try(
JSON.member(conf, "imports")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(FieldTypes.toString <|> ImportsParser.parse)
) {
| JSON.NullJSONValue(_) => []
| _e => raise(ImportsParserFailure())
};
let modesAsFieldTypes =
try(Some(JSON.member(conf, "modes") |> JSON.toValue)) {
| JSON.NullJSONValue () => None
| e => raise(e)
};
let modes =
switch (modesAsFieldTypes) {
| Some(modesAsFieldTypes) =>
try(Some(Executable.Mode.ofFieldTypes(modesAsFieldTypes))) {
| _ => None
}
| None => None
};
let pesyModuleNamespace =
[rootName]
@ String.split_on_char('/', dir)
@ ["PesyModules"]
|> List.map(upperCamelCasify)
|> List.fold_left((++), "");
let isLocalLibrary = path => findIndex(path, rootName) == 0;
let aliases =
imports
|> List.map(import => {
let (exportedNamespace, lib) = import;
let libraryAsPath =
lib
|> (
x => {
x.[0] == '.' ? sprintf("%s/%s/%s", rootName, dir, x) : x;
}
)
|> resolveRelativePath
|> (
x =>
x.[0] == '@'
? if (findIndex(doubleKebabifyIfScoped(x), rootName) != 0) {
switch (x |> resolveDepNameToFindlib) {
| Ok(x) => x
| Error(msg) => raise(Failure(msg))
};
} else {
x |> doubleKebabifyIfScoped;
}
: x
);
let stripRootName =
Str.global_replace(Str.regexp(rootName ++ "/"), "");
let basePathToRequirePkg =
libraryAsPath
|> stripRootName
|> (x => Path.(projectPath / x))
|> resolveRelativePath;
if (isLocalLibrary(libraryAsPath)) {
if (!Sys.file_exists(basePathToRequirePkg)) {
raise(PesyEsyPesyErrors.Errors.LocalLibraryPathNotFound(lib));
};
};
let originalNamespace =
if (isLocalLibrary(libraryAsPath)) {
libraryAsPath
|> String.split_on_char('/')
|> List.map(upperCamelCasify)
|> List.fold_left((++), "");
} else {
/** ie. external library. We use findlib and figure out namespace **/
let findlibQueryModes =
switch (modesAsFieldTypes) {
| Some(modes) =>
modes |> FieldTypes.toList |> List.map(FieldTypes.toString)
| None => ["native"]
};
Str.global_replace(
Str.regexp("\\.cm.*"),
"",
Findlib.package_property(
findlibQueryModes,
pathToOCamlLibName(libraryAsPath),
"archive",
),
)
|> String.mapi((i, c) => i == 0 ? Char.uppercase_ascii(c) : c);
};
PesyModule.Alias.create(
~alias={
switch (
List.fold_left(
(accExt, ext) =>
List.fold_left(
(accEntry, entry) => {
switch (accEntry) {
| Some(x) => Some(x)
| None =>
if (findIndex(libraryAsPath, rootName) != 0) {
Some(
sprintf(
"module %s = %s;",
exportedNamespace,
originalNamespace,
),
);
} else if (Sys.file_exists(
Path.(basePathToRequirePkg / entry)
++ ext,
)
|| Sys.file_exists(
Path.(
basePathToRequirePkg
/ String.lowercase_ascii(entry)
)
++ ext,
)) {
Some(
sprintf(
"module %s = %s.%s;",
exportedNamespace,
originalNamespace,
entry,
),
);
} else {
None;
}
}
},
accExt,
["Index", exportedNamespace] /* If it finds, Index.re, it doesn't look for Bar.re */
),
None,
[".re", ".ml"],
)
) {
| Some(x) => x
| None =>
sprintf(
"module %s = %s;",
exportedNamespace,
originalNamespace,
)
};
},
~internal=isLocalLibrary(libraryAsPath),
~library=pathToOCamlLibName(libraryAsPath),
~originalNamespace,
~exportedNamespace,
);
});
let aliasesWithDifferentNamespace =
PesyModule.Alias.(
aliases
|> List.filter(alias =>
alias.originalNamespace != alias.exportedNamespace
)
);
/*
Ex: Pastel = require('pastel/lib'). In this case,
we should skip aliasing. See: https://github.com/esy/pesy/issues/171
*/
let aliasesWithSameNamespace =
PesyModule.Alias.(
aliases
|> List.filter(alias =>
alias.originalNamespace == alias.exportedNamespace
)
);
let pesyModules =
PesyModule.create(
~namespace=pesyModuleNamespace,
~dunePublicName=
sprintf(
"%s.pesy-modules",
pathToOCamlLibName(rootName ++ "/" ++ dir),
),
~aliases=aliasesWithDifferentNamespace,
);
let fromListOrString = ls =>
try(FieldTypes.toList(ls)) {
| FieldTypes.ConversionException("Expected list. Actual string") =>
FieldTypes.toString(ls)
|> Str.split(Str.regexp("[ \n\r\x0c\t]+"))
|> List.map(s => FieldTypes.String(s))
| e => raise(e)
};
let flags =
try(
Some(
JSON.member(conf, "flags")
|> JSON.toValue
|> fromListOrString
|> List.map(FieldTypes.toString),
)
) {
| _ => None
};
let ocamlcFlags =
try(
Some(
JSON.member(conf, "ocamlcFlags")
|> JSON.toValue
|> fromListOrString
|> List.map(FieldTypes.toString),
)
) {
| _ => None
};
let ocamloptFlags =
try(
Some(
JSON.member(conf, "ocamloptFlags")
|> JSON.toValue
|> fromListOrString
|> List.map(FieldTypes.toString),
)
) {
| _ => None
};
let jsooFlags =
try(
Some(
JSON.member(conf, "jsooFlags")
|> JSON.toValue
|> fromListOrString
|> List.map(FieldTypes.toString),
)
) {
| _ => None
};
let preprocess =
try(
Some(
JSON.member(conf, "preprocess")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(FieldTypes.toString),
)
) {
| _ => None
};
let includeSubdirs =
try(
Some(
JSON.member(conf, "includeSubdirs")
|> JSON.toValue
|> FieldTypes.toString,
)
) {
| JSON.NullJSONValue(_) => None
| e => raise(e)
};
let rawBuildConfig =
try(
Some(
JSON.member(conf, "rawBuildConfig")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(FieldTypes.toString),
)
) {
| _ => None
};
let rawBuildConfigFooter =
try(
Some(
JSON.member(conf, "rawBuildConfigFooter")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(FieldTypes.toString),
)
) {
| _ => None
};
let pkg_path = Path.(projectPath / dir);
let common =
Common.create(
Path.(projectPath / dir),
List.append(
require,
PesyModule.Alias.(
aliasesWithSameNamespace
|> List.map(pesyModule => pesyModule.library)
),
),
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
includeSubdirs,
rawBuildConfig,
rawBuildConfigFooter,
pesyModules,
);
/* Prioritising `bin` over `name` */
switch (bin) {
| Some(binKVs) =>
//bins |> List.map(((mainFileName, _installedBinaryName)) => moduleNameOf(mainFileName))
{
pkg_path,
common,
pkgType: ExecutablePackage(Executable.create(binKVs, modes)),
}
| None =>
let namespace =
try(
JSON.member(conf, "namespace") |> JSON.toValue |> FieldTypes.toString
) {
| JSON.NullJSONValue () =>
sprintf("%s/%s", rootName, dir)
|> String.split_on_char('/')
|> List.map(upperCamelCasify)
|> List.fold_left((++), "")
| e => raise(e)
};
let libraryModes =
try(
Some(
JSON.member(conf, "modes")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(FieldTypes.toString)
|> List.map(Library.Mode.ofString),
)
) {
| JSON.NullJSONValue () => None
| e => raise(e)
};
let cStubs = cns =>
try(
JSON.toValue(cns)
|> FieldTypes.toList
|> List.map(FieldTypes.toString)
) {
| e => raise(e)
};
let foreignStubs = fss =>
try(
JSON.toListKVPairs(fss)
|> List.map(kvs =>
List.map(
kv => {
let (k, v) = kv;
(k, v |> JSON.toValue);
},
kvs,
)
)
) {
| e => raise(e)
};
let (cnJSON, fsJSON) = (
JSON.member(conf, "cNames"),
JSON.member(conf, "foreignStubs"),
);
let ffi =
switch (
Version.ofString(duneVersion),
JSON.toOption(cnJSON),
JSON.toOption(fsJSON),
) {
| (V1(_), None, None) => None
| (V1(_), Some(cn), None)
| (V1(_), Some(cn), Some(_)) => Some(Stubs.ofCNames(cStubs(cn)))
| (V1(_), None, Some(_)) => raise(ForeignStubsIncorrectlyUsed)
/* foreign stubs is supported in version > 1.0 */
| (_, None, None) => None
| (_, Some(_), None) => raise(CNamesIncorrectlyUsed)
| (_, None, Some(fs))
| (_, Some(_), Some(fs)) =>
Some(Stubs.ofForeignStubs(foreignStubs(fs)))
};
let virtualModules =
try(
Some(
JSON.member(conf, "virtualModules")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(FieldTypes.toString),
)
) {
| JSON.NullJSONValue () => None
| e => raise(e)
};
let implements =
try(
Some(
JSON.member(conf, "implements")
|> JSON.toValue
|> FieldTypes.toList
|> List.map(
FieldTypes.toString
<|> (
x => x.[0] == '.' ? sprintf("%s/%s/%s", rootName, dir, x) : x
)
<|> (
x =>
x.[0] == '@'
? if (findIndex(doubleKebabifyIfScoped(x), rootName)
!= 0) {
switch (x |> resolveDepNameToFindlib) {
| Ok(x) => x
| Error(msg) => raise(Failure(msg))
};
} else {
x |> doubleKebabifyIfScoped;
}
: x
)
<|> resolveRelativePath
<|> pathToOCamlLibName,
),
)
) {
| JSON.NullJSONValue () => None
| e => raise(e)
};
let wrapped =
try(
Some(
JSON.member(conf, "wrapped") |> JSON.toValue |> FieldTypes.toBool,
)
) {
| JSON.NullJSONValue () => None
| e => raise(e)
};
{
pkg_path,
common,
pkgType:
LibraryPackage(
Library.create(
name,
public,
namespace,
libraryModes,
ffi,
virtualModules,
implements,
wrapped,
),
),
};
};
};
let toDunePackages = (_prjPath, _rootName, pkg) => {
switch (pkg.pkgType) {
| LibraryPackage(l) => Library.toDuneStanza(pkg.common, l)
| ExecutablePackage(e) => Executable.toDuneStanza(pkg.common, e)
| TestPackage(e) => Test.toDuneStanza(pkg.common, e)
};
};
/* TODO: Figure better test setup */
/** DEPRECATED: Pesy is not supposed to be run in build env https://github.com/jchavarri/rebez/issues/4 **/;
/* let validateDuneFiles = (projectPath, pkgPath) => { */
/* let json = JSON.fromFile(pkgPath); */
/* let (rootName, pesyPackages) = toPesyConf(projectPath, json); */
/* let rootNameOpamFile = rootName ++ ".opam"; */
/* let dunePackages = toDunePackages(projectPath, rootName, pesyPackages); */
/* let staleDuneFiles = */
/* dunePackages */
/* |> List.map(dpkg => { */
/* let (path, updatedDuneFile) = dpkg; */
/* let currentDuneFilePath = Path.(path / "dune"); */
/* (updatedDuneFile, currentDuneFilePath); */
/* }) */
/* |> List.filter(dpkg => { */
/* let (updatedDuneFile, currentDuneFilePath) = dpkg; */
/* updatedDuneFile != DuneFile.ofFile(currentDuneFilePath); */
/* }) */
/* |> List.map(t => { */
/* let (_updatedDuneFile, currentDuneFilePath) = t; */
/* currentDuneFilePath; */
/* }); */
/* let foundAnOpamFile = ref(false); */
/* let dirForEachEntry = (f, dirname) => { */
/* let d = Unix.opendir(dirname); */
/* try( */
/* while (true) { */
/* f(Unix.readdir(d)); */
/* } */
/* ) { */
/* | End_of_file => Unix.closedir(d) */
/* }; */
/* }; */
/* let contains = (n, s) => */
/* try(Str.search_forward(Str.regexp(s), n, 0) != (-1)) { */
/* | Not_found => false */
/* }; */
/* let staleOpamFile = ref(None); */
/* dirForEachEntry( */
/* n => */
/* if (contains(n, ".opam") && ! foundAnOpamFile^) { */
/* foundAnOpamFile := true; */
/* if (n != rootNameOpamFile) { */
/* staleOpamFile := Some((n, rootNameOpamFile)); */
/* }; */
/* }, */
/* projectPath, */
/* ); */
/* let errors = */
/* List.map(x => StaleDuneFile(x), staleDuneFiles) */
/* @ ( */
/* switch (staleOpamFile^) { */
/* | Some(x) => [StaleOpamFile(x)] */
/* | None => [] */
/* } */
/* ); */
/* if (List.length(errors) != 0) { */
/* raise(BuildValidationFailures(errors)); */
/* }; */
/* Str.global_replace(Str.regexp(".opam"), "", rootNameOpamFile); */
/* }; */
/* "name" in root package.json */
let rootName = json =>
try(
doubleKebabifyIfScoped(
JSON.member(json, "name") |> JSON.toValue |> FieldTypes.toString,
)
) {
| JSON.NullJSONValue () => raise(ShouldNotBeNull("name"))
| x => raise(x)
};
let get = manifestFile => {
JSON.fromFile(manifestFile);
};
let pkgs = json => JSON.toKeyValuePairs(JSON.member(json, "buildDirs"));
================================================
FILE: lib/PesyModule.re
================================================
module Alias = {
type t = {
alias: string,
library: string,
internal: bool, // if an alias is for a local sub-library
originalNamespace: string,
exportedNamespace: string,
};
let create = (~alias, ~internal, ~library, ~originalNamespace, ~exportedNamespace) => {
{alias, library, internal, originalNamespace, exportedNamespace};
};
let isInternal = x => x.internal;
let getOriginalNamespace = x => x.originalNamespace;
let getLibrary = x => x.library;
let transform = (~f, x) => f(x);
let toReStatement = a => a.alias;
};
type t' = {
namespace: string,
dunePublicName: string,
aliases: list(Alias.t),
};
type t = option(t');
let getNamespace = pm => pm.namespace;
let getDunePublicName = pm => pm.dunePublicName;
let create:
(~namespace: string, ~dunePublicName: string, ~aliases: list(Alias.t)) => t =
(~namespace, ~dunePublicName, ~aliases) => {
List.length(aliases) == 0
? None : Some({namespace, dunePublicName, aliases});
};
let generateAliasModuleStanza = pesyModules =>
switch (pesyModules) {
| Some(x) =>
let pesyModulesReFile =
x.aliases |> List.map(Alias.toReStatement) |> String.concat("\n");
Some(
Stanza.createExpression([
Stanza.createAtom("rule"),
Stanza.createExpression([
Stanza.createAtom("with-stdout-to"),
Stanza.createAtom(Printf.sprintf("%s.re", x.namespace)),
Stanza.createExpression([
Stanza.createAtom("run"),
Stanza.createAtom("echo"),
Stanza.createAtom(Printf.sprintf("%s", pesyModulesReFile)),
]),
]),
]),
);
| None => None
};
let generateLibraryStanza = (preprocess, pesyModules) => {
let preprocessStanza =
switch (preprocess) {
| None => []
| Some(l) => [
Stanza.createExpression([
Stanza.createAtom("preprocess"),
Stanza.createExpression(List.map(f => Stanza.createAtom(f), l)),
]),
]
};
switch (pesyModules) {
| Some(x) =>
Some(
Stanza.createExpression([
Stanza.createAtom("library"),
Stanza.createExpression([
Stanza.createAtom("name"),
Stanza.createAtom(x.namespace),
]),
Stanza.createExpression([
Stanza.createAtom("modules"),
Stanza.createAtom(x.namespace),
]),
Stanza.createExpression([
Stanza.createAtom("libraries"),
...{
module SS = Set.Make(String);
x.aliases
|> List.map(alias => Alias.isInternal(alias) ? Alias.getOriginalNamespace(alias): Alias.getLibrary(alias))
|> SS.of_list
|> SS.elements
|> List.map(Alias.transform(~f=Stanza.createAtom));
},
]),
...preprocessStanza,
]),
)
| None => None
};
};
================================================
FILE: lib/Stanza.re
================================================
open Sexplib.Sexp;
type t = Sexplib.Sexp.t;
let createAtom = a => Atom(a);
let create = (stanza: string, expression) =>
List([Atom(stanza), expression]);
let createExpression = atoms => List(atoms);
let ofString = s =>
Sexplib.Parser.sexp(Sexplib.Lexer.main, Lexing.from_string(s));
let toSexp = x => x;
let ofSexp = x => x;
================================================
FILE: lib/Stanza.rei
================================================
type t;
let create: (string, t) => t;
let createAtom: string => t;
let createExpression: list(t) => t;
let toSexp: t => Sexplib.Sexp.t;
let ofSexp: Sexplib.Sexp.t => t;
let ofString: string => t;
================================================
FILE: lib/Stubs.re
================================================
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
type t =
| CNames(list(string))
| ForeignStubs(list(list((string, FieldTypes.t))));
let ofCNames = cns => CNames(cns);
let ofForeignStubs = fss => ForeignStubs(fss);
module ForeignStub = {
exception LanguageNotSupported(string);
module Language = {
type t =
| C
| CXX;
let ofString =
fun
| "c" => C
| "cxx" => CXX
| x => raise(LanguageNotSupported(x));
let toString =
fun
| C => "c"
| CXX => "cxx";
};
type t = {
language: Language.t,
names: option(list(string)),
flags: option(list(string)),
};
let ofFieldTypes = tfts => {
/* Note: here language is set C as default value it will be replaced by the
correct value in the below pattern match */
let fs = ref({language: Language.C, names: None, flags: None});
List.iter(
tft => {
switch (tft) {
| ("names", ft) =>
fs :=
{
...fs^,
names:
switch (FieldTypes.toList(ft)) {
| [] => None
| l => Some(List.map(FieldTypes.toString, l))
},
}
| ("flags", ft) =>
fs :=
{
...fs^,
flags:
switch (FieldTypes.toList(ft)) {
| [] => None
| l => Some(List.map(FieldTypes.toString, l))
},
}
| ("language", ft) =>
fs :=
{...fs^, language: Language.ofString(FieldTypes.toString(ft))}
| _ => ()
}
},
tfts,
);
fs^;
};
let toDuneStanza = fs => {
let stanzas = [];
stanzas
@ [
Stanza.createExpression([
Stanza.createAtom("language"),
Stanza.createAtom(Language.toString(fs.language)),
]),
]
@ (
switch (fs.names) {
| None
| Some([]) => [
Stanza.createExpression([
Stanza.createAtom("names"),
Stanza.createAtom(":standard"),
]),
]
| Some(xs) => [
Stanza.createExpression([
Stanza.createAtom("names"),
...List.map(Stanza.createAtom, xs),
]),
]
}
)
@ (
switch (fs.flags) {
| None
| Some([]) => [
Stanza.createExpression([
Stanza.createAtom("flags"),
Stanza.createAtom(":standard"),
]),
]
| Some(xs) => [
Stanza.createExpression([
Stanza.createAtom("flags"),
...List.map(Stanza.createAtom, xs),
]),
]
}
);
};
};
let cNamesD = cNamesP => [
Some(
Stanza.createExpression([
Stanza.createAtom("c_names"),
...List.map(Stanza.createAtom, cNamesP),
]),
),
];
let foreignStubsD = foreignStubsP =>
List.map(ForeignStub.ofFieldTypes, foreignStubsP)
|> List.map(fs =>
Some(
Stanza.createExpression([
Stanza.createAtom("foreign_stubs"),
...ForeignStub.toDuneStanza(fs),
]),
)
);
let toDuneStanza = stubsP =>
switch (stubsP) {
| Some(s) =>
switch (s) {
| CNames(cNamesP) => cNamesD(cNamesP)
| ForeignStubs(foreignStubsP) => foreignStubsD(foreignStubsP)
}
| None => [None]
};
================================================
FILE: lib/Stubs.rei
================================================
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
type t;
let ofCNames: list(string) => t;
let ofForeignStubs: list(list((string, FieldTypes.t))) => t;
let toDuneStanza: option(t) => list(option(Stanza.t));
================================================
FILE: lib/Test.re
================================================
open Printf;
module Utils = PesyEsyPesyUtils.Utils;
open Utils;
module Mode = {
exception InvalidCompilationMode(unit);
exception InvalidBinaryKind(unit);
module Compilation: {
type t;
let toString: t => string;
let ofString: string => t;
} = {
type t =
| Byte
| Native
| Best;
let toString =
fun
| Byte => "byte"
| Native => "native"
| Best => "best";
let ofString =
fun
| "byte" => Byte
| "native" => Native
| "best" => Best
| _ => raise(InvalidCompilationMode());
};
module BinaryKind: {
type t;
let toString: t => string;
let ofString: string => t;
} = {
type t =
| C
| Exe
| Object
| Shared_object;
let toString =
fun
| C => "c"
| Exe => "exe"
| Object => "object"
| Shared_object => "shared_object";
let ofString =
fun
| "c" => C
| "exe" => Exe
| "object" => Object
| "shared_object" => Shared_object
| _ => raise(InvalidBinaryKind());
};
type t = (Compilation.t, BinaryKind.t);
exception InvalidExecutableMode(string);
let ofList = parts =>
switch (parts) {
| [c, b] => (Compilation.ofString(c), BinaryKind.ofString(b))
| _ =>
raise(
InvalidExecutableMode(
sprintf(
"Invalid executable mode: expected of the form (<compilation mode>, <binary_kind>). Got %s",
List.fold_left((a, e) => sprintf("%s %s", a, e), "", parts),
),
),
)
};
let toList = m => {
let (c, b) = m;
[Compilation.toString(c), BinaryKind.toString(b)];
};
};
type t = {
main: string,
modes: option(Mode.t),
};
let create = (main, modes) => {main, modes};
let toDuneStanza = (common: Common.t, e) => {
/* let {name: pkgName, require, path} = common; */
let {main, modes: modesP} = e;
let (
libraries,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
includeSubdirs,
rawBuildConfig,
rawBuildConfigFooter,
pesyModulesLibrary,
pesyModulesAliasModuleGen,
) =
Common.toDuneStanzas(common);
let path = Common.getPath(common);
/* Pesy's main is Dune's name */
let name = Stanza.create("name", Stanza.createAtom(main));
let modules =
Stanza.createExpression([
Stanza.createAtom("modules"),
Stanza.createExpression(
[Stanza.createAtom(":standard")]
@ (
switch (Common.getPesyModules(common)) {
| Some(x) => [
Stanza.createAtom("\\"),
Stanza.createAtom(PesyModule.getNamespace(x)),
]
| None => []
}
),
),
]);
/* let public_name = */
/* Stanza.create("public_name", Stanza.createAtom(pkgName)); */
/* let libraries = */
/* switch (require) { */
/* | [] => None */
/* | libs => */
/* Some( */
/* Stanza.createExpression([ */
/* Stanza.createAtom("libraries"), */
/* ...List.map(r => Stanza.createAtom(r), libs), */
/* ]), */
/* ) */
/* }; */
let modesD =
switch (modesP) {
| None => None
| Some(m) =>
Some(
Stanza.createExpression([
Stanza.createAtom("modes"),
Stanza.createExpression(
m |> Mode.toList |> List.map(Stanza.createAtom),
),
]),
)
};
let mandatoryExpressions = [name, modules];
let optionalExpressions = [
libraries,
modesD,
flags,
ocamlcFlags,
ocamloptFlags,
jsooFlags,
preprocess,
pesyModulesLibrary,
];
let rawBuildConfig =
switch (rawBuildConfig) {
| Some(l) => l
| None => []
};
let rawBuildConfigFooter =
switch (rawBuildConfigFooter) {
| Some(l) => l
| None => []
};
let optionalRootStanzas =
rawBuildConfigFooter
@ (
switch (pesyModulesLibrary) {
| Some(s) => [s]
| None => []
}
)
@ (
switch (pesyModulesAliasModuleGen) {
| Some(s) => [s]
| None => []
}
)
@ (
switch (includeSubdirs) {
| Some(s) => [s]
| None => []
}
);
let executable =
Stanza.createExpression([
Stanza.createAtom("test"),
...mandatoryExpressions
@ filterNone(optionalExpressions)
@ rawBuildConfig,
]);
(path, [executable, ...optionalRootStanzas]);
};
================================================
FILE: lib/Test.rei
================================================
type t;
module Mode: {
type t;
let ofList: list(string) => t;
let toList: t => list(string);
};
let create: (string, option(Mode.t)) => t;
let toDuneStanza: (Common.t, t) => (string, list(Stanza.t));
================================================
FILE: lib/dune
================================================
(library
(name PesyEsyPesyLib)
(public_name pesy--esy-pesy.lib)
(modules (:standard))
(libraries str sexplib findlib pesy--esy-pesy.utils
pesy--esy-pesy.errors))
(rule
(targets Lexer.ml)
(deps Lexer.mll)
(action (run %{bin:ocamllex} -q -o %{targets} %{deps})))
(rule
(targets Parser.ml Parser.mli)
(deps Parser.mly)
(action (run %{bin:ocamlyacc} %{deps})))
================================================
FILE: notes/benchmarking.md
================================================
The results of benchmarking a sample "generateEverything.sh" script:
DELTAms 5
DELTAms 11 - detect shell
DELTAms 6 - setup variables
DELTAms 10 - define functions
DELTAms 11 - define build variables
DELTAms 8 - print directories
DELTAms 9 - check lib dir
DELTAms 30 - create lib build file
DELTAms 12 - verify lib build existing contents
DELTAms 12 - setup bin vars
DELTAms 8 - check bin main mod name
DELTAms 10 - check bin main module exists
DELTAms 28 - read existing dune bin contents
DELTAms 23 - create bin dune contents
DELTAms 5 - check bin against required bin
DELTAms 8 - check root dune exists
DELTAms 4 - check opam file exists
DELTAms -989 - check root dune-project file exists
DELTAms 82 - perform actual build
Build Succeeded! To test a binary:
esy x ChalkConsole.exe
DELTAms 5 - check build failure
DELTAms 8 - end
DELTAms 5 - end
For the following manually instrumented profiling of that script file (requires
gdate be installed). I've since improved the detect shell step (avoided using
grep subprocess).
All that really matters is the time it takes to run the genEverything script.
It doesn't matter how long it takes to generate the genEverything script. The
biggest impacts for optimization of running genEverything would be:
1. Omitting fields in the generated dune files that aren't present in the
package.json config.
Note that each measurement has an additional 5ms overhead apparently (so
subtract about 5ms for each measurement to get the real time).
Some smaller improvements:
- md5 takes about 7ms to execute which blocks running of genEverything.sh, but
just checking for if md5 program exists is time consuming.
If we have a previous build hash, we don't need to check
Run it in a project by doing `esy b ./_build/genEverything.sh`
```sh
#!/bin/bash
set -e
set -u
BOLD=`tput bold` || BOLD='' # Select bold mode
BLACK=`tput setaf 0` || BLACK=''
RED=`tput setaf 1` || RED=''
GREEN=`tput setaf 2` || GREEN=''
YELLOW=`tput setaf 3` || YELLOW=''
RESET=`tput sgr0` || RESET=''
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
# Some operation:
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS}"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
MODE="update"
[[ $SHELL =~ "noprofile" ]] && MODE="build"
# Some operation:
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - detect shell"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
LAST_EXE_NAME=""
NOTIFIED_USER="false"
BUILD_STALE_PROBLEM="false"
DEFAULT_MAIN_MODULE_NAME="Index"
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - setup variables"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
function notifyUser() {
if [ "${NOTIFIED_USER}" == "false" ]; then
echo ""
if [ "${MODE}" == "build" ]; then
printf " %sAlmost there!%s %sWe just need to prepare a couple of files:%s\\n\\n" "${YELLOW}${BOLD}" "${RESET}" "${BOLD}" "${RESET}"
else
printf " %sPreparing for build:%s\\n\\n" "${YELLOW}${BOLD}" "${RESET}"
fi
NOTIFIED_USER="true"
else
# do nothing
true
fi
}
function printDirectory() {
if [ "${MODE}" != "build" ]; then
DIR=$1
NAME=$2
NAMESPACE=$3
REQUIRE=$4
IS_LAST=$5
printf "│\\n"
PREFIX=""
if [[ "$IS_LAST" == "last" ]]; then
printf "└─%s/\\n" "$DIR"
PREFIX=" "
else
printf "├─%s/\\n" "$DIR"
PREFIX="│ "
fi
printf "%s%s\\n" "$PREFIX" "$NAME"
printf "%s%s\\n" "$PREFIX" "$NAMESPACE"
if [ -z "$REQUIRE" ]; then
true
else
if [ "$REQUIRE" != " " ]; then
printf "%s%s\\n" "$PREFIX" "$REQUIRE"
fi
fi
fi
}
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - define functions"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
PACKAGE_NAME="chalk-console"
PACKAGE_NAME_UPPER_CAMEL="ChalkConsole"
NAMESPACE="ChalkConsole"
PUBLIC_LIB_NAME="chalk-console.lib"
#Default Namespace
lib_NAMESPACE="ChalkConsole"
#Default Requires
lib_REQUIRE=""
#Default Flags
lib_FLAGS=""
lib_OCAMLC_FLAGS=""
lib_OCAMLOPT_FLAGS=""
lib_PREPROCESS=""
lib_C_NAMES=""
#Default Requires
bin_REQUIRE=""
#Default Flags
bin_FLAGS=""
bin_OCAMLC_FLAGS=""
bin_OCAMLOPT_FLAGS=""
bin_PREPROCESS=""
bin_C_NAMES=""
lib_NAMESPACE=""ChalkConsole""
bin_MAIN_MODULE=""TestChalkConsole""
lib_REQUIRE=" console.lib chalk.lib "
bin_REQUIRE=" console.lib chalk.lib chalk-console.lib "
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - define build variables"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
printDirectory "lib" "library name: chalk-console.lib" "namespace: $lib_NAMESPACE" "require: $lib_REQUIRE" not-last
printDirectory "bin" "name: ChalkConsole.exe" "main: ${bin_MAIN_MODULE:-$DEFAULT_MAIN_MODULE_NAME}" "require:$bin_REQUIRE" last
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - print directories"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
# Perform validation:
LIB_DIR="${cur__root}/lib"
LIB_DUNE_FILE="${LIB_DIR}/dune"
# TODO: Error if there are multiple libraries all using the default namespace.
if [ -d "${LIB_DIR}" ]; then
true
else
BUILD_STALE_PROBLEM="true"
notifyUser
if [ "${MODE}" == "build" ]; then
printf " □ Your project is missing the lib directory described in package.json buildDirs\\n"
else
printf " %s☒%s Your project is missing the lib directory described in package.json buildDirs\\n" "${BOLD}${GREEN}" "${RESET}"
mkdir -p "${LIB_DIR}"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check lib dir"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
LIB_DUNE_CONTENTS=""
LIB_DUNE_EXISTING_CONTENTS=""
if [ -f "${LIB_DUNE_FILE}" ]; then
LIB_DUNE_EXISTING_CONTENTS=$(<"${LIB_DUNE_FILE}")
fi
LIB_DUNE_CONTENTS="(library"
LIB_DUNE_CONTENTS=$(printf "%s\\n %s" "${LIB_DUNE_CONTENTS}" " ; !!!! This dune file is generated from the package.json file. Do NOT modify by hand.")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s" "${LIB_DUNE_CONTENTS}" " ; !!!! Instead, edit the package.json and then rerun 'esy pesy' at the project root.")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s" "${LIB_DUNE_CONTENTS}" " ; The namespace other code see this as")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s" "${LIB_DUNE_CONTENTS}" " (name ${lib_NAMESPACE})")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s" "${LIB_DUNE_CONTENTS}" " (public_name chalk-console.lib)")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${LIB_DUNE_CONTENTS}" " (libraries ${lib_REQUIRE})")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${LIB_DUNE_CONTENTS}" " (c_names ${lib_C_NAMES}) ; From package.json cNames field")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${LIB_DUNE_CONTENTS}" " (flags (:standard ${lib_FLAGS})) ; From package.json flags field")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${LIB_DUNE_CONTENTS}" " (ocamlc_flags (:standard ${lib_OCAMLC_FLAGS})) ; From package.json ocamlcFlags field")
LIB_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${LIB_DUNE_CONTENTS}" " (ocamlopt_flags (:standard ${lib_OCAMLOPT_FLAGS}))) ; From package.json ocamloptFlags")
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - create lib build file"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ "${LIB_DUNE_EXISTING_CONTENTS}" == "${LIB_DUNE_CONTENTS}" ]; then
true
else
notifyUser
BUILD_STALE_PROBLEM="true"
if [ "${MODE}" == "build" ]; then
printf " □ Update lib/dune build config\\n"
else
printf " %s☒%s Update lib/dune build config\\n" "${BOLD}${GREEN}" "${RESET}"
printf "%s" "$LIB_DUNE_CONTENTS" > "${LIB_DUNE_FILE}"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - verify lib build existing contents"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
BIN_DIR="${cur__root}/bin"
BIN_DUNE_FILE="${BIN_DIR}/dune"
# FOR BINARY IN DIRECTORY bin
bin_MAIN_MODULE="${bin_MAIN_MODULE:-$DEFAULT_MAIN_MODULE_NAME}"
bin_MAIN_MODULE_NAME="${bin_MAIN_MODULE%%.*}"
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - setup bin vars"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
# https://stackoverflow.com/a/965072
if [ "$bin_MAIN_MODULE_NAME"=="$bin_MAIN_MODULE" ]; then
# If they did not specify an extension, we'll assume it is .re
bin_MAIN_MODULE_FILENAME="${bin_MAIN_MODULE}.re"
else
bin_MAIN_MODULE_FILENAME="${bin_MAIN_MODULE}"
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check bin main mod name"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ -f "${BIN_DIR}/${bin_MAIN_MODULE_FILENAME}" ]; then
true
else
BUILD_STALE_PROBLEM="true"
notifyUser
echo ""
if [ "${MODE}" == "build" ]; then
printf " □ Generate %s main module\\n" "${bin_MAIN_MODULE_FILENAME}"
else
printf " %s☒%s Generate %s main module\\n" "${BOLD}${GREEN}" "${RESET}" "${bin_MAIN_MODULE_FILENAME}"
mkdir -p "${BIN_DIR}"
printf "print_endline(\"Hello!\");" > "${BIN_DIR}/${bin_MAIN_MODULE_FILENAME}"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check bin main module exists"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ -d "${BIN_DIR}" ]; then
LAST_EXE_NAME="ChalkConsole.exe"
BIN_DUNE_EXISTING_CONTENTS=""
if [ -f "${BIN_DUNE_FILE}" ]; then
BIN_DUNE_EXISTING_CONTENTS=$(<"${BIN_DUNE_FILE}")
else
BIN_DUNE_EXISTING_CONTENTS=""
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - read existing dune bin contents"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
BIN_DUNE_CONTENTS="(executable"
BIN_DUNE_CONTENTS=$(printf "%s\\n %s" "${BIN_DUNE_CONTENTS}" " ; !!!! This dune file is generated from the package.json file. Do NOT modify by hand.")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s" "${BIN_DUNE_CONTENTS}" " ; !!!! Instead, edit the package.json and then rerun 'esy pesy' at the project root.")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s" "${BIN_DUNE_CONTENTS}" " ; The entrypoint module")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s" "${BIN_DUNE_CONTENTS}" " (name ${bin_MAIN_MODULE_NAME}) ; From package.json main field")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s" "${BIN_DUNE_CONTENTS}" " (public_name ChalkConsole.exe) ; From package.json name field")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${BIN_DUNE_CONTENTS}" " (libraries ${bin_REQUIRE}) ; From package.json require field (array of strings)")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${BIN_DUNE_CONTENTS}" " (flags (:standard ${bin_FLAGS})) ; From package.json flags field")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${BIN_DUNE_CONTENTS}" " (ocamlc_flags (:standard ${bin_OCAMLC_FLAGS})) ; From package.json ocamlcFlags field")
BIN_DUNE_CONTENTS=$(printf "%s\\n %s\\n" "${BIN_DUNE_CONTENTS}" " (ocamlopt_flags (:standard ${bin_OCAMLOPT_FLAGS}))) ; From package.json ocamloptFlags field")
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - create bin dune contents"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ "${BIN_DUNE_EXISTING_CONTENTS}" == "${BIN_DUNE_CONTENTS}" ]; then
true
else
notifyUser
BUILD_STALE_PROBLEM="true"
if [ "${MODE}" == "build" ]; then
printf " □ Update bin/dune build config\\n"
else
printf " %s☒%s Update bin/dune build config\\n" "${BOLD}${GREEN}" "${RESET}"
printf "%s" "${BIN_DUNE_CONTENTS}" > "${BIN_DUNE_FILE}"
mkdir -p "${BIN_DIR}"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check bin against required bin"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
else
BUILD_STALE_PROBLEM="true"
notifyUser
if [ "${MODE}" == "build" ]; then
printf " □ Generate missing the bin directory described in package.json buildDirs\\n"
else
printf " %s☒%s Generate missing the bin directory described in package.json buildDirs\\n" "${BOLD}${GREEN}" "${RESET}"
mkdir -p "${BIN_DIR}"
fi
fi
if [ -f "${cur__root}/dune" ]; then
true
else
BUILD_STALE_PROBLEM="true"
notifyUser
if [ "${MODE}" == "build" ]; then
printf " □ Update ./dune to ignore node_modules\\n"
else
printf " %s☒%s Update ./dune to ignore node_modules\\n" "${BOLD}${GREEN}" "${RESET}"
printf "(ignored_subdirs (node_modules))" > "${cur__root}/dune"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check root dune exists"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ -f "${cur__root}/${PACKAGE_NAME}.opam" ]; then
true
else
BUILD_STALE_PROBLEM="true"
notifyUser
if [ "${MODE}" == "build" ]; then
printf " □ Add %s\\n" "${PACKAGE_NAME}.opam"
else
printf " %s☒%s Add %s\\n" "${BOLD}${GREEN}" "${RESET}" "${PACKAGE_NAME}.opam"
touch "${cur__root}/${PACKAGE_NAME}.opam"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check opam file exists"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ -f "${cur__root}/dune-project" ]; then
true
else
BUILD_STALE_PROBLEM="true"
notifyUser
if [ "${MODE}" == "build" ]; then
printf " □ Add a ./dune-project\\n"
else
printf " %s☒%s Add a ./dune-project\\n" "${BOLD}${GREEN}" "${RESET}"
printf "(lang dune 1.0)\\n (name %s)" "${PACKAGE_NAME}" > "${cur__root}/dune-project"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check root dune-project file exists"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ "${MODE}" == "build" ]; then
if [ "${BUILD_STALE_PROBLEM}" == "true" ]; then
printf "\\n %sTo perform those updates and build run:%s\n\n" "${BOLD}${YELLOW}" "${RESET}"
printf " esy pesy\\n\\n\\n\\n"
exit 1
else
# If you list a refmterr as a dev dependency, we'll use it!
BUILD_FAILED=""
if hash refmterr 2>/dev/null; then
refmterr dune build -p "${PACKAGE_NAME}" || BUILD_FAILED="true"
else
dune build -p "${PACKAGE_NAME}" || BUILD_FAILED="true"
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - perform actual build"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
if [ -z "$BUILD_FAILED" ]; then
printf "\\n%s Build Succeeded!%s " "${BOLD}${GREEN}" "${RESET}"
if [ -z "$LAST_EXE_NAME" ]; then
printf "\\n\\n"
true
else
# If we built an EXE
printf "%sTo test a binary:%s\\n\\n" "${BOLD}" "${RESET}"
printf " esy x %s\\n\\n\\n" "${LAST_EXE_NAME}"
fi
true
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - check build failure"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
else
exit 1
fi
fi
else
# In update mode.
if [ "${BUILD_STALE_PROBLEM}" == "true" ]; then
printf "\\n %sUpdated!%s %sNow run:%s\\n\\n" "${BOLD}${GREEN}" "${RESET}" "${BOLD}" "${RESET}"
printf " esy build\\n\\n\\n"
else
printf "\\n %sAlready up to date!%s %sNow run:%s\\n\\n" "${BOLD}${GREEN}" "${RESET}" "${BOLD}" "${RESET}"
printf " esy build\\n\\n\\n"
fi
fi
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - end"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
NOW="1$(/opt/homebrew/bin/gdate +%N)"
DELTA_MS=$(( $(($NOW-$PREV_TIME)) / 1000000))
echo "DELTAms ${DELTA_MS} - end"
PREV_TIME="1$(/opt/homebrew/bin/gdate +%N)"
```
Although we quickly call into genEverything if the package.json hasn't changed,
there appears to be an 80ms overhead in pesy until we get to that point.
DELTAms 4 - baseline margin of error amount
DELTAms 5 - check help
DELTAms 12 - check env
DELTAms 13 - check create
DELTAms 7 - record pesy dir
super-project-now@0.0.0
DELTAms 6 - output title
DELTAms 7 - mkdir cur__target_dir
DELTAms 7 - check if md5 exists
DELTAms 12 - compute md5
DELTAms 14 - get previous md5
================================================
FILE: notes/checksum-verification.md
================================================
# Checksum verification
As we create the build artifacts to publish to NPM, we also generate the SHA1 hash
of the `.tgz` file created by `npm pack`, in a manner similar to how npm does.
This way, you can verify that the package published to NPM is infact the same
set of binaries that were built on CI.
You can verify this by following this simple steps.
1. Head over to CI logs as per the release version
a. [Pre-beta](https://dev.azure.com/pesy/pesy/_build/results?buildId=103)
2) Navigate to the `Release Job` section

3. Look for 'Calculating sha1'

4. Verify its the same as the one in `npm info pesy`. Of course, ensure that the version
you see in `npm info pesy` is the same the one in the logs.

You can also download the package straight from the CI and check if it is the
same as the one on NPM.
1. In the same logs, on the top right you would see a blue button labeled `Artifacts`

2. In the sub menu drawn up by `Artifacts`, click on `Release`. This is the job where we
collect are platform binaries and package them for NPM. You'll only find platform-specfic
binaries in the other jobs.

3. A file explorer like interface opens on clicking `Release` as explained in the previous step.
Click on the `Release` folder - the only option. We run `npm pack` in this directory structure.

4. `pesy-<version>.tgz` is the tar file that published to npm. You can uncompress and inspect its
contents, or check its SHA1 integrity and ensure it's the same as the one on NPM

5. You might have to tap on the file once to show the kebab menu.

================================================
FILE: notes/compiler-support.org
================================================
* Compiler support
This is an attempt to document compiler support pesy provides.
** Terminologies
As defined in [[https://esy.sh/docs/en/environment.html][Esy's documentation]]
- Regular dependencies
#+begin_quote
Regular dependencies are dependencies which are needed at
runtime. They are listed in "dependencies" key of the
package.json.
#+end_quote
- Development dependencies
#+begin_quote
are dependencies which are needed only during development. They
are listed in "devDependencies" key of the package.json. Examples:
@opam/merlin, @opam/ocamlformat and so on.
#+end_quote
** Regular dependencies must be atleast 4.4.2004 because of ocamlbuild and base
Base becase (and @pesy/esy-pesy -> @opam/sexplib -> @opam/parsexp -> @opam/base -> ocaml@>=4.4.2)
** 4.4 cannot be supported - possibly because of a solver bug in esy
#+begin_example
[ERROR] It appears that the num library was previously installed to your system
compiler's lib directory, probably by a faulty opam package.
You will need to remove arith_flags.*, arith_status.*, big_int.*,
int_misc.*, nat.*, num.*, ratio.*, nums.*, libnums.* and
stublibs/dllnums.* from /Users/manas/.esy/3___________________________________________________________________/i/ocaml-4.4.2004-52c21c80/lib/ocaml.
make: *** [findlib-install] Error 1
error: command failed: 'make' 'LIBDIR=/Users/manas/.esy/3___________________________________________________________________/s/opam__s__num-opam__c__1.3-c60c3f0c/lib' 'findlib-install' (exited with 2)
esy-build-package: exiting with errors above...
#+end_example
esy seeems to fetch num1.3 for compiler 4.4 when it's supposed to
fetch num.0. Reported [[https://github.com/esy/esy/issues/1013][here]]
** Development dependencies must be altleast is 4.6 because of refmterr
It uses string_of_bool_opt available post 4.05 only
** When dependencies: "ocaml": "4.4.2004 - 4.8" and devDependencies: "ocaml": "4.6.10 - 4.8"
#+begin_example
Conflicting constraints:
@pesy/esy-pesy -> @reason-native/pastel -> @opam/re -> @opam/seq ->
ocaml@<4.7.0
@pesy/esy-pesy -> ocaml@=4.8.1000
#+end_example
ie if consuming packages want to use ocaml > 4.7, latest seq
package (which is basically empty since the compiler supports
natively) must be used. If consuming package is < 4.7, c-cube
fallback package must be used. Esy's resolutions field cannot be
used to vendor seq package - resolutions package can only be
used to pin one package at a time.
** 4.9 support
#+begin_example
Conflicting constraints:
@pesy/esy-pesy -> @opam/sexplib -> @opam/parsexp -> @opam/base -> ocaml@>=4.4.2 && ocaml@<4.9.0
@pesy/esy-pesy -> ocaml@=4.9.0
#+end_example
Upgrading base requires latest dune. So 4.9 needs newer
dune. TODO
================================================
FILE: notes/e2e.org
================================================
* Setup
Recently [[https://github.com/esy/pesy/pull/105][Windows CI started failing]] due disk space issue. This could
be due to the heavy e2e setup - multiple test projects were probably
installing too many dependencies
As a work around, we're writing an in place e2e setup that actually
reflects developer workflow - it would bootstrap a new project and
edit it in place.
This time around, we aim to test error cases too
* Issues with mdx attempt
[[https://github.com/realworldocaml/mdx][MDX]] was the ideal tool for this. But unfortunately, we're testing a
cli tool that call =esy=. Using =MDX= meant we had to use esy to run
the tests and running a cli tool that spawns esy within esy can be
challenging. For instance, we had tried setting up the following
#+BEGIN_SRC lisp
(alias
(name runtest)
(deps (:t test.t) (source_tree ./files))
(action
(progn
(run pwd)
(run echo "%{project_root}/npm-cli/pesy")
(setenv ESY__PROJECT "."
(setenv OCAMLRUNPARAM b
(setenv TEST_PROJECT_DIR "."
(setenv
PESY %{bin:pesy}
(run %{bin:mdx} test --syntax=cram %{t})))))
(diff? %{t} %{t}.corrected))))
#+END_SRC
and
#+BEGIN_SRC sh
The simplest case: running pesy in an empty directory
$ mkdir -p $TEST_PROJECT_DIR
$ cp -R $PWD/files/package.json $TEST_PROJECT_DIR
$ cd $TEST_PROJECT_DIR; $PESY
#+END_SRC
It needs =MDX= which is available only in the sandbox and =esy-pesy=
runs =esy status= which assumes the root package.json is the root
not the test-project folder inside the =_build=. Writing to external $TMPDIR
================================================
FILE: notes/release.org
================================================
* Release process
The release process is mostly manual right now and works like this.
** @pesy/esy-pesy
1. =git ls-files | xargs sed -i 's/dev.<num>/alpha.<num/g'=
2. Commit and push
3. Tag the commit for just =@pesy/esy-pesy=
4. =npm publish= (watch the tag)
=@pesy/esy-pesy= is preferably released first so that =pesy-reason-template= can be updated with it
** Update pesy-reason-template with the newly released @pesy/esy-pesy
The diff usually looks like this
#+begin_src diff
- "@pesy/esy-pesy": "<old version>"
+ "@pesy/esy-pesy": "<new version>"
#+end_src
** pesy
Same as =@pesy/esy-pesy=
1. =git ls-files | xargs sed -i 's/dev.<num>/alpha.<num/g'=
2. Commit and push
3. Tag the commit for just =@pesy/esy-pesy=
4. =npm publish= (watch the tag)
** Back to dev version for further development
1. Revert the two commits that bump to release versions
2. =git ls-files | xargs sed -i 's/dev.<num>/dev.<num + 1>/g'=
3. Commit and push
================================================
FILE: npm-cli/.gitignore
================================================
.DS_Store
.merlin
.bsb.lock
npm-debug.log
/lib/bs/
/node_modules/
pesy.bundle.js
pesy-*.tgz
*.map
================================================
FILE: npm-cli/.npmignore
================================================
src/*
================================================
FILE: npm-cl
gitextract_x9up97r8/
├── .ci/
│ ├── build-platform.yml
│ ├── checksum.js
│ ├── cross-release.yml
│ ├── esy-build-steps.yml
│ ├── opam-build-steps.yml
│ ├── pipelines-release.js
│ ├── release-platform-setup.yml
│ ├── release-postinstall.js
│ └── utils/
│ ├── create-docs.yml
│ ├── publish-build-cache.yml
│ ├── restore-build-cache.yml
│ ├── use-cache-esy.yml
│ ├── use-cache-npm.yml
│ ├── use-cache-yarn.yml
│ ├── use-esy.yml
│ └── use-node.yml
├── .gitignore
├── .npmignore
├── CODE_OF_CONDUCT.md
├── LICENSE
├── ORIGIN.md
├── PesyE2E.opam
├── README.html
├── azure-pipelines.yml
├── bin/
│ ├── Pesy.re
│ └── dune
├── dune
├── dune-project
├── e2e-tests/
│ ├── .TestPesyConfigure.re.swp
│ ├── .gitignore
│ ├── Rimraf.re
│ ├── Runner.re
│ ├── Utils.re
│ ├── dune
│ └── pending-tests.re
├── errors/
│ ├── Errors.re
│ └── dune
├── lib/
│ ├── Common.re
│ ├── Common.rei
│ ├── DuneFile.re
│ ├── DuneFile.rei
│ ├── DuneProject.re
│ ├── DuneProject.rei
│ ├── EsyCommand.re
│ ├── Executable.re
│ ├── Executable.rei
│ ├── ImportsParser.re
│ ├── Lexer.mll
│ ├── Lib.re
│ ├── Library.re
│ ├── Library.rei
│ ├── Mode.re
│ ├── Parser.mly
│ ├── PesyConf.re
│ ├── PesyModule.re
│ ├── Stanza.re
│ ├── Stanza.rei
│ ├── Stubs.re
│ ├── Stubs.rei
│ ├── Test.re
│ ├── Test.rei
│ └── dune
├── notes/
│ ├── benchmarking.md
│ ├── checksum-verification.md
│ ├── compiler-support.org
│ ├── e2e.org
│ └── release.org
├── npm-cli/
│ ├── .gitignore
│ ├── .npmignore
│ ├── .vscode/
│ │ └── tasks.json
│ ├── README.md
│ ├── __tests__/
│ │ └── utils_test.re
│ ├── bsconfig.json
│ ├── esy.json
│ ├── package.json
│ ├── pesy
│ ├── pesy.bundle.js.minisig
│ ├── pesy.js
│ ├── rollup.config.js
│ ├── scripts/
│ │ └── vendor-template.js
│ ├── src/
│ │ ├── AzurePipelines.re
│ │ ├── Bindings.re
│ │ ├── Bootstrapper.re
│ │ ├── Bootstrapper.rei
│ │ ├── Cmd.re
│ │ ├── DefaultTemplate.re
│ │ ├── Dir.re
│ │ ├── Dir.rei
│ │ ├── Esy.re
│ │ ├── Esy.rei
│ │ ├── EsyLock.re
│ │ ├── EsyLock.rei
│ │ ├── EsyStatus.re
│ │ ├── EsyStatus.rei
│ │ ├── Path.re
│ │ ├── Path.rei
│ │ ├── Pesy.re
│ │ ├── Pesy.rei
│ │ ├── PesyConfig.re
│ │ ├── PesyConfig.rei
│ │ ├── Project.re
│ │ ├── Project.rei
│ │ ├── Readline.re
│ │ ├── Result.re
│ │ ├── ResultPromise.re
│ │ ├── ResultPromise.rei
│ │ ├── Spinner.re
│ │ ├── Spinner.rei
│ │ ├── Template.re
│ │ ├── Template.rei
│ │ ├── Utils.re
│ │ └── Warmup.re
│ ├── stubs/
│ │ ├── crypto.js
│ │ ├── fs.js
│ │ └── resolve.js
│ ├── templates/
│ │ ├── .gitignore
│ │ ├── .npmignore
│ │ ├── ci/
│ │ │ ├── .ci/
│ │ │ │ ├── build-docker.yml
│ │ │ │ ├── build-platform.yml
│ │ │ │ ├── cross-release.yml
│ │ │ │ ├── esy-build-steps.yml
│ │ │ │ ├── opam-build-steps.yml
│ │ │ │ ├── pipelines-release.js
│ │ │ │ ├── release-platform-setup.yml
│ │ │ │ ├── release-postinstall.js
│ │ │ │ └── utils/
│ │ │ │ ├── create-docs.yml
│ │ │ │ ├── publish-build-cache.yml
│ │ │ │ ├── publish-sources.yml
│ │ │ │ ├── restore-build-cache.yml
│ │ │ │ ├── use-cache-esy.yml
│ │ │ │ ├── use-cache-yarn.yml
│ │ │ │ ├── use-esy.yml
│ │ │ │ └── use-node.yml
│ │ │ └── azure-pipelines-template.yml
│ │ └── docker/
│ │ └── docker/
│ │ ├── DevImage.Dockerfile
│ │ └── ProdImage.Dockerfile
│ └── v0.4.4/
│ ├── LICENSE
│ ├── ORIGIN.md
│ ├── README.md
│ ├── azure-ci-template/
│ │ ├── azure-pipelines.yml
│ │ ├── esy-build-steps.template.yml
│ │ ├── publish-build-cache.yml
│ │ └── restore-build-cache.yml
│ ├── esy-peasy
│ ├── notes/
│ │ └── benchmarking.md
│ ├── pesy
│ ├── pesy-JSON.sh
│ ├── pesy-README.template.md
│ ├── pesy-create.sh
│ ├── pesy-footer.template.sh
│ ├── pesy-genBin.template.sh
│ ├── pesy-genLib.template.sh
│ ├── pesy-gitignore.template
│ ├── pesy-header.sh
│ ├── pesy-name-utils.sh
│ └── pesy-package.template.json
├── package.json
├── pesy--esy-pesy.opam
├── scripts/
│ ├── bootstrap.sh
│ ├── run.bat
│ ├── run.sh
│ ├── simulate-latest.js
│ └── verdaccio.yaml
├── site/
│ ├── ORIGINS.md
│ ├── Reload.js
│ ├── fonts/
│ │ ├── CodingFont.css
│ │ ├── LICENSE-Fira
│ │ ├── LICENSE-Roboto
│ │ └── WordFont.css
│ ├── index.dev.html
│ ├── package.json
│ └── theme-white/
│ ├── theme.js
│ └── theme.styl.html
├── unit-tests/
│ └── runner/
│ ├── Lib.re
│ ├── RunUnitTests.re
│ ├── Utils.re
│ └── dune
└── utils/
├── FieldTypes.re
├── JSON.re
├── JSON.rei
├── Utils.re
└── dune
SYMBOL INDEX (29 symbols across 6 files)
FILE: .ci/release-postinstall.js
function copyRecursive (line 26) | function copyRecursive(srcDir, dstDir) {
function arch (line 62) | function arch() {
function copyFileSync (line 117) | function copyFileSync(sourcePath, destPath) {
FILE: npm-cli/stubs/crypto.js
function sha256 (line 4) | function sha256(data, secret) {
function shaFile (line 8) | function shaFile(path, algo) {
function sha1File (line 26) | function sha1File(path) {
function sha256File (line 30) | function sha256File(path) {
FILE: npm-cli/stubs/resolve.js
function run (line 3) | function run(urlStr, callback) {
FILE: npm-cli/templates/ci/.ci/release-postinstall.js
function copyRecursive (line 26) | function copyRecursive(srcDir, dstDir) {
function arch (line 62) | function arch() {
function copyFileSync (line 117) | function copyFileSync(sourcePath, destPath) {
FILE: site/Reload.js
function detectDocOrStyleIfNotNodeScript (line 43) | function detectDocOrStyleIfNotNodeScript() {
function loadData (line 245) | function loadData(locations, response, callback) {
function loadData (line 342) | function loadData(locations, response, callback) {
function mkdir_p (line 564) | function mkdir_p(level) {
function process (line 678) | function process(node, $parent) {
function checkDone (line 780) | function checkDone() {
function getTextNodesIn (line 874) | function getTextNodesIn(el) {
function quotify (line 882) | function quotify(a) {
function e (line 902) | function e(e){this.tokens=[];this.tokens.links={};this.options=e||a.defa...
function s (line 902) | function s(t,e){this.options=e||a.defaults;this.links=t;this.rules=n.nor...
function i (line 902) | function i(t){this.tokens=[];this.token=null;this.options=t||a.defaults}
function r (line 902) | function r(t,e){return t.replace(!e?/&(?!#?\w+;)/g:/&/g,"&").replace...
function l (line 902) | function l(t,e){t=t.source;e=e||"";return function n(s,i){if(!s)return n...
function o (line 902) | function o(){}
function h (line 902) | function h(t){var e=1,n,s;for(;e<arguments.length;e++){n=arguments[e];fo...
function a (line 902) | function a(t,n,s){if(s||typeof n==="function"){if(!s){s=n;n=null}if(n)n=...
FILE: site/theme-white/theme.js
function onOneImageLoaded (line 82) | function onOneImageLoaded(loadedEl) {
function onClick (line 321) | function onClick(e) {
Condensed preview — 182 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (1,188K chars).
[
{
"path": ".ci/build-platform.yml",
"chars": 3990,
"preview": "parameters:\n platform: \"macOS\"\n vmImage: \"macOS-latest\"\n\njobs:\n - job: ${{ parameters.platform }}\n pool:\n vmI"
},
{
"path": ".ci/checksum.js",
"chars": 528,
"preview": "let crypto = require(\"crypto\"),\n path = require(\"path\"),\n fs = require(\"fs\");\n\nlet algorithm = \"sha1\",\n shasum = cryp"
},
{
"path": ".ci/cross-release.yml",
"chars": 1072,
"preview": "steps:\n - script: echo \"No release steps. They are disabled\"\n displayName: \"[Disabled] Release steps\"\n # - template"
},
{
"path": ".ci/esy-build-steps.yml",
"chars": 1856,
"preview": "# Cross-platform set of build steps for building esy projects\n\nsteps:\n - template: utils/use-node.yml\n - template: uti"
},
{
"path": ".ci/opam-build-steps.yml",
"chars": 1348,
"preview": "# Shared steps for building OPAM projects\n\nsteps:\n - task: NodeTool@0\n inputs:\n versionSpec: '8.9'\n - script: "
},
{
"path": ".ci/pipelines-release.js",
"chars": 2879,
"preview": "const fs = require(\"fs\");\nconst path = require(\"path\");\n\nconsole.log(\"Creating package.json\");\n\n// From the project root"
},
{
"path": ".ci/release-platform-setup.yml",
"chars": 546,
"preview": "parameters:\n platform: \"macOS\"\n folder: \"platform-darwin\"\n\nsteps:\n - task: DownloadBuildArtifacts@0\n displayName: "
},
{
"path": ".ci/release-postinstall.js",
"chars": 4693,
"preview": "/**\n * release-postinstall.js\n *\n * XXX: We want to keep this script installable at least with node 4.x.\n *\n * This scri"
},
{
"path": ".ci/utils/create-docs.yml",
"chars": 592,
"preview": "# These steps are only run on Linux\nsteps:\n - script: \"esy doc\"\n displayName: \"Build docs\"\n condition: and(succee"
},
{
"path": ".ci/utils/publish-build-cache.yml",
"chars": 1768,
"preview": "# Steps for publishing project cache\n\nsteps:\n - bash: 'mkdir -p $(Build.StagingDirectory)'\n condition: and(succeeded"
},
{
"path": ".ci/utils/restore-build-cache.yml",
"chars": 5267,
"preview": "# Steps for restoring project cache\n\nsteps:\n - bash: 'mkdir -p $(Build.StagingDirectory)'\n condition: and(eq(variabl"
},
{
"path": ".ci/utils/use-cache-esy.yml",
"chars": 1234,
"preview": "steps:\n - task: Cache@2\n condition: and(eq(variables['Build.Reason'], 'PullRequest'), ne(variables['Build.SourceBran"
},
{
"path": ".ci/utils/use-cache-npm.yml",
"chars": 557,
"preview": "steps:\n- task: Cache@2\n inputs:\n key: 'npm | \"$(Agent.OS)\" | \"$(Build.SourcesDirectory)/npm-cli/package-lock.json\"'\n"
},
{
"path": ".ci/utils/use-cache-yarn.yml",
"chars": 347,
"preview": "steps:\n- bash: |\n YARN_CACHE_DIR=$(yarn cache dir)\n echo \"##vso[task.setvariable variable=YARN_CACHE_DIR]$YARN_CAC"
},
{
"path": ".ci/utils/use-esy.yml",
"chars": 382,
"preview": "# steps to install @esy-nightly/esy globally\n\nsteps:\n - script: \"sudo npm install --prefix /usr/local --unsafe-perm -g "
},
{
"path": ".ci/utils/use-node.yml",
"chars": 127,
"preview": "# steps to use node on agent\n\nsteps:\n - task: NodeTool@0\n displayName: \"Use Node 10.x\"\n inputs:\n versionSpec"
},
{
"path": ".gitignore",
"chars": 199,
"preview": ".DS_Store\nnpm-debug.log\n.merlin\nyarn-error.log\nnode_modules/\n_build\n_esy\n_release\npesy.install\n.DS_Store\n*.install\n*~\nsc"
},
{
"path": ".npmignore",
"chars": 225,
"preview": ".DS_Store\nnpm-debug.log\n.merlin\nyarn-error.log\nnode_modules/\n_build\n_esy\n_release\npesy.install\n.DS_Store\n*.install\n*~\nsc"
},
{
"path": "CODE_OF_CONDUCT.md",
"chars": 3361,
"preview": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nIn the interest of fostering an open and welcoming environment, w"
},
{
"path": "LICENSE",
"chars": 1057,
"preview": "MIT License\n\nCopyright (c) 2019 \n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this s"
},
{
"path": "ORIGIN.md",
"chars": 171,
"preview": "\n- Includes vendored copy of [JSON.sh](https://github.com/dominictarr/JSON.sh)\n (under MIT LICENSE) which has been ren"
},
{
"path": "PesyE2E.opam",
"chars": 0,
"preview": ""
},
{
"path": "README.html",
"chars": 17931,
"preview": "[ vim: set filetype=Markdown: ]: # (<script src=\"site/Reload.js\"> </script>)\n\n\n# pesy\n\n`pesy` is a command line tool to "
},
{
"path": "azure-pipelines.yml",
"chars": 1068,
"preview": "name: Build npm release\n\nvariables:\n esy__ci_cache_version: v1\n\ntrigger:\n branches:\n include:\n - master\n - re"
},
{
"path": "bin/Pesy.re",
"chars": 11928,
"preview": "module Lib = PesyEsyPesyLib.Lib;\nmodule Utils = PesyEsyPesyUtils.Utils;\nopen Lib;\nopen Utils;\nopen Printf;\nopen Cmdliner"
},
{
"path": "bin/dune",
"chars": 203,
"preview": "(executable\n (package pesy--esy-pesy)\n (name Pesy)\n (modules (:standard))\n (public_name pesy)\n (libraries pesy--esy-pesy"
},
{
"path": "dune",
"chars": 67,
"preview": "(dirs :standard \\ node_modules tmp npm-cli)\n(vendored_dirs vendor)\n"
},
{
"path": "dune-project",
"chars": 29,
"preview": "(lang dune 1.11)\n(name pesy)\n"
},
{
"path": "e2e-tests/.gitignore",
"chars": 65,
"preview": "esy.lock\nnode_modules\n_esy\nlibrary/dune\nexecutable/dune\ntest/dune"
},
{
"path": "e2e-tests/Rimraf.re",
"chars": 1354,
"preview": "type result =\n | Ok\n | Failure;\n\nlet is_directory = p => {\n let stats = Unix.lstat(p);\n switch (stats.st_kind) {\n |"
},
{
"path": "e2e-tests/Runner.re",
"chars": 18180,
"preview": "open Yojson.Basic;\nopen Unix;\nopen Utils;\nopen Bos;\nopen Printf;\n\n/** TODO: Cleanup **/\nlet () = {\n let run = (~env=?, "
},
{
"path": "e2e-tests/Utils.re",
"chars": 3575,
"preview": "open Printf;\n\nmodule Path = {\n let (/) = Filename.concat;\n};\n\nlet makeCommand = cmd =>\n Sys.unix\n ? cmd\n : {\n "
},
{
"path": "e2e-tests/dune",
"chars": 107,
"preview": "(executable\n (name Runner)\n (package PesyE2E)\n (public_name Runner.exe)\n (libraries unix bos yojson str))\n\n"
},
{
"path": "e2e-tests/pending-tests.re",
"chars": 15361,
"preview": "/* OS.Dir.create(~path=true, Fpath.(v(cwd) / \"virtual-foo\")) */ /* >>= ( */ /* L.withLog(\"Editing "
},
{
"path": "errors/Errors.re",
"chars": 590,
"preview": "type validationError =\n | StaleDuneFile(string)\n | StaleOpamFile((string, string));\n\nexception ShouldNotBeNull(string)"
},
{
"path": "errors/dune",
"chars": 97,
"preview": "(library (name PesyEsyPesyErrors) (public_name pesy--esy-pesy.errors)\n (modules (:standard)))\n"
},
{
"path": "lib/Common.re",
"chars": 4876,
"preview": "exception InvalidSubDirs(string);\ntype include_subdirs =\n | No\n | Unqualified;\n\ntype t = {\n path: string,\n require: "
},
{
"path": "lib/Common.rei",
"chars": 854,
"preview": "type include_subdirs;\ntype t;\nlet toDuneStanzas:\n t =>\n (\n option(Stanza.t),\n option(Stanza.t),\n option(Stanz"
},
{
"path": "lib/DuneFile.re",
"chars": 644,
"preview": "module Utils = PesyEsyPesyUtils.Utils;\nopen Sexplib.Sexp;\nopen Printf;\nexception InvalidDuneFile(unit);\nlet toString = ("
},
{
"path": "lib/DuneFile.rei",
"chars": 78,
"preview": "let toString: list(Stanza.t) => string;\nlet ofFile: string => list(Stanza.t);\n"
},
{
"path": "lib/DuneProject.re",
"chars": 284,
"preview": "open Sexplib.Sexp;\n\nexception InvalidDuneProjectFile;\nlet findLangVersion =\n fun\n | [x, ..._] =>\n switch (Stanza.to"
},
{
"path": "lib/DuneProject.rei",
"chars": 46,
"preview": "let findLangVersion: list(Stanza.t) => string;"
},
{
"path": "lib/EsyCommand.re",
"chars": 1596,
"preview": "open Printf;\n\nlet esy_command = \"esy\";\n\n/* @esy-ocaml/foo-package -> foo-package */\nlet resolveEsyCommand = () =>\n Sys"
},
{
"path": "lib/Executable.re",
"chars": 6113,
"preview": "module Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\n\nmodule Mode = {\n exception InvalidCompilationMode(unit);\n exceptio"
},
{
"path": "lib/Executable.rei",
"chars": 285,
"preview": "module Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\n\ntype t;\nmodule Mode: {\n type t;\n let ofFieldTypes: FieldTypes.t =>"
},
{
"path": "lib/ImportsParser.re",
"chars": 99,
"preview": "let parse = str => {\n let lexbuf = Lexing.from_string(str);\n Parser.main(Lexer.read, lexbuf);\n};\n"
},
{
"path": "lib/Lexer.mll",
"chars": 513,
"preview": "(* File lexer.mll *)\n{\n open Parser (* The type token is defined in parser.mli *)\n}\nrule read = parse\n[' ' '\\t']"
},
{
"path": "lib/Lib.re",
"chars": 5961,
"preview": "module Mode = Mode;\nmodule EsyCommand = EsyCommand;\n\nmodule Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\nopen NoLwt;\n\ntyp"
},
{
"path": "lib/Library.re",
"chars": 4129,
"preview": "module Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\n\nmodule Mode = {\n exception InvalidLibraryMode(string);\n type t =\n "
},
{
"path": "lib/Library.rei",
"chars": 373,
"preview": "module Mode: {\n exception InvalidLibraryMode(string);\n type t;\n let ofString: string => t;\n let toString: t => strin"
},
{
"path": "lib/Mode.re",
"chars": 64,
"preview": "module EsyEnv = {\n type t' =\n | UPDATE;\n /* | BUILD; */\n};\n"
},
{
"path": "lib/Parser.mly",
"chars": 421,
"preview": "%token LPAREN RPAREN SEMICOLON\n%token EOL ASSN SQUOTE EOF REQUIRE\n%token <string> MODULE_NAME\n%token <string> MODULE_PAT"
},
{
"path": "lib/PesyConf.re",
"chars": 25830,
"preview": "open Printf;\nmodule Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\nopen PesyEsyPesyErrors.Errors;\n\n/*****\n localName ~~>"
},
{
"path": "lib/PesyModule.re",
"chars": 2868,
"preview": "module Alias = {\n type t = {\n alias: string,\n library: string,\n internal: bool, // if an alias is for a local "
},
{
"path": "lib/Stanza.re",
"chars": 329,
"preview": "open Sexplib.Sexp;\ntype t = Sexplib.Sexp.t;\nlet createAtom = a => Atom(a);\nlet create = (stanza: string, expression) =>\n"
},
{
"path": "lib/Stanza.rei",
"chars": 196,
"preview": "type t;\nlet create: (string, t) => t;\nlet createAtom: string => t;\nlet createExpression: list(t) => t;\nlet toSexp: t => "
},
{
"path": "lib/Stubs.re",
"chars": 3345,
"preview": "module Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\n\ntype t =\n | CNames(list(string))\n | ForeignStubs(list(list((string"
},
{
"path": "lib/Stubs.rei",
"chars": 211,
"preview": "module Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\n\ntype t;\n\nlet ofCNames: list(string) => t;\n\nlet ofForeignStubs: list("
},
{
"path": "lib/Test.re",
"chars": 4432,
"preview": "open Printf;\nmodule Utils = PesyEsyPesyUtils.Utils;\nopen Utils;\n\nmodule Mode = {\n exception InvalidCompilationMode(unit"
},
{
"path": "lib/Test.rei",
"chars": 206,
"preview": "type t;\nmodule Mode: {\n type t;\n let ofList: list(string) => t;\n let toList: t => list(string);\n};\nlet create: (strin"
},
{
"path": "lib/dune",
"chars": 381,
"preview": "(library\n (name PesyEsyPesyLib)\n (public_name pesy--esy-pesy.lib)\n (modules (:standard))\n (libraries str sexplib findlib"
},
{
"path": "notes/benchmarking.md",
"chars": 16694,
"preview": "The results of benchmarking a sample \"generateEverything.sh\" script:\n\n\nDELTAms 5\nDELTAms 11 - detect shell\nDELTAms 6 -"
},
{
"path": "notes/checksum-verification.md",
"chars": 2164,
"preview": "# Checksum verification\n\nAs we create the build artifacts to publish to NPM, we also generate the SHA1 hash\nof the `.tgz"
},
{
"path": "notes/compiler-support.org",
"chars": 2998,
"preview": "* Compiler support\n\n This is an attempt to document compiler support pesy provides.\n \n** Terminologies\n\n As defined "
},
{
"path": "notes/e2e.org",
"chars": 1622,
"preview": "* Setup\n\n Recently [[https://github.com/esy/pesy/pull/105][Windows CI started failing]] due disk space issue. This coul"
},
{
"path": "notes/release.org",
"chars": 1018,
"preview": "* Release process\n\n The release process is mostly manual right now and works like this.\n \n** @pesy/esy-pesy\n\n 1. =gi"
},
{
"path": "npm-cli/.gitignore",
"chars": 97,
"preview": ".DS_Store\n.merlin\n.bsb.lock\nnpm-debug.log\n/lib/bs/\n/node_modules/\npesy.bundle.js\npesy-*.tgz\n*.map"
},
{
"path": "npm-cli/.npmignore",
"chars": 5,
"preview": "src/*"
},
{
"path": "npm-cli/.vscode/tasks.json",
"chars": 1029,
"preview": "{\n \"version\": \"0.1.0\",\n \"command\": \"npm\",\n \"options\": {\n \"cwd\": \"${workspaceRoot}\",\n \"env\": {\n "
},
{
"path": "npm-cli/README.md",
"chars": 367,
"preview": "# Basic Reason Template\n\nHello! This project allows you to quickly get started with Reason and BuckleScript. If you want"
},
{
"path": "npm-cli/__tests__/utils_test.re",
"chars": 1538,
"preview": "open Jest;\nopen Expect;\nopen! Expect.Operators;\n\nopen Utils;\n\ndescribe(\"kebab function\", () => {\n test(\"sample 1\", ()"
},
{
"path": "npm-cli/bsconfig.json",
"chars": 515,
"preview": "{\n \"name\": \"pesy-bootstrapper\",\n \"version\": \"0.1.0\",\n \"sources\": [\n {\n \"dir\": \"src\",\n \"subdirs\": true\n "
},
{
"path": "npm-cli/esy.json",
"chars": 229,
"preview": "{\n \"dependencies\": {\n \"ocaml\": \"4.6.x\",\n \"@opam/dune\": \"2.7.1\",\n \"@esy-ocaml/reason\": \"3.6.x\",\n \"@opam/odoc"
},
{
"path": "npm-cli/package.json",
"chars": 3517,
"preview": "{\n \"name\": \"pesy\",\n \"version\": \"0.5.0-dev.23\",\n \"bin\": {\n \"pesy\": \"pesy\"\n },\n \"esy\": {\n \"install\": [\n \"m"
},
{
"path": "npm-cli/pesy",
"chars": 51,
"preview": "#! /usr/bin/env node\n\nrequire('./pesy.bundle.js');\n"
},
{
"path": "npm-cli/pesy.bundle.js.minisig",
"chars": 284,
"preview": "untrusted comment: signature from minisign secret key\nRWSTA7d4LKXMeRPeL7ceAi164fDdHZ1FIBWA8v6SdC+F496BaNW/H5mEFRhT+mr/6a"
},
{
"path": "npm-cli/pesy.js",
"chars": 228,
"preview": "const Pesy = require(\"./lib/js/src/Pesy.bs.js\");\n\ntry {\n Pesy.main(process.argv.slice(1)).catch((e) =>\n console.erro"
},
{
"path": "npm-cli/rollup.config.js",
"chars": 681,
"preview": "// rollup.config.js\nimport commonjs from \"@rollup/plugin-commonjs\";\nimport nodeResolve from \"@rollup/plugin-node-resolve"
},
{
"path": "npm-cli/scripts/vendor-template.js",
"chars": 2167,
"preview": "const fs = require(\"fs\");\nconst https = require(\"https\");\nconst path = require(\"path\");\nconst cp = require(\"child_proces"
},
{
"path": "npm-cli/src/AzurePipelines.re",
"chars": 5627,
"preview": "open Bindings;\nmodule P = Js.Promise;\n\nmodule ProjectName: {\n type validated;\n type unvalidated;\n type t('a);\n let o"
},
{
"path": "npm-cli/src/Bindings.re",
"chars": 16571,
"preview": "open Js;\n\nmodule Process = {\n type t;\n [@bs.val] external v: t = \"process\";\n [@bs.val] [@bs.scope \"process\"] external"
},
{
"path": "npm-cli/src/Bootstrapper.re",
"chars": 5050,
"preview": "open Bindings;\nopen ResultPromise;\n\nlet runCommand = (cmd, args, projectPath, message) => {\n Js.log(\"\");\n Js.log(messa"
},
{
"path": "npm-cli/src/Bootstrapper.rei",
"chars": 176,
"preview": "let run:\n (\n Cmd.t,\n Path.t,\n Template.Kind.t,\n bool /* bootstrapOnly */,\n bool /* bootstrapCIOnly */,\n "
},
{
"path": "npm-cli/src/Cmd.re",
"chars": 2695,
"preview": "open Bindings;\n\ntype t = {\n cmd: string,\n env: Js.Dict.t(string),\n};\n\ntype stdout = string;\n\ntype stderr = string;\n\nle"
},
{
"path": "npm-cli/src/DefaultTemplate.re",
"chars": 306,
"preview": "open Bindings;\n\nlet path =\n Path.resolve([|\n dirname,\n \"templates\",\n \"pesy-reason-template-0.1.0-alpha.20\", /*"
},
{
"path": "npm-cli/src/Dir.re",
"chars": 904,
"preview": "open Bindings;\n\nlet isEmpty = path =>\n !Node.Fs.existsSync(path)\n || Belt.Array.length(Node.Fs.readdirSync(path)) == 0"
},
{
"path": "npm-cli/src/Dir.rei",
"chars": 274,
"preview": "let isEmpty: Path.t => bool;\n\nlet forEachEnt:\n (string => ResultPromise.t(unit, string), Path.t) =>\n ResultPromise.t(u"
},
{
"path": "npm-cli/src/Esy.re",
"chars": 568,
"preview": "open Bindings;\nopen ResultPromise;\n\ntype t = Cmd.t;\n\nlet make = () => Cmd.make(~cmd=\"esy\", ~env=Process.env);\nlet status"
},
{
"path": "npm-cli/src/Esy.rei",
"chars": 335,
"preview": "type t = Cmd.t;\nlet make: unit => Js.Promise.t(result(t, string));\nlet manifestPath: (string, t) => Js.Promise.t(result("
},
{
"path": "npm-cli/src/EsyLock.re",
"chars": 753,
"preview": "type t = {checksum: string};\n\nlet ofPath = path => {\n Bindings.(\n Fs.readFile(. path)\n |> Js.Promise.then_(lockfi"
},
{
"path": "npm-cli/src/EsyLock.rei",
"chars": 86,
"preview": "type t;\n\nlet ofPath: Path.t => ResultPromise.t(t, string);\nlet checksum: t => string;\n"
},
{
"path": "npm-cli/src/EsyStatus.re",
"chars": 654,
"preview": "type t = {\n isProjectReadyForDev: bool,\n rootPackageConfigPath: string,\n};\nlet make = statusOutput =>\n switch (Json.p"
},
{
"path": "npm-cli/src/EsyStatus.rei",
"chars": 127,
"preview": "type t;\nlet make: string => result(t, string);\nlet getRootPackageConfigPath: t => string;\nlet isProjectReadyForDev: t =>"
},
{
"path": "npm-cli/src/Path.re",
"chars": 17,
"preview": "type t = string;\n"
},
{
"path": "npm-cli/src/Path.rei",
"chars": 17,
"preview": "type t = string;\n"
},
{
"path": "npm-cli/src/Pesy.re",
"chars": 6964,
"preview": "Bindings.sourceMapSupportInstall();\n\nopen Cmdliner;\nopen Utils;\nopen Bindings;\nopen ResultPromise;\n\nlet promptEmptyDirec"
},
{
"path": "npm-cli/src/Pesy.rei",
"chars": 50,
"preview": "let main: (. array(string)) => Js.Promise.t(int);\n"
},
{
"path": "npm-cli/src/PesyConfig.re",
"chars": 1857,
"preview": "module Tag = {\n open Json.Decode;\n type t = string;\n let decoder = json => json |> string;\n};\n\ntype t = {\n azureProj"
},
{
"path": "npm-cli/src/PesyConfig.rei",
"chars": 914,
"preview": "type t;\n\n/* Json decoder */\nlet decoder: Json.Decode.decoder(t);\n\n/* Extracts the Azure Project name from the config */\n"
},
{
"path": "npm-cli/src/Project.re",
"chars": 1174,
"preview": "open ResultPromise;\n\ntype t = {\n path: Path.t,\n manifest: string,\n hash: string,\n};\n\nlet ofPath = (esy, projectPath) "
},
{
"path": "npm-cli/src/Project.rei",
"chars": 464,
"preview": "type t;\n\n/** Given a path, it tries to returns a [project] if it\n is a valid one */\nlet ofPath: (Esy.t, Path.t) => Res"
},
{
"path": "npm-cli/src/Readline.re",
"chars": 361,
"preview": "type io = {\n .\n \"input\": in_channel,\n \"output\": out_channel,\n};\ntype rlType = {\n .\n [@bs.meth] \"close\": unit => uni"
},
{
"path": "npm-cli/src/Result.re",
"chars": 646,
"preview": "open Belt.Result;\n\nlet (>>=) = flatMap;\n\nlet (>>|) = map;\n\nlet return = x => Ok(x);\n\nlet fail = x => Error(x);\n\nlet all "
},
{
"path": "npm-cli/src/ResultPromise.re",
"chars": 1278,
"preview": "type t('a, 'b) = Js.Promise.t(result('a, 'b));\nlet (>>=) = (rp, f) => {\n rp\n |> Js.Promise.then_(\n fun\n | "
},
{
"path": "npm-cli/src/ResultPromise.rei",
"chars": 394,
"preview": "type t('a, 'b) = Js.Promise.t(result('a, 'b));\nlet (>>=): (t('a, 'b), 'a => t('c, 'b)) => t('c, 'b);\nlet (>>|): (t('a, '"
},
{
"path": "npm-cli/src/Spinner.re",
"chars": 896,
"preview": "let interval = 120;\nlet frames = [|\"-\", \"\\\\\", \"|\", \"/\"|];\n/* let frames = [|\". \", \".. \", \"...\"|]; */\n\ntype t = Js.Nulla"
},
{
"path": "npm-cli/src/Spinner.rei",
"chars": 82,
"preview": "type t;\nlet start: string => t;\nlet stop: t => unit;\nlet clearLine: unit => unit;\n"
},
{
"path": "npm-cli/src/Template.re",
"chars": 13160,
"preview": "open Utils;\n\nmodule Helpers: {\n /** Contains functions that compute package name kebabs, upper camel\n cased and other"
},
{
"path": "npm-cli/src/Template.rei",
"chars": 579,
"preview": "module Kind: {\n type t;\n\n let path: string => t;\n let gitRemote: string => t;\n\n let gen: (Path.t, bool, t) => Result"
},
{
"path": "npm-cli/src/Utils.re",
"chars": 4444,
"preview": "let (<<) = (f, g, x) => f(g(x));\nmodule Caml = {\n module List = List;\n module String = String;\n};\n\nlet spf = Printf.sp"
},
{
"path": "npm-cli/src/Warmup.re",
"chars": 16262,
"preview": "open Bindings;\nopen ResultPromise;\n\nlet os =\n switch (Process.platform) {\n | \"darwin\" =>\n let os = \"Darwin\";\n le"
},
{
"path": "npm-cli/stubs/crypto.js",
"chars": 736,
"preview": "const crypto = require(\"crypto\");\nconst fs = require(\"fs\");\n\nfunction sha256(data, secret) {\n crypto.createHmac(\"sha256"
},
{
"path": "npm-cli/stubs/fs.js",
"chars": 467,
"preview": "let { promisify } = require(\"util\");\nlet fs = require(\"fs\");\nlet readFile = promisify(fs.readFile);\nlet writeFile = prom"
},
{
"path": "npm-cli/stubs/resolve.js",
"chars": 870,
"preview": "const url = require(\"url\");\n\nfunction run(urlStr, callback) {\n let httpm;\n let urlObj = url.parse(urlStr);\n switch (u"
},
{
"path": "npm-cli/templates/.gitignore",
"chars": 35,
"preview": "pesy-reason-template-*\ntemplate.zip"
},
{
"path": "npm-cli/templates/.npmignore",
"chars": 0,
"preview": ""
},
{
"path": "npm-cli/templates/ci/.ci/build-docker.yml",
"chars": 1843,
"preview": "jobs:\n - job: Docker_Alpine_Build\n displayName: Docker Alpine Build\n pool:\n vmImage: ubuntu-latest\n steps"
},
{
"path": "npm-cli/templates/ci/.ci/build-platform.yml",
"chars": 4040,
"preview": "parameters:\n platform: \"macOS\"\n vmImage: \"macOS-10.13\"\n\njobs:\n - job: ${{ parameters.platform }}\n pool:\n vmIm"
},
{
"path": "npm-cli/templates/ci/.ci/cross-release.yml",
"chars": 886,
"preview": "steps:\n - template: utils/use-node.yml\n\n - script: \"mkdir _release\"\n displayName: \"Create _release dir\"\n\n - templa"
},
{
"path": "npm-cli/templates/ci/.ci/esy-build-steps.yml",
"chars": 741,
"preview": "# Cross-platform set of build steps for building esy projects\n\nsteps:\n - template: utils/use-node.yml\n - template: uti"
},
{
"path": "npm-cli/templates/ci/.ci/opam-build-steps.yml",
"chars": 1348,
"preview": "# Shared steps for building OPAM projects\n\nsteps:\n - task: NodeTool@0\n inputs:\n versionSpec: '8.9'\n - script: "
},
{
"path": "npm-cli/templates/ci/.ci/pipelines-release.js",
"chars": 3280,
"preview": "const fs = require(\"fs\");\nconst path = require(\"path\");\n\nconsole.log(\"Creating package.json\");\n\n// From the project root"
},
{
"path": "npm-cli/templates/ci/.ci/release-platform-setup.yml",
"chars": 562,
"preview": "parameters:\n platform: \"macOS\"\n folder: \"platform-darwin\"\n\nsteps:\n - task: DownloadBuildArtifacts@0\n displayName: "
},
{
"path": "npm-cli/templates/ci/.ci/release-postinstall.js",
"chars": 4618,
"preview": "/**\n * release-postinstall.js\n *\n * XXX: We want to keep this script installable at least with node 4.x.\n *\n * This scri"
},
{
"path": "npm-cli/templates/ci/.ci/utils/create-docs.yml",
"chars": 604,
"preview": "# These steps are only run on Linux\nsteps:\n - script: \"esy doc\"\n displayName: \"Build docs\"\n condition: and(succee"
},
{
"path": "npm-cli/templates/ci/.ci/utils/publish-build-cache.yml",
"chars": 2437,
"preview": "# Steps for publishing project cache\n\nsteps:\n - bash: 'mkdir -p $(Build.StagingDirectory)'\n displayName: '[Cache][Pu"
},
{
"path": "npm-cli/templates/ci/.ci/utils/publish-sources.yml",
"chars": 284,
"preview": "steps:\n - task: PublishBuildArtifacts@1\n displayName: '[Cache][Publish] Sources'\n condition: eq(variables['AGENT."
},
{
"path": "npm-cli/templates/ci/.ci/utils/restore-build-cache.yml",
"chars": 5267,
"preview": "# Steps for restoring project cache\n\nsteps:\n - bash: 'mkdir -p $(Build.StagingDirectory)'\n condition: and(eq(variabl"
},
{
"path": "npm-cli/templates/ci/.ci/utils/use-cache-esy.yml",
"chars": 1453,
"preview": "steps:\n - task: Cache@2\n inputs:\n key: 'v1 | esy-install-cache | \"$(Agent.OS)\" | \"$(Build.SourcesDirectory)/esy"
},
{
"path": "npm-cli/templates/ci/.ci/utils/use-cache-yarn.yml",
"chars": 347,
"preview": "steps:\n- bash: |\n YARN_CACHE_DIR=$(yarn cache dir)\n echo \"##vso[task.setvariable variable=YARN_CACHE_DIR]$YARN_CAC"
},
{
"path": "npm-cli/templates/ci/.ci/utils/use-esy.yml",
"chars": 742,
"preview": "# steps to install esy globally\n\nsteps:\n- script: \"yarn global add @esy-nightly/esy\"\n displayName: \"Install esy-nightly"
},
{
"path": "npm-cli/templates/ci/.ci/utils/use-node.yml",
"chars": 127,
"preview": "# steps to use node on agent\n\nsteps:\n - task: NodeTool@0\n displayName: \"Use Node 12.x\"\n inputs:\n versionSpec"
},
{
"path": "npm-cli/templates/ci/azure-pipelines-template.yml",
"chars": 1243,
"preview": "name: Build npm release\n\nvariables:\n esy__ci_cache_version: v1 # this is available to all jobs in env as $ESY__CI_CA"
},
{
"path": "npm-cli/templates/docker/docker/DevImage.Dockerfile",
"chars": 256,
"preview": "FROM esydev/esy:nightly-alpine-latest\n\nRUN apk add nodejs npm linux-headers emacs curl git perl-utils bash gcc g++ musl-"
},
{
"path": "npm-cli/templates/docker/docker/ProdImage.Dockerfile",
"chars": 216,
"preview": "FROM node:current-alpine\nCOPY _container_release /app/_container_release\nWORKDIR /app/_container_release\nRUN yarn global"
},
{
"path": "npm-cli/v0.4.4/LICENSE",
"chars": 1065,
"preview": "MIT License\n\nCopyright (c) 2017 Jordan W\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\no"
},
{
"path": "npm-cli/v0.4.4/ORIGIN.md",
"chars": 171,
"preview": "\n- Includes vendored copy of [JSON.sh](https://github.com/dominictarr/JSON.sh)\n (under MIT LICENSE) which has been ren"
},
{
"path": "npm-cli/v0.4.4/README.md",
"chars": 15026,
"preview": "# pesy: Native Reason Project from Json.\n\n> Use `package.json` to automatically configure libraries and executables buil"
},
{
"path": "npm-cli/v0.4.4/azure-ci-template/azure-pipelines.yml",
"chars": 1914,
"preview": "# Starter pipeline\n# Start with a minimal pipeline that you can customize to build and deploy your code.\n# Add steps tha"
},
{
"path": "npm-cli/v0.4.4/azure-ci-template/esy-build-steps.template.yml",
"chars": 730,
"preview": "# Cross-platform set of build steps for building esy projects\n\nsteps:\n - task: NodeTool@0\n inputs:\n versionSpec"
},
{
"path": "npm-cli/v0.4.4/azure-ci-template/publish-build-cache.yml",
"chars": 379,
"preview": "# Steps for publishing project cache\n\nsteps:\n - task: PublishBuildArtifacts@1\n displayName: 'Cache: Upload install f"
},
{
"path": "npm-cli/v0.4.4/azure-ci-template/restore-build-cache.yml",
"chars": 870,
"preview": "# Steps for restoring project cache\n\nsteps:\n - task: DownloadBuildArtifacts@0\n condition: and(succeeded(), ne(variab"
},
{
"path": "npm-cli/v0.4.4/esy-peasy",
"chars": 5114,
"preview": "#!/bin/bash\n\nset -e\nset -u\n\necho \"\"\necho \"esy-peasy low-config build\"\necho \"--------------------------\"\necho \"Building a"
},
{
"path": "npm-cli/v0.4.4/notes/benchmarking.md",
"chars": 16694,
"preview": "The results of benchmarking a sample \"generateEverything.sh\" script:\n\n\nDELTAms 5\nDELTAms 11 - detect shell\nDELTAms 6 -"
},
{
"path": "npm-cli/v0.4.4/pesy",
"chars": 28276,
"preview": "#!/bin/bash\n\nset -e\n\n# pesy-JSON.sh prints out all the fields in the form of:\n# [\"key\"] value\n# where value may be an ar"
},
{
"path": "npm-cli/v0.4.4/pesy-JSON.sh",
"chars": 4809,
"preview": "#!/bin/sh\n\nthrow() {\n echo \"$*\" >&2\n exit 1\n}\n\nBRIEF=0\nLEAFONLY=0\nPRUNE=0\nNO_HEAD=0\nNORMALIZE_SOLIDUS=0\n\nusage() {\n e"
},
{
"path": "npm-cli/v0.4.4/pesy-README.template.md",
"chars": 973,
"preview": "# <PACKAGE_NAME_FULL>\n\n\n[]"
},
{
"path": "npm-cli/v0.4.4/pesy-create.sh",
"chars": 5861,
"preview": "#!/bin/bash\n\nset -e\nset -u\n\nuppers=\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\nlowers=\"abcdefghijklmnopqrstuvwxyz\"\nwordBoundaries=\"._-\""
},
{
"path": "npm-cli/v0.4.4/pesy-footer.template.sh",
"chars": 2355,
"preview": "if [ -f \"${cur__root}/dune\" ]; then\n true\nelse\n BUILD_STALE_PROBLEM=\"true\"\n notifyUser\n if [ \"${MODE}\" == \"build\" ]"
},
{
"path": "npm-cli/v0.4.4/pesy-genBin.template.sh",
"chars": 5760,
"preview": "BIN_DIR=\"${cur__root}/<ORIG_DIR>\"\nBIN_DUNE_FILE=\"${BIN_DIR}/dune\"\n# FOR BINARY IN DIRECTORY <DIR>\n<DIR>_MAIN_MODULE=\"${<"
},
{
"path": "npm-cli/v0.4.4/pesy-genLib.template.sh",
"chars": 5405,
"preview": "\n# Perform validation:\n\nLIB_DIR=\"${cur__root}/<ORIG_DIR>\"\nLIB_DUNE_FILE=\"${LIB_DIR}/dune\"\n\n# TODO: Error if there are mu"
},
{
"path": "npm-cli/v0.4.4/pesy-gitignore.template",
"chars": 129,
"preview": "npm-debug.log\n.merlin\nyarn-error.log\nnode_modules\nnode_modules/\n_build\n_release\n_esy/\n<PACKAGE_NAME>.install\n.DS_Store\n*"
},
{
"path": "npm-cli/v0.4.4/pesy-header.sh",
"chars": 1728,
"preview": "#!/bin/bash\n\nset -e\nset -u\n\nBOLD=`tput bold` || BOLD='' # Select bold mode\nBLACK=`tput setaf 0` || BLACK=''\nRED=`tput"
},
{
"path": "npm-cli/v0.4.4/pesy-name-utils.sh",
"chars": 932,
"preview": "#!/bin/bash\n\nset -e\nset -u\n\n# Also removes /\n# https://stackoverflow.com/a/8952274\nuppers=\"ABCDEFGHIJKLMNOPQRSTUVWXYZ\"\nl"
},
{
"path": "npm-cli/v0.4.4/pesy-package.template.json",
"chars": 1076,
"preview": "{\n \"name\": \"<PACKAGE_NAME_FULL>\",\n \"version\": \"0.0.0\",\n \"description\": \"My Project\",\n \"esy\": {\n \"build\": \"dune bu"
},
{
"path": "package.json",
"chars": 2017,
"preview": "{\n \"name\": \"@pesy/esy-pesy\",\n \"version\": \"0.1.0-alpha.14\",\n \"description\": \"\\\"Esy Pesy\\\" - Your Esy Assistant.\",\n \"e"
},
{
"path": "pesy--esy-pesy.opam",
"chars": 461,
"preview": "opam-version: \"2.0\"\nname: \"pesy\"\ndescription: \"Esy Pesy - Your Esy Assistant\"\nsynopsis: \"Esy Pesy - Your Esy Assistant\"\n"
},
{
"path": "scripts/bootstrap.sh",
"chars": 2459,
"preview": "#! /bin/bash\n\nmkdir -p vendor\ncd vendor\n\n# reason_tag=\"3.0.4\"\n# curl -LO \"https://github.com/facebook/reason/archive/${r"
},
{
"path": "scripts/run.bat",
"chars": 178,
"preview": "call esy build\ncall esy npm-release\ncall cd _release\ncall npm pack\ncall npm install -g ./pesy-0.5.0-dev.23.tgz\ncall cd ."
},
{
"path": "scripts/run.sh",
"chars": 2596,
"preview": "#! /bin/bash\n\n# TODO: Add npm install -g verdaccio\n\nroot=$PWD\ncustom_registry_url=http://localhost:4873\noriginal_npm_reg"
},
{
"path": "scripts/simulate-latest.js",
"chars": 266,
"preview": "let path = require(\"path\");\nlet fs = require(\"fs\");\nlet packageJSONPath = path.join(process.argv[2]);\nlet packageJSON = "
},
{
"path": "scripts/verdaccio.yaml",
"chars": 1972,
"preview": "# path to a directory with all packages\nstorage: ./storage\n# path to a directory with plugins to include\nplugins: ./plug"
},
{
"path": "site/ORIGINS.md",
"chars": 1798,
"preview": "\nDox:\n\nHere's a list of all of the technologies that are used and included in Dox.\n\nEach of these projects should includ"
},
{
"path": "site/Reload.js",
"chars": 48332,
"preview": "/*!\n * Flatdoc - (c) 2013, 2014 Rico Sta. Cruz\n * http://ricostacruz.com/flatdoc\n * @license MIT\n */\n\n\n/**\n * Reload is "
},
{
"path": "site/fonts/CodingFont.css",
"chars": 113062,
"preview": "/*\n * This is actually Fira Mono\n */\n@font-face {\n font-family: 'CodingFont';\n src: url(data:application/font-woff"
},
{
"path": "site/fonts/LICENSE-Fira",
"chars": 4373,
"preview": "Copyright (c) 2012-2013, The Mozilla Corporation and Telefonica S.A.\n\nThis Font Software is licensed under the SIL Open "
},
{
"path": "site/fonts/LICENSE-Roboto",
"chars": 11359,
"preview": "\n Apache License\n Version 2.0, January 2004\n "
},
{
"path": "site/fonts/WordFont.css",
"chars": 469084,
"preview": "/*\n * This is actually Roboto.\n */\n@font-face {\n font-family: 'WordFont';\n src: url(data:application/font-woff;cha"
},
{
"path": "site/index.dev.html",
"chars": 2912,
"preview": "<!doctype html>\n<html>\n<head>\n <meta charset='utf-8'>\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n"
},
{
"path": "site/package.json",
"chars": 627,
"preview": "{\n \"name\": \"Reload\",\n \"description\": \"Fork of the wonderful Flatdoc\",\n \"keywords\": [\n \"documentation\",\n \"markdo"
},
{
"path": "site/theme-white/theme.js",
"chars": 10490,
"preview": "(function($) {\n var $window = $(window);\n var $document = $(document);\n\n /*\n * Scrollspy.\n */\n $document.on('fla"
},
{
"path": "site/theme-white/theme.styl.html",
"chars": 21128,
"preview": "<script src=\"../Reload.js\"> </script>\nsupport-for-ie = true\n\n// Fonts\n$body-font = PrivateWordFont, WordFont, Helvetica "
},
{
"path": "unit-tests/runner/Lib.re",
"chars": 23772,
"preview": "module TestFramework = {\n include Rely.Make({\n let config =\n Rely.TestFrameworkConfig.initialize({\n snap"
},
{
"path": "unit-tests/runner/RunUnitTests.re",
"chars": 112,
"preview": "module UtilsTests = Utils;\nmodule LibTests = Lib;\nLibTests.TestFramework.cli();\nUtilsTests.TestFramework.cli();\n"
},
{
"path": "unit-tests/runner/Utils.re",
"chars": 477,
"preview": "module TestFramework = {\n include Rely.Make({\n let config =\n Rely.TestFrameworkConfig.initialize({\n snap"
},
{
"path": "unit-tests/runner/dune",
"chars": 90,
"preview": "(test\n (name RunUnitTests)\n (libraries pesy--esy-pesy.lib pesy--esy-pesy.utils rely.lib))\n"
},
{
"path": "utils/FieldTypes.re",
"chars": 846,
"preview": "open Printf;\ntype t =\n | Bool(bool)\n | String(string)\n | List(list(t));\nexception ConversionException(string);\nlet to"
},
{
"path": "utils/JSON.re",
"chars": 1495,
"preview": "/* TODO: Making parsing more lenient? Eg. allow string where single element list is valid */\ninclude Yojson.Basic;\nopen "
},
{
"path": "utils/JSON.rei",
"chars": 394,
"preview": "include (module type of Yojson.Basic);\ntype t = Yojson.Basic.t;\nexception NullJSONValue(unit);\nexception InvalidJSONValu"
},
{
"path": "utils/Utils.re",
"chars": 7932,
"preview": "open Printf;\nlet prompt = q => {\n print_endline(q);\n read_line();\n};\n\nlet kebab = str => {\n let charStrings = Str.spl"
},
{
"path": "utils/dune",
"chars": 134,
"preview": "(library\n (name PesyEsyPesyUtils)\n (public_name pesy--esy-pesy.utils)\n (modules (:standard))\n (libraries unix yojson str"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the esy/pesy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 182 files (1.1 MB), approximately 550.7k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.