Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
79c5f78
Test packed CLI installations across package managers on Linux
fhammerschmidt Oct 12, 2026
7dfe963
Allow package manager completion output in E2E assertions
fhammerschmidt Oct 12, 2026
8dcf2c1
Use ReScript 12 for existing-project E2E coverage
fhammerschmidt Oct 12, 2026
ab1393d
Run installation E2E tests in JavaScript
fhammerschmidt Oct 12, 2026
b0e48f0
Isolate E2E launcher from the parent npm script environment
fhammerschmidt Oct 12, 2026
901b33a
Write installation E2E tests in ReScript
fhammerschmidt Oct 13, 2026
e67ea5b
Clarify ReScript 12 conventions and require explicit E2E parameters
fhammerschmidt Oct 13, 2026
73dceef
Keep the terminal-mode pattern easy to read
fhammerschmidt Oct 13, 2026
4526b39
Consolidate E2E Node bindings in the existing Node module
fhammerschmidt Oct 13, 2026
8cf24bd
Update ReScript to 12.3.1 and use let? in E2E tests
fhammerschmidt Oct 13, 2026
f9e73b0
Keep minimal E2E instructions in AGENTS.md
fhammerschmidt Oct 13, 2026
6e3ae44
Propagate typed E2E failures with let? and wrap unsafe Node calls
fhammerschmidt Oct 13, 2026
fd339fc
Separate multiline bindings and types with blank lines
fhammerschmidt Oct 13, 2026
550a317
Reuse production package manager type in E2E tests
fhammerschmidt Oct 13, 2026
880dc39
Use dict patterns for E2E environment and JSON assertions
fhammerschmidt Oct 13, 2026
a37be2f
Handle E2E results with top-level await
fhammerschmidt Oct 13, 2026
42f25dc
Keep Node bindings raw and collect safe adapters in NodeSafe
fhammerschmidt Oct 13, 2026
aa77354
Propagate E2E results without catches in main
fhammerschmidt Oct 13, 2026
88c7831
Handle nullable Node values directly in E2E tests
fhammerschmidt Oct 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 71 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,78 @@ jobs:
npm i -g ${{ needs.build.outputs.artifact_filename }}
npx create-rescript-app

e2e:
name: E2E (${{ matrix.manager }})
needs: build
runs-on: ubuntu-24.04
timeout-minutes: 25

strategy:
fail-fast: false
matrix:
include:
- manager: npm
package: ""
- manager: pnpm
package: pnpm@10.11.0
- manager: yarn-classic
package: yarn@1.22.22
- manager: yarn-berry
package: "@yarnpkg/cli-dist@4.9.2"
- manager: bun
package: ""

steps:
- name: Checkout
uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 20.19.0 # minimum supported version

- name: Install package manager
if: matrix.package != ''
env:
PACKAGE: ${{ matrix.package }}
run: |
npm install --prefix "$RUNNER_TEMP/e2e-tools" "$PACKAGE"
echo "$RUNNER_TEMP/e2e-tools/node_modules/.bin" >> "$GITHUB_PATH"

- name: Install Bun
if: matrix.manager == 'bun'
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.11

- name: Download packed CLI
uses: actions/download-artifact@v4
with:
name: ${{ needs.build.outputs.artifact_filename }}

- name: Install harness build dependencies
run: npm ci

- name: Install, compile and run real projects
env:
PACKAGE: ${{ needs.build.outputs.artifact_filename }}
MANAGER: ${{ matrix.manager }}
run: >-
npm run test:e2e --
--package "$PACKAGE"
--manager "$MANAGER"
--logs "$RUNNER_TEMP/cra-e2e-logs"

- name: Upload failure logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: e2e-${{ matrix.manager }}-logs
path: ${{ runner.temp }}/cra-e2e-logs
if-no-files-found: ignore

publish:
needs: [build, test]
needs: [build, test, e2e]

if: startsWith(github.ref, 'refs/tags/v')

Expand Down
16 changes: 14 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@

## Coding Style & Naming Conventions

