Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
6 changes: 3 additions & 3 deletions .github/workflows/ci.common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ jobs:
- name: Install
run: pnpm install --frozen-lockfile

- name: Build TS
run: pnpm --filter=${{ inputs.package_name }} run build
- name: Build
run: pnpm turbo run build --filter=${{ inputs.package_name }}

- name: Run Tests
run: pnpm --filter=${{ inputs.package_name }} run test
run: pnpm turbo run test --filter=${{ inputs.package_name }}
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ jobs:
["packages/core"]="@modular-react/core"
["packages/react"]="@modular-react/react"
["packages/testing"]="@modular-react/testing"
["packages/react-router-cli"]="@react-router-modules/cli"
["packages/react-router-core"]="@react-router-modules/core"
["packages/react-router-runtime"]="@react-router-modules/runtime"
["packages/react-router-testing"]="@react-router-modules/testing"
)

PACKAGES=()
Expand Down
54 changes: 37 additions & 17 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,35 @@ jobs:
- 'packages/testing/src/**'
- 'packages/testing/package.json'
- 'packages/testing/README.md'
pkg_rr_cli:
- 'packages/react-router-cli/src/**'
- 'packages/react-router-cli/package.json'
- 'packages/react-router-cli/README.md'
pkg_rr_core:
- 'packages/react-router-core/src/**'
- 'packages/react-router-core/package.json'
- 'packages/react-router-core/README.md'
pkg_rr_runtime:
- 'packages/react-router-runtime/src/**'
- 'packages/react-router-runtime/package.json'
- 'packages/react-router-runtime/README.md'
pkg_rr_testing:
- 'packages/react-router-testing/src/**'
- 'packages/react-router-testing/package.json'
- 'packages/react-router-testing/README.md'

