create-bluecopa-react-app 1.0.44 → 1.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +132 -110
- package/bin/create-bluecopa-react-app.js +23 -21
- package/package.json +1 -1
- package/templates/latest/CLAUDE.md +24 -0
- package/templates/latest/app/app.tsx +5 -1
- package/templates/latest/app/contexts/app-context.tsx +11 -3
- package/templates/latest/app/hooks/use-permissions.ts +41 -0
- package/templates/latest/app/single-spa.tsx +2 -0
- package/templates/latest/app/types/index.ts +7 -0
- package/templates/latest/package-lock.json +544 -975
- package/templates/latest/package.json +7 -7
package/README.md
CHANGED
|
@@ -2,26 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
## A CLI Tool for Bluecopa React Applications
|
|
4
4
|
|
|
5
|
-
A command-line interface for
|
|
5
|
+
A command-line interface for scaffolding a Bluecopa microfrontend (MFE) React
|
|
6
|
+
application, pre-wired for the Bluecopa platform: `@bluecopa/react` data hooks,
|
|
7
|
+
single-spa + Module Federation mounting, the Dream Light design system on
|
|
8
|
+
Tailwind CSS v4, AG Grid tables and ECharts charts.
|
|
9
|
+
|
|
10
|
+
> The generated app is a **single-spa / Module Federation microfrontend** that
|
|
11
|
+
> mounts inside the Bluecopa shell (it also runs standalone for local dev). It is
|
|
12
|
+
> not a plain React Router SPA.
|
|
6
13
|
|
|
7
14
|
## Table of Contents
|
|
8
15
|
|
|
9
|
-
- [Version](#version)
|
|
10
16
|
- [Installation](#installation)
|
|
11
17
|
- [Requirements](#requirements)
|
|
12
|
-
- [Configuration](#configuration)
|
|
13
|
-
- [Features](#features)
|
|
14
18
|
- [Usage](#usage)
|
|
15
|
-
- [
|
|
16
|
-
- [
|
|
19
|
+
- [CLI Options](#cli-options)
|
|
20
|
+
- [Interactive Prompts](#interactive-prompts)
|
|
21
|
+
- [Features](#features)
|
|
22
|
+
- [Generated App](#generated-app)
|
|
23
|
+
- [Tech Stack](#tech-stack)
|
|
24
|
+
- [Project Structure](#project-structure)
|
|
25
|
+
- [Scripts](#scripts)
|
|
17
26
|
- [Development](#development)
|
|
18
|
-
- [
|
|
27
|
+
- [Monorepo Integration](#monorepo-integration)
|
|
19
28
|
- [License](#license)
|
|
20
29
|
|
|
21
|
-
## Version
|
|
22
|
-
|
|
23
|
-
Current version: 1.0.5
|
|
24
|
-
|
|
25
30
|
## Installation
|
|
26
31
|
|
|
27
32
|
```bash
|
|
@@ -30,142 +35,159 @@ npm install -g create-bluecopa-react-app
|
|
|
30
35
|
pnpm add -g create-bluecopa-react-app
|
|
31
36
|
```
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
- Node.js >= 18.0.0
|
|
36
|
-
- npm >= 9.0.0 or pnpm >= 8.0.0
|
|
37
|
-
|
|
38
|
-
## Configuration
|
|
39
|
-
|
|
40
|
-
The CLI uses environment variables for configuration:
|
|
38
|
+
Or run without installing:
|
|
41
39
|
|
|
42
40
|
```bash
|
|
43
|
-
|
|
44
|
-
export BLUECOPA_BOILERPLATE_REGISTRY=https://registry.npmjs.org
|
|
41
|
+
npx create-bluecopa-react-app my-dashboard
|
|
45
42
|
```
|
|
46
43
|
|
|
47
|
-
##
|
|
44
|
+
## Requirements
|
|
48
45
|
|
|
49
|
-
-
|
|
50
|
-
-
|
|
51
|
-
- 📊 **Data Visualization**: Recharts integration for charts and analytics
|
|
52
|
-
- 🎨 **UI Components**: shadcn/ui components built on Radix UI primitives
|
|
53
|
-
- 📡 **Bluecopa React Components**: Pre-configured [`@bluecopa/react`](packages/react:1) package with hooks (includes TanStack Query integration)
|
|
54
|
-
- 🎯 **Type Safety**: Full TypeScript support
|
|
55
|
-
- 📱 **Responsive Design**: Tailwind CSS v4 with mobile-first approach and Bluecopa design system
|
|
56
|
-
- 🛠️ **Development Tools**: ESLint, TypeScript checking, and Vite fast build system
|
|
57
|
-
- ⚡ **Fast Development**: Hot module replacement with Vite
|
|
58
|
-
- 🔄 **Server-side Rendering**: React Router v7 with SSR support
|
|
59
|
-
- 🌙 **Dark Mode**: Built-in theme switching with next-themes
|
|
60
|
-
- 🗂️ **Data Tables**: TanStack Table with sorting, filtering, and pagination
|
|
61
|
-
- 🎯 **Drag & Drop**: DND Kit integration for interactive interfaces
|
|
46
|
+
- Node.js >= 18.0.0
|
|
47
|
+
- npm >= 9, or pnpm (recommended). yarn is also supported.
|
|
62
48
|
|
|
63
49
|
## Usage
|
|
64
50
|
|
|
65
|
-
### Creating a New Application
|
|
66
|
-
|
|
67
51
|
```bash
|
|
68
|
-
#
|
|
69
|
-
create-bluecopa-react-app
|
|
52
|
+
# Interactive — prompts for name, template, git, package manager, install
|
|
53
|
+
create-bluecopa-react-app
|
|
70
54
|
|
|
71
|
-
#
|
|
72
|
-
create-bluecopa-react-app my-dashboard --
|
|
55
|
+
# Non-interactive
|
|
56
|
+
create-bluecopa-react-app my-dashboard --yes
|
|
73
57
|
|
|
74
|
-
#
|
|
75
|
-
|
|
76
|
-
|
|
58
|
+
# Explicit options
|
|
59
|
+
create-bluecopa-react-app my-app --package-manager pnpm --no-git
|
|
60
|
+
create-bluecopa-react-app my-app --skip-install
|
|
77
61
|
```
|
|
78
62
|
|
|
79
|
-
|
|
63
|
+
## CLI Options
|
|
80
64
|
|
|
81
|
-
-
|
|
82
|
-
- **shadcn Template**: Full-featured template with shadcn/ui components, data tables, charts, and dark mode support
|
|
65
|
+
Positional argument: `[project-name]` — name of the project directory (prompted if omitted).
|
|
83
66
|
|
|
84
|
-
|
|
67
|
+
| Option | Alias | Description | Default |
|
|
68
|
+
| ---------------------------------- | ----- | -------------------------------------- | ---------- |
|
|
69
|
+
| `--template <template>` | `-t` | Template to use (only `latest` exists) | `latest` |
|
|
70
|
+
| `--package-manager <manager>` | | Package manager: `npm`, `yarn`, `pnpm` | `auto` |
|
|
71
|
+
| `--skip-install` | | Skip dependency installation | installs |
|
|
72
|
+
| `--git` / `--no-git` | | Initialize (or skip) a git repository | `--git` |
|
|
73
|
+
| `--yes` | | Skip all prompts and use defaults | off |
|
|
74
|
+
| `--typescript` / `--no-typescript` | | Template language flag (see note) | TypeScript |
|
|
75
|
+
| `--version` | | Print the CLI version | |
|
|
76
|
+
| `--help` | | Show help | |
|
|
85
77
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
| `--template` | Specify template (default, shadcn) |
|
|
90
|
-
| `--help` | Show help information |
|
|
78
|
+
> **Note:** only a single TypeScript template (`latest`) ships today. The
|
|
79
|
+
> `--typescript` / `--no-typescript` flags are parsed but there is no JavaScript
|
|
80
|
+
> template, so they have no effect.
|
|
91
81
|
|
|
92
|
-
##
|
|
82
|
+
## Interactive Prompts
|
|
93
83
|
|
|
94
|
-
|
|
84
|
+
When `--yes` is not passed, the CLI prompts (via inquirer) in order:
|
|
95
85
|
|
|
96
|
-
|
|
86
|
+
1. **Project name** — defaults to a random `my-<adjective>-<noun>`; validated against npm naming rules.
|
|
87
|
+
2. **Template** — `latest` (Bluecopa MFE React app).
|
|
88
|
+
3. **Initialize git repository?** — default yes (unless `--no-git`).
|
|
89
|
+
4. **Package manager** — pnpm (recommended) / npm / auto-detect.
|
|
90
|
+
5. **Install dependencies now?** — default depends on `--skip-install`.
|
|
97
91
|
|
|
98
|
-
|
|
92
|
+
If the target directory already exists, it asks to overwrite (auto-overwrites with `--yes`). After copying the template it sets `package.json` `name`, replaces `{{APP_NAME}}` placeholders in the HTML entry points, and (when git is enabled) writes a `.gitignore` and creates an initial commit.
|
|
99
93
|
|
|
100
|
-
|
|
101
|
-
- `options`: CLI options (see above)
|
|
94
|
+
## Features
|
|
102
95
|
|
|
103
|
-
**
|
|
96
|
+
- ⚛️ **React 18 + TypeScript** with the SWC-based Vite plugin
|
|
97
|
+
- 🧩 **Microfrontend-ready** — single-spa mount/unmount + Module Federation (`@originjs/vite-plugin-federation`), SystemJS output
|
|
98
|
+
- 📡 **Bluecopa data layer** — pre-configured [`@bluecopa/react`](https://www.npmjs.com/package/@bluecopa/react) hooks (TanStack Query under the hood)
|
|
99
|
+
- 🎨 **Dream Light design system** on **Tailwind CSS v4** with the `copa:` prefix, plus shadcn/ui (`new-york`) + Radix UI primitives
|
|
100
|
+
- 🗂️ **AG Grid** tables (community + enterprise) with a server-side datasource helper
|
|
101
|
+
- 📊 **ECharts** charts via a `ChartProvider` and themed chart components
|
|
102
|
+
- 🔔 **Toasts** with sonner, icons from lucide-react and Tabler
|
|
103
|
+
- 🧭 **React Router v7** routing
|
|
104
|
+
- ⚡ **Vite** dev server + fast build, `~/` path alias to `app/`
|
|
105
|
+
- 🐳 **Deployment ready** — Dockerfile and `.env.example` included
|
|
104
106
|
|
|
105
|
-
|
|
106
|
-
- Installs dependencies
|
|
107
|
-
- Configures environment
|
|
107
|
+
## Generated App
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
### Tech Stack
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
| Area | Libraries |
|
|
112
|
+
| ------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
113
|
+
| Framework | React `^18.3.1`, react-dom `^18.3.1`, react-router-dom `^7.16.0` |
|
|
114
|
+
| Build | Vite `8.0.16`, `@vitejs/plugin-react-swc`, `vite-tsconfig-paths`, TypeScript `^5.8.3` |
|
|
115
|
+
| Data layer | `@bluecopa/react` `0.1.117` (TanStack Query) |
|
|
116
|
+
| Styling | Tailwind CSS v4 (`@tailwindcss/vite`), `tw-animate-css`, `cva`, `clsx`, `tailwind-merge` |
|
|
117
|
+
| UI | shadcn/ui (`new-york`), Radix UI (dialog, dropdown, tabs, tooltip, …), lucide-react, `@tabler/icons-react`, sonner |
|
|
118
|
+
| Tables | `ag-grid-community`, `ag-grid-enterprise`, `ag-grid-react` (v34) |
|
|
119
|
+
| Charts | `echarts` v6 + `echarts-for-react` (also `ag-charts-enterprise` available) |
|
|
120
|
+
| Microfrontend | `single-spa` `6.0.3`, `@originjs/vite-plugin-federation` |
|
|
112
121
|
|
|
113
|
-
|
|
114
|
-
create-bluecopa-react-app my-dashboard
|
|
115
|
-
```
|
|
122
|
+
`package.json` `overrides` pin security-sensitive transitive deps: `axios` `1.16.0`, `rollup` `4.59.0`, `ajv` `8.18.0`, `protobufjs` `>=7.5.8 <8`, `postcss` `>=8.5.10`, `ws` `>=8.20.1 <9`.
|
|
116
123
|
|
|
117
|
-
###
|
|
124
|
+
### Project Structure
|
|
118
125
|
|
|
119
|
-
|
|
120
|
-
|
|
126
|
+
The source root is `app/` (aliased as `~/`), not `src/`:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
my-app/
|
|
130
|
+
├── index.html # standalone entry ({{APP_NAME}} placeholder)
|
|
131
|
+
├── preview/index.html # preview build entry
|
|
132
|
+
├── vite.config.ts # dev server :8080, Module Federation, SystemJS output
|
|
133
|
+
├── tsconfig.json # ES2022, bundler resolution, ~/* → app/*
|
|
134
|
+
├── components.json # shadcn config (new-york, copa prefix, @bluecopa-ui registry)
|
|
135
|
+
├── Dockerfile, .env.example, .gitignore
|
|
136
|
+
├── CLAUDE.md, Agent.md # agent/dev guides
|
|
137
|
+
├── public/ # favicons
|
|
138
|
+
└── app/
|
|
139
|
+
├── app.tsx # root: QueryClient, ChartProvider, AppProvider, MFE config
|
|
140
|
+
├── main.tsx # standalone BrowserRouter entry
|
|
141
|
+
├── single-spa.tsx # MFE MemoryRouter entry (manualMount / manualUnmount)
|
|
142
|
+
├── app.css # Tailwind v4 + Dream Light theme tokens
|
|
143
|
+
├── components/ # bluecopa-logo, error-boundary, loading-screen
|
|
144
|
+
│ ├── ui/ # shadcn components (button, card, sidebar, ag-grid-table, …)
|
|
145
|
+
│ ├── charts/ # ECharts: bar-chart, donut-chart, chart-provider, chart-theme
|
|
146
|
+
│ └── layouts/ # app-layout, app-sidebar, nav-main, nav-user, site-header
|
|
147
|
+
├── contexts/ # app-context
|
|
148
|
+
├── hooks/ # use-mobile, use-permissions
|
|
149
|
+
├── pages/ # dashboard, payments, settings
|
|
150
|
+
├── routes/ # index route table
|
|
151
|
+
├── constants/, types/ # MfeProps, EnvConfig, …
|
|
152
|
+
└── utils/ # ag-grid-datasource, ag-grid-license, ag-grid-theme,
|
|
153
|
+
# component-style, style-drivers, cn()
|
|
121
154
|
```
|
|
122
155
|
|
|
123
|
-
|
|
156
|
+
The Vite config exposes `./App` → `app/single-spa.tsx` under the federation name
|
|
157
|
+
`__copa_ext_app__react_route` (`remoteEntry.js`), externalizes `react`/`react-dom`,
|
|
158
|
+
and emits SystemJS (`format: "system"`) so the Bluecopa shell can mount it.
|
|
124
159
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
160
|
+
### Scripts
|
|
161
|
+
|
|
162
|
+
| Script | Command | Purpose |
|
|
163
|
+
| ----------- | ------------------------------------ | ----------------------------- |
|
|
164
|
+
| `dev` | `vite` | Dev server on port 8080 |
|
|
165
|
+
| `build` | `tsc && vite build` | Type-check + production build |
|
|
166
|
+
| `start` | `npx serve dist -l 3001 --cors` | Serve the production build |
|
|
167
|
+
| `preview` | `npx serve ./preview -l 8080 --cors` | Serve the preview build |
|
|
168
|
+
| `typecheck` | `tsc` | Type-check without emitting |
|
|
128
169
|
|
|
129
170
|
## Development
|
|
130
171
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
- `npm run dev` - Start Vite development server with hot reload
|
|
134
|
-
- `npm run build` - Build for production (TypeScript + Vite optimization)
|
|
135
|
-
- `npm run start` - Start production build locally
|
|
136
|
-
- `npm run preview` - Preview production build locally
|
|
137
|
-
- `npm run lint` - Run ESLint for code quality
|
|
138
|
-
- `npm run lint:fix` - Fix linting issues
|
|
139
|
-
- `npm run type-check` - Run TypeScript type checking without emitting files
|
|
140
|
-
- `npm run clean` - Clean build artifacts and dependencies
|
|
141
|
-
|
|
142
|
-
### Technologies Used
|
|
143
|
-
|
|
144
|
-
- **React 18** - Modern React with concurrent features
|
|
145
|
-
- **React Router v7** - Modern routing with SSR support
|
|
146
|
-
- **Vite 6** - Fast build tool and development server
|
|
147
|
-
- **TypeScript** - Full type safety and developer experience
|
|
148
|
-
- **[`@bluecopa/react`](packages/react:1)** - Bluecopa-specific React components and hooks
|
|
149
|
-
- **shadcn/ui** - Accessible component library built on Radix UI
|
|
150
|
-
- **Radix UI** - Unstyled, accessible UI primitives
|
|
151
|
-
- **TanStack Table** - Headless table library for React
|
|
152
|
-
- **Recharts** - Composable charting library for React
|
|
153
|
-
- **DND Kit** - Drag and drop toolkit for React
|
|
154
|
-
- **Tailwind CSS v4** - Utility-first CSS framework with Bluecopa design system
|
|
155
|
-
- **next-themes** - Theme switching with dark mode support
|
|
156
|
-
- **Lucide React** - Beautiful, customizable icons
|
|
172
|
+
Working on the CLI itself (inside the monorepo):
|
|
157
173
|
|
|
158
|
-
|
|
174
|
+
```bash
|
|
175
|
+
# From packages/boilerplate/react
|
|
176
|
+
node bin/create-bluecopa-react-app.js my-test-app # run the CLI locally
|
|
177
|
+
pnpm format # format with Prettier
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The template lives in `templates/latest/`. Edit files there to change what gets
|
|
181
|
+
scaffolded. Keep the CLI's `.version()` in `bin/create-bluecopa-react-app.js` in
|
|
182
|
+
sync with `package.json` when publishing.
|
|
159
183
|
|
|
160
|
-
|
|
184
|
+
## Monorepo Integration
|
|
161
185
|
|
|
162
|
-
|
|
186
|
+
This CLI is part of the Bluecopa UI monorepo and pairs with:
|
|
163
187
|
|
|
164
|
-
|
|
188
|
+
- [`@bluecopa/react`](https://www.npmjs.com/package/@bluecopa/react) — React Query hooks over the Bluecopa API
|
|
189
|
+
- [`@bluecopa/core`](https://www.npmjs.com/package/@bluecopa/core) — the underlying TypeScript API SDK
|
|
165
190
|
|
|
166
|
-
|
|
191
|
+
## License
|
|
167
192
|
|
|
168
|
-
|
|
169
|
-
2. **Environment setup**: Includes proper environment variable configuration
|
|
170
|
-
3. **Build optimization**: Uses Vite for fast development and production builds
|
|
171
|
-
4. **Deployment ready**: Includes Docker configuration and deployment guides
|
|
193
|
+
MIT
|
|
@@ -17,7 +17,7 @@ const __dirname = path.dirname(__filename);
|
|
|
17
17
|
const program = new Command();
|
|
18
18
|
|
|
19
19
|
program
|
|
20
|
-
.version("1.0.
|
|
20
|
+
.version("1.0.45")
|
|
21
21
|
.name("create-bluecopa-react-app")
|
|
22
22
|
.description("Create a new Bluecopa React application")
|
|
23
23
|
.argument("[project-name]", "Name of the project")
|
|
@@ -28,7 +28,7 @@ program
|
|
|
28
28
|
.option(
|
|
29
29
|
"--package-manager <manager>",
|
|
30
30
|
"Package manager to use (npm, yarn, pnpm)",
|
|
31
|
-
"auto"
|
|
31
|
+
"auto",
|
|
32
32
|
)
|
|
33
33
|
.option("--git", "Initialize git repository", true)
|
|
34
34
|
.option("--no-git", "Skip git initialization")
|
|
@@ -49,7 +49,7 @@ Templates:
|
|
|
49
49
|
|
|
50
50
|
For more information, visit:
|
|
51
51
|
https://github.com/bluecopa/blui/tree/dev/packages/boilerplate/react
|
|
52
|
-
|
|
52
|
+
`,
|
|
53
53
|
)
|
|
54
54
|
.action(async (projectName, options) => {
|
|
55
55
|
try {
|
|
@@ -178,7 +178,7 @@ async function createApp(projectName, options) {
|
|
|
178
178
|
// When using --yes flag, validate required options
|
|
179
179
|
if (!appName) {
|
|
180
180
|
console.error(
|
|
181
|
-
chalk.red("Project name is required when using --yes flag")
|
|
181
|
+
chalk.red("Project name is required when using --yes flag"),
|
|
182
182
|
);
|
|
183
183
|
process.exit(1);
|
|
184
184
|
}
|
|
@@ -189,7 +189,7 @@ async function createApp(projectName, options) {
|
|
|
189
189
|
if (!validTemplates.includes(selectedTemplate)) {
|
|
190
190
|
console.error(chalk.red(`Invalid template: ${selectedTemplate}`));
|
|
191
191
|
console.error(
|
|
192
|
-
chalk.yellow(`Available templates: ${validTemplates.join(", ")}`)
|
|
192
|
+
chalk.yellow(`Available templates: ${validTemplates.join(", ")}`),
|
|
193
193
|
);
|
|
194
194
|
process.exit(1);
|
|
195
195
|
}
|
|
@@ -199,12 +199,12 @@ async function createApp(projectName, options) {
|
|
|
199
199
|
if (!validation.validForNewPackages) {
|
|
200
200
|
console.error(
|
|
201
201
|
chalk.red("Invalid project name:"),
|
|
202
|
-
validation.errors?.[0] || validation.warnings?.[0]
|
|
202
|
+
validation.errors?.[0] || validation.warnings?.[0],
|
|
203
203
|
);
|
|
204
204
|
console.error(
|
|
205
205
|
chalk.yellow(
|
|
206
|
-
"Project names must be lowercase, contain no spaces, and follow npm naming conventions."
|
|
207
|
-
)
|
|
206
|
+
"Project names must be lowercase, contain no spaces, and follow npm naming conventions.",
|
|
207
|
+
),
|
|
208
208
|
);
|
|
209
209
|
process.exit(1);
|
|
210
210
|
}
|
|
@@ -215,7 +215,7 @@ async function createApp(projectName, options) {
|
|
|
215
215
|
if (fs.existsSync(targetDir)) {
|
|
216
216
|
if (options.yes) {
|
|
217
217
|
console.log(
|
|
218
|
-
chalk.yellow(`Directory ${appName} already exists. Overwriting...`)
|
|
218
|
+
chalk.yellow(`Directory ${appName} already exists. Overwriting...`),
|
|
219
219
|
);
|
|
220
220
|
await fs.remove(targetDir);
|
|
221
221
|
} else {
|
|
@@ -238,7 +238,9 @@ async function createApp(projectName, options) {
|
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
console.log(
|
|
241
|
-
chalk.blue(
|
|
241
|
+
chalk.blue(
|
|
242
|
+
`Creating a new Bluecopa React app in ${chalk.green(targetDir)}`,
|
|
243
|
+
),
|
|
242
244
|
);
|
|
243
245
|
console.log(chalk.gray(`Template: ${selectedTemplate}`));
|
|
244
246
|
console.log();
|
|
@@ -256,15 +258,15 @@ async function createApp(projectName, options) {
|
|
|
256
258
|
if (!options.skipInstall) {
|
|
257
259
|
const detectedPackageManager = await detectPackageManager(packageManager);
|
|
258
260
|
spinner.start(
|
|
259
|
-
`Installing dependencies with ${detectedPackageManager}
|
|
261
|
+
`Installing dependencies with ${detectedPackageManager}...`,
|
|
260
262
|
);
|
|
261
263
|
await installDependencies(targetDir, detectedPackageManager);
|
|
262
264
|
spinner.succeed("Dependencies installed");
|
|
263
265
|
} else {
|
|
264
266
|
console.log(
|
|
265
267
|
chalk.yellow(
|
|
266
|
-
"Skipping dependency installation. You can run it manually later."
|
|
267
|
-
)
|
|
268
|
+
"Skipping dependency installation. You can run it manually later.",
|
|
269
|
+
),
|
|
268
270
|
);
|
|
269
271
|
}
|
|
270
272
|
|
|
@@ -276,7 +278,7 @@ async function createApp(projectName, options) {
|
|
|
276
278
|
}
|
|
277
279
|
|
|
278
280
|
spinner.succeed(
|
|
279
|
-
chalk.green("Success! Created " + appName + " at " + targetDir)
|
|
281
|
+
chalk.green("Success! Created " + appName + " at " + targetDir),
|
|
280
282
|
);
|
|
281
283
|
|
|
282
284
|
console.log();
|
|
@@ -336,8 +338,8 @@ async function createApp(projectName, options) {
|
|
|
336
338
|
console.log(chalk.gray("Need help? Check out the documentation:"));
|
|
337
339
|
console.log(
|
|
338
340
|
chalk.gray(
|
|
339
|
-
" https://github.com/bluecopa/blui/tree/main/packages/boilerplate/react"
|
|
340
|
-
)
|
|
341
|
+
" https://github.com/bluecopa/blui/tree/main/packages/boilerplate/react",
|
|
342
|
+
),
|
|
341
343
|
);
|
|
342
344
|
} catch (error) {
|
|
343
345
|
spinner.fail("Failed to create project");
|
|
@@ -356,7 +358,7 @@ async function createProjectStructure(targetDir, appName, options) {
|
|
|
356
358
|
// Validate template directory exists
|
|
357
359
|
if (!(await fs.pathExists(templateDir))) {
|
|
358
360
|
throw new Error(
|
|
359
|
-
`Template '${options.template}' not found at ${templateDir}
|
|
361
|
+
`Template '${options.template}' not found at ${templateDir}`,
|
|
360
362
|
);
|
|
361
363
|
}
|
|
362
364
|
|
|
@@ -400,8 +402,8 @@ async function detectPackageManager(preferredManager) {
|
|
|
400
402
|
} catch {
|
|
401
403
|
console.log(
|
|
402
404
|
chalk.yellow(
|
|
403
|
-
`Warning: ${preferredManager} not found, falling back to auto-detection
|
|
404
|
-
)
|
|
405
|
+
`Warning: ${preferredManager} not found, falling back to auto-detection`,
|
|
406
|
+
),
|
|
405
407
|
);
|
|
406
408
|
}
|
|
407
409
|
}
|
|
@@ -437,7 +439,7 @@ async function installDependencies(targetDir, packageManager) {
|
|
|
437
439
|
execSync(installCommand, { stdio: "inherit" });
|
|
438
440
|
} catch (error) {
|
|
439
441
|
throw new Error(
|
|
440
|
-
`Failed to install dependencies with ${packageManager}: ${error.message}
|
|
442
|
+
`Failed to install dependencies with ${packageManager}: ${error.message}`,
|
|
441
443
|
);
|
|
442
444
|
}
|
|
443
445
|
}
|
|
@@ -466,7 +468,7 @@ pnpm-debug.log*
|
|
|
466
468
|
if (!fs.existsSync(gitignorePath)) {
|
|
467
469
|
fs.writeFileSync(gitignorePath, gitignoreContent.trimStart());
|
|
468
470
|
}
|
|
469
|
-
|
|
471
|
+
|
|
470
472
|
// Initialize git repository
|
|
471
473
|
execSync("git init", { stdio: "ignore" });
|
|
472
474
|
// Create initial commit
|
package/package.json
CHANGED
|
@@ -35,6 +35,7 @@ interface MfeProps {
|
|
|
35
35
|
basename?: string; // Router basename
|
|
36
36
|
envVars?: EnvConfig; // Runtime env (workbook `storedEnvVariable`) — see below
|
|
37
37
|
schemaMap?: SchemaMap; // Column-name normalization (workbook `storedSchemaMap`)
|
|
38
|
+
permissions?: PermissionsMap; // Logged-in user's authz check (actionId → permission → granted)
|
|
38
39
|
}
|
|
39
40
|
```
|
|
40
41
|
|
|
@@ -60,6 +61,29 @@ It sets the `x-bluecopa-solution-*` headers from `envVars.VITE_BLUECOPA_SOLUTION
|
|
|
60
61
|
`_SOLUTION_BRANCH` / `_SOLUTION_BRANCH_TYPE` — **required by the Input Table V2 SDK
|
|
61
62
|
(`copaInputTableDb`)**. Defaults: branch `main`, type `REMOTE`.
|
|
62
63
|
|
|
64
|
+
### Permissions (authorization)
|
|
65
|
+
|
|
66
|
+
The host injects a `permissions` prop — the logged-in user's live authorization
|
|
67
|
+
check (`actionId → permission → granted`), computed before mount against the
|
|
68
|
+
app's access-control matrix. It's exposed via `useAppContext().permissions` and,
|
|
69
|
+
more conveniently, the `usePermissions()` hook (`app/hooks/use-permissions.ts`):
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import { usePermissions } from "~/hooks/use-permissions";
|
|
73
|
+
|
|
74
|
+
function SendEmailButton() {
|
|
75
|
+
const { can } = usePermissions();
|
|
76
|
+
if (!can("send_email")) return null; // any permission grants it
|
|
77
|
+
return <Button>Send</Button>;
|
|
78
|
+
}
|
|
79
|
+
// can("send_email", "write_with_approval") checks a specific permission.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Fail-closed: `permissions` defaults to `{}` (no model configured / non-owner), so
|
|
83
|
+
`can()` returns `false`. Configure actions + the permission matrix on the app's
|
|
84
|
+
**Settings → Access control** tab in the host; assign users in **Settings →
|
|
85
|
+
Authorization → User Permission**.
|
|
86
|
+
|
|
63
87
|
### Error handling
|
|
64
88
|
|
|
65
89
|
`app/components/error-boundary.tsx` (`RouteErrorBoundary`) wraps the routed tree in `app.tsx`.
|
|
@@ -81,7 +81,11 @@ export default function App(props: MfeProps) {
|
|
|
81
81
|
<QueryClientProvider client={queryClient}>
|
|
82
82
|
<div className="mfe-root copa:min-h-svh copa:animate-in copa:fade-in copa:duration-300">
|
|
83
83
|
<ChartProvider>
|
|
84
|
-
<AppProvider
|
|
84
|
+
<AppProvider
|
|
85
|
+
envConfig={envConfig}
|
|
86
|
+
schemaMap={props.schemaMap}
|
|
87
|
+
permissions={props.permissions}
|
|
88
|
+
>
|
|
85
89
|
<RouteErrorBoundary>
|
|
86
90
|
<RouteConfig />
|
|
87
91
|
</RouteErrorBoundary>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createContext, useContext, useMemo, useState, type ReactNode } from "react";
|
|
2
2
|
import { useUser, useGetAllUsers } from "@bluecopa/react";
|
|
3
|
-
import type { AppUser, EnvConfig, SchemaMap, WorkspaceDataSettings } from "~/types";
|
|
3
|
+
import type { AppUser, EnvConfig, PermissionsMap, SchemaMap, WorkspaceDataSettings } from "~/types";
|
|
4
4
|
import { DEFAULT_WORKSPACE_SETTINGS } from "~/constants";
|
|
5
5
|
|
|
6
6
|
interface AppContextValue {
|
|
@@ -21,6 +21,8 @@ interface AppContextValue {
|
|
|
21
21
|
envConfig: EnvConfig;
|
|
22
22
|
/** Column-name normalization map injected by the host (workbook `storedSchemaMap`). */
|
|
23
23
|
schemaMap: SchemaMap;
|
|
24
|
+
/** Logged-in user's live permissions injected by the host (actionId → permission → granted). */
|
|
25
|
+
permissions: PermissionsMap;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
const AppContext = createContext<AppContextValue | undefined>(undefined);
|
|
@@ -73,15 +75,19 @@ export type AppProviderProps = {
|
|
|
73
75
|
envConfig?: EnvConfig;
|
|
74
76
|
/** Host-injected schema map. Defaults to empty. */
|
|
75
77
|
schemaMap?: SchemaMap;
|
|
78
|
+
/** Host-injected permissions for the logged-in user. Defaults to empty. */
|
|
79
|
+
permissions?: PermissionsMap;
|
|
76
80
|
};
|
|
77
81
|
|
|
78
|
-
/** Stable empty
|
|
82
|
+
/** Stable empty fallbacks so the context-value memo isn't invalidated every render. */
|
|
79
83
|
const EMPTY_SCHEMA_MAP: SchemaMap = {};
|
|
84
|
+
const EMPTY_PERMISSIONS: PermissionsMap = {};
|
|
80
85
|
|
|
81
|
-
export function AppProvider({ children, envConfig, schemaMap }: AppProviderProps) {
|
|
86
|
+
export function AppProvider({ children, envConfig, schemaMap, permissions }: AppProviderProps) {
|
|
82
87
|
const resolvedEnvConfig = (envConfig ??
|
|
83
88
|
(import.meta.env as unknown as EnvConfig)) as EnvConfig;
|
|
84
89
|
const resolvedSchemaMap = schemaMap ?? EMPTY_SCHEMA_MAP;
|
|
90
|
+
const resolvedPermissions = permissions ?? EMPTY_PERMISSIONS;
|
|
85
91
|
const [currencyOverride, setCurrencyOverride] = useState<string | null>(null);
|
|
86
92
|
|
|
87
93
|
const {
|
|
@@ -185,6 +191,7 @@ export function AppProvider({ children, envConfig, schemaMap }: AppProviderProps
|
|
|
185
191
|
setCurrency: setCurrencyOverride,
|
|
186
192
|
envConfig: resolvedEnvConfig,
|
|
187
193
|
schemaMap: resolvedSchemaMap,
|
|
194
|
+
permissions: resolvedPermissions,
|
|
188
195
|
}),
|
|
189
196
|
[
|
|
190
197
|
userWithDetails,
|
|
@@ -202,6 +209,7 @@ export function AppProvider({ children, envConfig, schemaMap }: AppProviderProps
|
|
|
202
209
|
currentWorkspaceSettings,
|
|
203
210
|
resolvedEnvConfig,
|
|
204
211
|
resolvedSchemaMap,
|
|
212
|
+
resolvedPermissions,
|
|
205
213
|
]
|
|
206
214
|
);
|
|
207
215
|
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
import { useAppContext } from "~/contexts/app-context";
|
|
3
|
+
import type { PermissionsMap } from "~/types";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Access the logged-in user's permissions, injected by the host (PRD-3294).
|
|
7
|
+
*
|
|
8
|
+
* The host runs a backend authorization check before mounting this MFE and
|
|
9
|
+
* passes the result as the `permissions` prop — a map of `actionId → permission
|
|
10
|
+
* → granted`. Use the returned `can()` helper to gate UI:
|
|
11
|
+
*
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const { can } = usePermissions();
|
|
14
|
+
* if (can("send_email")) { ... } // any permission grants the action
|
|
15
|
+
* if (can("send_email", "write_with_approval")) { ... } // a specific permission
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* `permissions` is `{}` when the app has no permission model configured, so
|
|
19
|
+
* `can()` returns `false` by default (fail-closed).
|
|
20
|
+
*/
|
|
21
|
+
export function usePermissions() {
|
|
22
|
+
const { permissions } = useAppContext();
|
|
23
|
+
|
|
24
|
+
return useMemo(() => {
|
|
25
|
+
const can = (actionId: string, permission?: string): boolean => {
|
|
26
|
+
const actionPerms = permissions[actionId];
|
|
27
|
+
if (!actionPerms) return false;
|
|
28
|
+
if (permission) return actionPerms[permission] === true;
|
|
29
|
+
// No specific permission asked → granted if ANY permission is true.
|
|
30
|
+
return Object.values(actionPerms).some(Boolean);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/** All permissions granted to the user for an action (the `true` keys). */
|
|
34
|
+
const grantedFor = (actionId: string): string[] =>
|
|
35
|
+
Object.entries(permissions[actionId] ?? {})
|
|
36
|
+
.filter(([, granted]) => granted)
|
|
37
|
+
.map(([permission]) => permission);
|
|
38
|
+
|
|
39
|
+
return { permissions: permissions as PermissionsMap, can, grantedFor };
|
|
40
|
+
}, [permissions]);
|
|
41
|
+
}
|
|
@@ -70,6 +70,8 @@ export async function mount(props: MountProps): Promise<void> {
|
|
|
70
70
|
// normalize API column names at runtime.
|
|
71
71
|
envVars: props.envVars,
|
|
72
72
|
schemaMap: props.schemaMap,
|
|
73
|
+
// Logged-in user's permissions (host ran the backend check pre-mount).
|
|
74
|
+
permissions: props.permissions,
|
|
73
75
|
}}
|
|
74
76
|
/>,
|
|
75
77
|
);
|
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
* Keyed by env-var name → { backendColumn: frontendColumn }. */
|
|
3
3
|
export type SchemaMap = Record<string, Record<string, string>>;
|
|
4
4
|
|
|
5
|
+
/** Logged-in user's live permissions injected by the host (PRD-3294).
|
|
6
|
+
* Keyed by actionId → { permission: granted }. The host computes this with a
|
|
7
|
+
* backend check against the app's access-control matrix before mount. */
|
|
8
|
+
export type PermissionsMap = Record<string, Record<string, boolean>>;
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* Runtime environment config. The host injects these via the `envVars` prop
|
|
7
12
|
* (the app's Settings → Env Vars panel, persisted on the workbook). At
|
|
@@ -34,6 +39,8 @@ export interface MfeProps {
|
|
|
34
39
|
envVars?: EnvConfig;
|
|
35
40
|
/** Column-name normalization map injected by the host (workbook `storedSchemaMap`). */
|
|
36
41
|
schemaMap?: SchemaMap;
|
|
42
|
+
/** Logged-in user's live permissions injected by the host (actionId → permission → granted). */
|
|
43
|
+
permissions?: PermissionsMap;
|
|
37
44
|
}
|
|
38
45
|
|
|
39
46
|
/** Normalized user object provided by AppContext. */
|