- Make sure to use modern ReScript and not Reason syntax! Read https://rescript-lang.org/llms/manual/llm-small.txt to learn the language syntax.
- Use ReScript 12 syntax and the built-in standard library, not Reason syntax. Read https://rescript-lang.org/llms/manual/llm-small.txt to learn the language syntax.
- Prefer `Option`, `Result`, `Array`, `List`, `Dict`, `Set`, `Map`, and `JSON` over `Js.*` or `Belt.*`.
- Prefer `dict{...}` patterns for known dictionary keys and nested JSON shapes; use `Dict.get` for dynamic keys.
- Match `Nullable.t` directly with `Value`, `Null`, and `Undefined`, and use `Nullable.getOr` for defaults instead of converting to `option`.
- Match local style; avoid old BuckleScript/ReScript-era patterns.
- Write new tests and test helpers in ReScript under `test/`. Prefer records and functions over classes.
- Reuse existing application types in tests instead of redefining them.
- Keep parameters mandatory unless callers need to omit them.
- Do not add redundant parentheses around switch expressions: use `switch value { ... }`. Parentheses required by tuple syntax are fine.
- Formatting is enforced by `rescript format`; keep 2-space indentation and prefer pattern matching over chained conditionals.
- Separate multiline `let` bindings (including `let?`) and type definitions from surrounding statements or declarations with a blank line before and after. Keep attached comments and attributes together with the declaration.
- Prefer `result` values over exceptions for expected failure paths; only raise or throw at clear integration boundaries where the surrounding API requires it.
- Use `let?` to unwrap and propagate `Option`/`Result` failures where it simplifies the code. `LetUnwrap` is enabled in `rescript.json`.
- Prefer pattern matching over `if`/`else` chains when branching on the shape or state of the same value; plain comparisons across different values are fine with `if`/`else`.
- Do not run `rescript`, `npm test`, or `npm run prepack` in parallel; ReScript compiler artifacts are not safe for concurrent builds in this repo.
- Module files are PascalCase (`Templates.res`), values/functions camelCase, types/variants PascalCase, and records snake_case fields only when matching external JSON.
- Module files and variant constructors are PascalCase (`Templates.res`), values/functions and type names camelCase, and records use snake_case fields only when matching external JSON.
- Keep `.resi` signatures accurate and minimal; avoid exposing helpers that are template-specific.
- Keep raw Node.js bindings in `src/bindings/Node.res`. Put exception-to-result/option wrappers in `src/bindings/NodeSafe.res`, using small concrete helpers.
- When touching templates, mirror upstream defaults and keep package scripts consistent with the chosen toolchain.

## Package Manager Support
Expand Down Expand Up @@ -40,6 +51,7 @@
## Testing and Validation

- **Automated Tests**: Run `npm test` for automated coverage of CLI parsing and related helpers
- **Installation E2E**: Run `npm pack`, then `npm run test:e2e -- --package <tarball> --manager <manager> --logs <directory>`. Requires Linux, util-linux `script`, and the selected package manager; use the versions in `.github/workflows/ci.yml`.
- **Manual Testing**: Perform smoke tests by running the CLI into a temp directory
- **CI Preview Testing**: To test a pkg.pr.new build from CI, run `npm exec --yes --package=https://pkg.pr.new/rescript-lang/create-rescript-app/create-rescript-app@<commit> -- create-rescript-app my-app --template vite` and replace `<commit>` with the SHA from the pkg.pr.new CI comment.
- **Template Validation**: After changes, test each template type (basic/Next.js/Vite) to ensure templates bootstrap cleanly
Expand Down
67 changes: 37 additions & 30 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"start": "node src/Main.res.mjs",
"prepack": "rescript && rollup -c",
"test": "rescript && node --test test/*Test.res.mjs test/*Test.mjs",
"test:e2e": "rescript && node test/e2e/E2eMain.res.mjs",
"format": "rescript format",
"dev": "rescript -w"
},
Expand Down Expand Up @@ -44,7 +45,7 @@
"@vitejs/plugin-react": "^6.0.1",
"compare-versions": "^6.1.0",
"jsonc-parser": "^3.3.1",
"rescript": "^12.0.0",
"rescript": "^12.3.1",
"rollup": "^4.53.3",
"typescript": "^5.9.3",
"vite": "^8.0.10"
Expand Down
5 changes: 4 additions & 1 deletion rescript.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,8 @@
"module": "esmodule",
"in-source": true
},
"suffix": ".res.mjs"
"suffix": ".res.mjs",
"experimental-features": {
"LetUnwrap": true
}
}
4 changes: 2 additions & 2 deletions src/NewProjectLocation.res
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ let hasDevcontainerDirectory = projectPath =>
try {
Path.join2(projectPath, devcontainerDirectoryName)->Fs.statSync->Fs.Stats.isDirectory
} catch {
| Exn.Error(_) => false
| JsExn(_) => false
}

let containsOnlyDevcontainerDirectory = projectPath =>
Expand All @@ -42,7 +42,7 @@ let containsOnlyDevcontainerDirectory = projectPath =>
| _ => false
}
} catch {
| Exn.Error(_) => false
| JsExn(_) => false
}

let isAllowedCurrentDirectoryEntry = (~cwd, entry) => {
Expand Down
2 changes: 1 addition & 1 deletion src/NpmRegistry.res
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let defaultRegistryUrl = "https://registry.npmjs.org"

let getNpmRegistry = () =>
npm_config_registry
->Option.flatMap(registry => registry->Node.Url.make)
->Option.flatMap(registry => registry->NodeSafe.Url.make)
->Option.mapOr(defaultRegistryUrl, url => url->Node.Url.href)

type fetchError =
Expand Down
Loading
Loading