- name: Build dynamic matrix
id: build-matrix
run: |
declare -A PKG_NAMES=(
["pkg_core"]="core"
["pkg_react"]="react"
["pkg_testing"]="testing"
# Each entry: filter_key -> dir:npmName
declare -A PKG_MAP=(
["pkg_core"]="core:@modular-react/core"
["pkg_react"]="react:@modular-react/react"
["pkg_testing"]="testing:@modular-react/testing"
["pkg_rr_cli"]="react-router-cli:@react-router-modules/cli"
["pkg_rr_core"]="react-router-core:@react-router-modules/core"
["pkg_rr_runtime"]="react-router-runtime:@react-router-modules/runtime"
["pkg_rr_testing"]="react-router-testing:@react-router-modules/testing"
)

CHANGED='${{ steps.filter.outputs.changes }}'
Expand All @@ -73,18 +94,17 @@ jobs:
MATRIX="["
FIRST=true

for key in "${!PKG_NAMES[@]}"; do
for key in "${!PKG_MAP[@]}"; do
if echo "$CHANGED" | grep -q "\"$key\""; then
NAME="${PKG_NAMES[$key]}"
NPM_NAME="@modular-react/${NAME}"
IFS=':' read -r DIR NPM_NAME <<< "${PKG_MAP[$key]}"

if [ "$FIRST" = true ]; then
FIRST=false
else
MATRIX+=","
fi

MATRIX+="{\"name\":\"${NAME}\",\"npmName\":\"${NPM_NAME}\"}"
MATRIX+="{\"dir\":\"${DIR}\",\"npmName\":\"${NPM_NAME}\"}"
fi
done

Expand Down Expand Up @@ -148,9 +168,9 @@ jobs:
VERSIONS_JSON="{"
FIRST=true

for PKG_NAME in $(echo "$MATRIX" | jq -r '.[] | .name'); do
echo "Bumping version for $PKG_NAME..."
cd "packages/$PKG_NAME"
for PKG_DIR in $(echo "$MATRIX" | jq -r '.[] | .dir'); do
echo "Bumping version for $PKG_DIR..."
cd "packages/$PKG_DIR"
OLD_VERSION=$(node -p "require('./package.json').version")
npm version "$BUMP" --no-git-tag-version
NEW_VERSION=$(node -p "require('./package.json').version")
Expand All @@ -161,7 +181,7 @@ jobs:
else
VERSIONS_JSON+=","
fi
VERSIONS_JSON+="\"$PKG_NAME\":\"$NEW_VERSION\""
VERSIONS_JSON+="\"$PKG_DIR\":\"$NEW_VERSION\""

cd ../..
done
Expand All @@ -186,8 +206,8 @@ jobs:
PUBLISHED=""
SKIPPED=""

for PKG_NAME in $(echo "$MATRIX" | jq -r '.[] | .name'); do
cd "packages/$PKG_NAME"
for PKG_DIR in $(echo "$MATRIX" | jq -r '.[] | .dir'); do
cd "packages/$PKG_DIR"

NPM_NAME=$(node -p "require('./package.json').name")
VERSION=$(node -p "require('./package.json').version")
Expand Down Expand Up @@ -220,9 +240,9 @@ jobs:
env:
MATRIX: ${{ needs.detect-changes.outputs.matrix }}
run: |
for PKG_NAME in $(echo "$MATRIX" | jq -r '.[] | .name'); do
NPM_NAME=$(node -p "require('./packages/$PKG_NAME/package.json').name")
VERSION=$(node -p "require('./packages/$PKG_NAME/package.json').version")
for PKG_DIR in $(echo "$MATRIX" | jq -r '.[] | .dir'); do
NPM_NAME=$(node -p "require('./packages/$PKG_DIR/package.json').name")
VERSION=$(node -p "require('./packages/$PKG_DIR/package.json').version")
TAG="${NPM_NAME}@${VERSION}"

if git rev-parse "$TAG" >/dev/null 2>&1; then
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"build": "turbo run build",
"dev": "turbo run dev",
"test": "turbo run test",
"typecheck": "tsc --build",
"lint": "tsc && oxfmt --check . && oxlint .",
"typecheck": "turbo run typecheck",
"lint": "turbo run typecheck && oxfmt --check . && oxlint .",
"lint:fix": "oxfmt --write . && oxlint --fix ."
},
"devDependencies": {
Expand Down
3 changes: 2 additions & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@
"build": "vite build",
"dev": "vite build --watch",
"test": "vitest run",
"prepublishOnly": "pnpm build"
"prepublishOnly": "pnpm build",
"typecheck": "tsc --noEmit"
},
"devDependencies": {
"@types/react": "^19.0.0",
Expand Down
3 changes: 2 additions & 1 deletion packages/core/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"]
"include": ["src"],
"exclude": ["src/**/*.test.*"]
}
52 changes: 52 additions & 0 deletions packages/react-router-cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# @react-router-modules/cli

Scaffolding CLI for the Reactive modular framework. Creates projects, modules, and stores with full wiring.

## Commands

```bash
reactive init <name> --scope @myorg --module dashboard # New project
reactive create module <name> --route billing # New module
reactive create store <name> # New Zustand store
```

