Use ReScript and Nx JSON schemas for CUE generate task

This commit is contained in:
Hoang Nguyen 2024-03-13 00:00:00 +07:00
parent 44f2665b82
commit a34eea4268
Signed by: folliehiyuki
GPG Key ID: B0567C20730E9B11
16 changed files with 510 additions and 42 deletions

View File

@ -12,6 +12,6 @@ trim_trailing_whitespace = true
indent_style = space
indent_size = 2
[{*.cue,*.json}]
[{*.cue,*.json,*.sh}]
tab_width = 2
indent_style = tab

View File

@ -3,10 +3,11 @@ Upstream-Name: infra
Upstream-Contact: Hoang Nguyen <folliekazetani@protonmail.com>
Source: https://gitlab.com/folliehiyuki/infra
Files: flake.lock pnpm-lock.yaml
Files: flake.lock pnpm-lock.yaml cue.mod/pkg/*.cue */package.json */rescript.json
Copyright: none
License: CC0-1.0
Comment: Auto-generated files
Files: pnpm-workspace.yaml biome.json nx.json package.json */package.json */rescript.json
Files: pnpm-workspace.yaml biome.json nx.json package.json
Copyright: 2024 Hoang Nguyen <folliekazetani@protonmail.com>
License: CC0-1.0
License: Apache-2.0

View File

@ -0,0 +1,111 @@
package nx
#Project: {
// JSON schema for Nx projects
@jsonschema(schema="http://json-schema.org/draft-07/schema")
@jsonschema(id="https://nx.dev/reference/project-configuration")
// Named inputs used by inputs defined in targets
namedInputs?: {
[string]: #inputs
}
// Configures all the targets which define what tasks you can run
// against the project
targets?: {
[string]: {
// The function that Nx will invoke when you run this target
executor?: string
options?: {
...
}
outputs?: [...string]
// The name of a configuration to use as the default if a
// configuration is not provided
defaultConfiguration?: string
// provides extra sets of values that will be merged into the
// options map
configurations?: {
[string]: {
...
}
}
inputs?: #inputs
dependsOn?: [...string | ({
projects: _
target: _
...
} | {
dependencies: _
target: _
...
} | {
target: _
...
}) & {
projects?: string | [...string]
dependencies?: bool
// The name of the target.
target?: string
// Configuration for params handling.
params?: "ignore" | "forward" | *"ignore"
}]
// A shorthand for using the nx:run-commands executor
command?: string
// Specifies if the given target should be cacheable
cache?: bool
...
}
}
tags?: [...string]
implicitDependencies?: [...string]
#inputs: [...string | {
// A glob used to determine a fileset.
fileset?: string
} | ({
projects: _
input: _
...
} | {
dependencies: _
input: _
...
} | {
input: _
...
}) & {
projects?: string | [...string]
// Include files belonging to the input for all the project
// dependencies of this target.
dependencies?: bool
// The name of the input.
input?: string
} | {
// The command that will be executed and included into the hash.
runtime?: string
} | {
// The env var that will be included into the hash.
env?: string
} | {
// The list of external dependencies that our target depends on
// for `nx:run-commands` and community plugins.
externalDependencies?: [...string]
} | {
// The glob list of output files that project depends on.
dependentTasksOutputFiles: string
// Whether the check for outputs should be recursive or stop at
// the first level of dependencies.
transitive?: bool
}]
...
}

View File