All commands support interactive (prompts) and non-interactive (flags) modes. See the [main README](../../README.md#cli-reference) for full documentation.

## Development

Requires Node.js 24+.

```bash
pnpm build # Compile TypeScript
pnpm dev # Watch mode
```

## Testing

### Unit tests (cli-testlab)

Tests CLI commands by executing them as child processes and asserting on output and generated files.

```bash
pnpm test
```

### E2E tests (Playwright)

Smoke tests that validate the full framework end-to-end: scaffold a project via CLI, start the dev server, and interact with the served UI using Playwright.

```bash
pnpm test:e2e:setup # Scaffold project, build framework, install deps
pnpm test:e2e:server # Start vite dev server on port 5188 (run in background)
pnpm test:e2e # Run Playwright tests against the running server
```

The setup script uses `link:` overrides to point `@react-router-modules/core` and `@react-router-modules/runtime` to the local built packages (since they aren't published to npm yet).

To re-scaffold from scratch:

```bash
pnpm clean # Remove dist + test artifacts
pnpm build # Rebuild CLI
pnpm test:e2e:setup # Re-scaffold
```
46 changes: 46 additions & 0 deletions packages/react-router-cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"name": "@react-router-modules/cli",
"version": "1.0.0",
"repository": {
"type": "git",
"url": "git+https://github.com/kibertoad/modular-react.git",
"directory": "packages/react-router-cli"
},
"bin": {
"reactive": "./dist/cli.js"
},
"files": [
"dist"
],
"type": "module",
"publishConfig": {
"access": "public"
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch",
"test": "vitest run",
"test:e2e:setup": "node scripts/e2e-setup.ts",
"test:e2e:server": "cd .e2e-output/smoke-app/shell && node node_modules/vite/bin/vite.js --port 5188",
"test:e2e": "playwright test smoke.e2e.ts",
"clean": "rimraf dist .test-output .e2e-output",
"prepublishOnly": "pnpm build",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@clack/prompts": "^1.1.0",
"citty": "^0.2.1",
"magicast": "^0.5.2",
"pathe": "^2.0.3"
},
"devDependencies": {
"@playwright/test": "^1.58.2",
"@types/node": "^25.5.0",
"cli-testlab": "^6.0.1",
"typescript": "^6.0.2",
"vitest": "^4.1.0"
},
"engines": {
"node": ">=24.0.0"
}
}
12 changes: 12 additions & 0 deletions packages/react-router-cli/playwright.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { defineConfig } from "@playwright/test";

export default defineConfig({
testDir: "./test",
testMatch: "**/*.e2e.ts",
timeout: 30_000,
retries: 0,
use: {
headless: true,
baseURL: "http://localhost:5188",
},
});
41 changes: 41 additions & 0 deletions packages/react-router-cli/scripts/e2e-setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/usr/bin/env node

import { execSync } from "node:child_process";
import { mkdirSync, existsSync, readFileSync, writeFileSync } from "node:fs";
import { resolve } from "node:path";

const CLI = resolve(import.meta.dirname, "..", "dist", "cli.js");
const REPO_ROOT = resolve(import.meta.dirname, "..", "..", "..");
const TMP = resolve(import.meta.dirname, "..", ".e2e-output");
const PROJECT_DIR = resolve(TMP, "smoke-app");

function exec(cmd: string, cwd?: string) {
execSync(cmd, { cwd, stdio: "inherit", timeout: 120_000 });
}

// Skip if already set up
if (existsSync(resolve(PROJECT_DIR, "node_modules"))) {
console.log('E2E project already exists. Run "pnpm clean" first to re-scaffold.');
process.exit(0);
}

mkdirSync(TMP, { recursive: true });

exec(`node ${CLI} init smoke-app --scope @smoke --module dashboard`, TMP);

// Build framework packages
exec("pnpm -r run build", REPO_ROOT);

// Override @react-router-modules/* to local (not yet on npm)
const rootPkg = JSON.parse(readFileSync(resolve(PROJECT_DIR, "package.json"), "utf-8"));
rootPkg.pnpm = {
overrides: {
"@react-router-modules/core": `link:${resolve(REPO_ROOT, "packages", "core")}`,
"@react-router-modules/runtime": `link:${resolve(REPO_ROOT, "packages", "runtime")}`,
},
};
writeFileSync(resolve(PROJECT_DIR, "package.json"), JSON.stringify(rootPkg, null, 2));

exec("pnpm install", PROJECT_DIR);

console.log("E2E setup complete.");
31 changes: 31 additions & 0 deletions packages/react-router-cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/usr/bin/env node

import { defineCommand, runMain } from "citty";
import init from "./commands/init.js";
import createModule from "./commands/create-module.js";
import createStore from "./commands/create-store.js";

const create = defineCommand({
meta: {
name: "create",
description: "Create a new module or store",
},
subCommands: {
module: createModule,
store: createStore,
},
});

const main = defineCommand({
meta: {
name: "reactive",
version: "0.1.0",
description: "Reactive framework CLI",
},
subCommands: {
init,
create,
},
});

runMain(main);
Loading
Loading