@ -0,0 +1,242 @@
package rescript
#Build: {
// ReScript build configuration
//
// All paths are required to be in **Unix format** (foo/bar), the
// build system normalizes them for other platforms internally
@jsonschema(schema="http://json-schema.org/draft-04/schema#")
// ReScript dependencies of the library, like in package.json.
// Currently searches in `node_modules`
"bs-dependencies"?: #dependencies
// ReScript dev dependencies of the library, like in package.json.
// Currently searches in `node_modules`
"bs-dev-dependencies"?: #dependencies
// (Not needed usually) external include directories, which will
// be applied `-I` to all compilation units
"bs-external-includes"?: [...string]
// Flags passed to bsc.exe
"bsc-flags"?: #["bsc-flags"]
// Ignore generators, cut the dependency on generator tools
"cut-generators"?: bool
// Use the external stdlib library instead of the one shipped with
// the compiler package
"external-stdlib"?: string
// (WIP) Pre defined rules
generators?: [...#["rule-generator"]]
// gentype config, see cristianoc/genType for more details
gentypeconfig?: #["gentype-specs"]
// a list of directories that bsb will not look into
"ignored-dirs"?: [...string]
// (Experimental) post-processing hook. bsb will invoke `cmd
// ${file}` whenever a `${file}` is changed
"js-post-build"?: #["js-post-build"]
// Configuration for the JSX transformation.
jsx?: #["jsx-specs"]
// Package name
name: string
// can be true/false or a customized name
namespace?: #["namespace-spec"]
// ReScript can currently output to
// [Commonjs](https://en.wikipedia.org/wiki/CommonJS), and [ES6
// modules](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
"package-specs"?: #["package-specs"]
// Those dependencies are pinned (since version 8.4)
"pinned-dependencies"?: #dependencies
// preprocessors to pass to compiler. The syntax is
// package_name/binary, for example: `pp/syntax.exe`. Currenly
// searches in `node_modules`
"pp-flags"?: #["pp-specs"]
// PPX macros to pass to compiler. The syntax is
// package_name/binary, for example:
// `reason/reactjs_jsx_ppx_3.native`. Currenly searches in
// `node_modules`
"ppx-flags"?: #["ppx-specs"]
// Configure reanalyze, a static code analysis tool for ReScript.
reanalyze?: #reanalyze
// ReScript comes with [Reason](http://reasonml.github.io/) by
// default. Specific configurations here.
reason?: #["reason-specs"]
// Source code location
sources: #sources
suffix?: #["suffix-spec"]
// Configuration for the uncurried mode.
uncurried?: bool
// (Experimental) whether to use the OCaml standard library.
// Default: true
"use-stdlib"?: bool
// The semantic version of the ReScript library
version?: string
// warning numbers and whether to turn it into error or not
warnings?: {
error?: bool | string
// Default: -40+6+7+27+32..39+44+45
// [Here](https://caml.inria.fr/pub/docs/manual-ocaml/comp.html#sec270)
// for the meanings of the warning flags
number?: string
...
}
#: "bs-dependency": string
#: "bsc-flags": [...string] | {
flags?: [...string]
kind?: "reset" | "prefix" | "append"
...
}
#: "build-generator": {
edge?: [...string]
name?: string
...
}
#dependencies: [...#["bs-dependency"]]
#: "gentype-specs": {
path?: string
...
}
#: "js-post-build": {
cmd?: string
...
}
#: "jsx-specs": {
// JSX transformation mode
mode?: "classic" | "automatic"
// JSX module. Either "react" for React, or (since v11.1) any
// valid module name to apply a generic JSX transform.
module?: string
// Build the given dependencies in JSX V3 compatibility mode.
"v3-dependencies"?: #dependencies
// Whether to apply the specific version of JSX PPX transformation
version?: (3 | 4) & int
}
#: "module-format": "commonjs" | "es6" | "es6-global"
#: "module-format-object": {
// Default: false.
"in-source"?: bool
module: #["module-format"]
suffix?: #["suffix-spec"]
...
}
#: "namespace-spec": bool | string
#: "package-spec": #["module-format"] | #["module-format-object"]
#: "package-specs": [...#["package-spec"]] | #["package-spec"]
#: "pp-specs": string
#: "ppx-specs": [...string | [...string]]
#: "react-jsx-version": number
#reanalyze: {
// The types of analysis to activate. `dce` means dead code
// analysis, `exception` means exception analysis, and
// `termination` is to check for infinite loops.
analysis?: [..."dce" | "exception" | "termination"]
// Paths for any folders you'd like to exclude from analysis.
// Useful for bindings and similar. Example: `["src/bindings"]`.
suppress?: [...string]
// specify whether transitively dead items should be reported
// (default: false)
transitive?: bool
// Any specific paths inside suppressed folders that you want to
// unsuppress. Example: ["src/bindings/SomeBinding.res"].
unsuppress?: [...string]
}
#: "reason-specs": {
// Whether to apply the
// [RescriptReact](https://github.com/rescript-lang/rescript-react)-specific
// JSX PPX transformation.
"react-jsx"?: #["react-jsx-version"]
...
}
#: "rule-generator": {
command?: string
name?: string
...
}
#sourceItem: {
// name of the directory
dir: string
files?: [...string] | {
// Files to be excluded
excludes?: [...string]
// Regex to glob the patterns, syntax is documented
// [here](http://caml.inria.fr/pub/docs/manual-ocaml/libref/Str.html),
// for better incremental build performance, we'd suggest listing
// files explicitly
"slow-re"?: string
...
}
// (WIP) Files generated in dev time
generators?: [...#["build-generator"]]
// Not implemented yet
group?: string | {
// When true, all subdirs are considered as a whole as dependency
hierachy?: bool
name?: string
...
}
"internal-depends"?: [...string]
// Default: export all modules. It is recommended for library
// developers to hide some files/interfaces
public?: [...string] | "all"
resources?: [...string]
// Sub directories
subdirs?: #sources | bool
type?: "dev"
...
} | string
#sources: [...#sourceItem] | #sourceItem
#: "suffix-spec": string
}

View File

@ -49,7 +49,7 @@
formatter = treefmtEval.config.build.wrapper;
devShells.default = with pkgs; mkShell {
devShells.default = with pkgs; mkShellNoCC {
inherit shellHook;
inputsFrom = [ treefmtEval.config.build.devShell ];
packages = pulumiInputs ++ [ git nix sops vim ];

View File

@ -7,13 +7,23 @@
"@nx/workspace": "^18.0.7",
"nx": "^18.0.7"
},
"scripts": {
"gen:project-conf": "cd tools/generate/ && cue cmd gen-project-conf"
},
"nx": {
"targets": {
"gen:cue-schemas": {
"executor": "nx:run-commands",
"options": {
"command": "./tools/gen-cue-schemas.sh",
"cwd": "{workspaceRoot}"
},
"outputs": ["{workspaceRoot}/cue.mod/pkg/**/*.cue"]
},
"gen:project-conf": {
"cache": true,
"executor": "nx:run-commands",
"options": {
"command": "cd ./tools/generate/ && cue cmd gen-project-conf",
"cwd": "{workspaceRoot}"
},
"inputs": ["{workspaceRoot}/tools/generate/*.cue"],
"outputs": [
"{workspaceRoot}/packages/*/package.json",

View File

@ -1,8 +1,17 @@
{
"devDependencies": {
"@pulumi/pulumi": "3.109.0",
"@pulumi/cloudflare": "5.22.0",
"@rescript/core": "^1.1.0",
"rescript": "^11.0.1"
},
"peerDependencies": {
"@pulumi/pulumi": ">=3.98.0",
"@pulumi/cloudflare": "5.x"
},
"name": "bindings",
"type": "module",
"private": true,
"devDependencies": { "@rescript/core": "^1.1.0", "rescript": "^11.0.1" },
"nx": {
"targets": {
"res:build": {},

View File

@ -1,7 +1,7 @@
{
"name": "bindings",
"namespace": true,
"sources": { "dir": "src", "subdirs": true },
"package-specs": { "module": "es6-global", "in-source": true },
"sources": { "dir": "src", "subdirs": true },
"suffix": ".res.js"
}

View File

@ -1,7 +1,11 @@
{
"main": "src/Main.res.js",
"dependencies": { "bindings": "workspace:*" },
"name": "cloudflare",
"main": "src/Main.res.js",
"dependencies": {
"bindings": "workspace:*",
"@pulumi/pulumi": "3.109.0",
"@pulumi/cloudflare": "5.22.0"
},
"type": "module",
"private": true,
"devDependencies": { "@rescript/core": "^1.1.0", "rescript": "^11.0.1" },

View File

@ -1,8 +1,8 @@
{
"name": "cloudflare",
"bs-dependencies": ["bindings"],
"name": "cloudflare",
"namespace": true,
"sources": { "dir": "src", "subdirs": true },
"package-specs": { "module": "es6-global", "in-source": true },
"sources": { "dir": "src", "subdirs": true },
"suffix": ".res.js"
}

26
tools/gen-cue-schemas.sh Executable file
View File

@ -0,0 +1,26 @@
#!/usr/bin/env nix
#! nix shell nixpkgs#bash nixpkgs#curl nixpkgs#gojq --command bash
# SPDX-FileCopyrightText: 2024 Hoang Nguyen <folliekazetani@protonmail.com>
#
# SPDX-License-Identifier: Apache-2.0
# NOTE: this script is expected to be run in the project root via
# `pnpm nx gen:cue-schemas` command
mkdir -p cue.mod/pkg/github.com/{nrwl/nx,rescript-lang/rescript}
cue import -f \
-p nx \
-l "#Project:" \
--outfile cue.mod/pkg/github.com/nrwl/nx/project-schema.cue \
node_modules/nx/schemas/project-schema.json
# Error: constraint not allowed because type number is excluded
curl -fsSL https://github.com/rescript-lang/rescript-compiler/raw/master/docs/docson/build-schema.json | \
gojq '.definitions."jsx-specs".properties.version.type = "integer"' > /tmp/build-schema.json
cue import -f \
-p rescript \
-l '#Build:' \
--outfile cue.mod/pkg/github.com/rescript-lang/rescript/build-schema.cue \
/tmp/build-schema.json

View File

@ -8,6 +8,7 @@ package generate
name: string
path: string
pulumi: bool | *false
dependencies: [...string] | *[]
// Additional package.json and rescript.json config
package: #Package
@ -18,10 +19,26 @@ workspaces: [...#Workspace] & [
{
name: "bindings"
path: "packages/bindings"
package: {
devDependencies: _mapNpmDeps & {
_deps: [
"@pulumi/pulumi",
"@pulumi/cloudflare",
]
}
peerDependencies: {
"@pulumi/pulumi": ">=3.98.0"
"@pulumi/cloudflare": "5.x"
}
}
},
{
name: "cloudflare"
path: "stacks/cloudflare"
pulumi: true
dependencies: [
"@pulumi/pulumi",
"@pulumi/cloudflare",
]
},
]

View File

@ -33,6 +33,12 @@ command: "gen-project-conf": {
contents: json.Marshal(ws.package & {
name: ws.name
_pulumi: ws.pulumi
if list.MinItems(ws.dependencies, 1) {
dependencies: _mapNpmDeps & {
_deps: ws.dependencies
}
}
})
}

View File

@ -4,10 +4,24 @@
package generate
import (
_nx "github.com/nrwl/nx"
)
// Custom internal types
#dependency: [string]: string
// https://json.schemastore.org/package.json is a pain to import into CUE, due to its external
// JSON Schema dependencies and invalid fields. Hence I only define what I need explicitly here.
#Package: {
// Required inputs
// Additional inputs
_pulumi: bool
name: string
// Optional fields I care to configure
name: string
main?: string
dependencies?: #dependency
peerDependencies?: #dependency
// Enforce ES6 style
type: "module"
@ -15,33 +29,31 @@ package generate
// I don't intend to publish anything to NPM registry
private: true
// Use the same version of ReScript everywhere
devDependencies: {
"@rescript/core": "^1.1.0"
rescript: "^11.0.1"
// All subprojects are written in ReScript, so force devDependencies here
devDependencies: #dependency & {
for _, pkg in ["@rescript/core", "rescript"] {
"\(pkg)": npmPackages[pkg]
}
}
// Make ReScript tasks available everywhere
nx: targets: {
"res:build": {}
"res:clean": {}
"res:dev": {}
"res:format": {}
nx: _nx.#Project & {
targets: {
"res:build": {}
"res:clean": {}
"res:dev": {}
"res:format": {}
}
}
// All Pulumi projects here should work the same
if _pulumi {
main: "src/Main.res.js"
dependencies: bindings: "workspace:*"
nx: {
targets: {
preview: options: stack: name
refresh: options: stack: name
up: options: stack: name
}
nx: targets: {
preview: options: stack: name
refresh: options: stack: name
up: options: stack: name
}
}
// Allow further customizations
...
}

View File

@ -4,13 +4,17 @@
package generate
#ReScript: {
// Required inputs
name: string
import (
"github.com/rescript-lang/rescript"
)
#ReScript: rescript.#Build & {
// Additional inputs
_pulumi: bool
// Enforced configurations
// Recommended to be enable by default
namespace: bool | string | *true
sources: {
dir: "src"
subdirs: true
@ -21,11 +25,9 @@ package generate
}
suffix: ".res.js"
// The rebuild of dependencies is handled by Nx, so
// pinned-dependencies isn't specified here
if _pulumi {
// The rebuild of dependencies is handled by Nx, so pinned-dependencies isn't specified here
"bs-dependencies": ["bindings", ...]
}
// Allow further customizations
...
}

View File

@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2024 Hoang Nguyen <folliekazetani@protonmail.com>
//
// SPDX-License-Identifier: Apache-2.0
package generate
// Centralize version management of NPM dependencies.
// NOTE: these version numbers aren't used for peerDependencies, as the key
// should allow a broad range of accepted versions.
npmPackages: #dependency & {
// ReScript
"@rescript/core": "^1.1.0"
rescript: "^11.0.1"
// Pulumi providers
"@pulumi/pulumi": "3.109.0"
"@pulumi/cloudflare": "5.22.0"
}
// This struct acts like a convenient function to convert a list of dependency names
// into a map of dependencies and their specific versions defined above
_mapNpmDeps: {
_deps: [...string]
for _, pkg in _deps {
"\(pkg)": npmPackages[pkg]
}
}