@bluealba/platform-cli 1.0.1 → 1.0.2
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/dist/index.js +277 -9
- package/docs/404.mdx +5 -0
- package/docs/architecture/api-explorer.mdx +478 -0
- package/docs/architecture/architecture-diagrams.mdx +12 -0
- package/docs/architecture/authentication-system.mdx +903 -0
- package/docs/architecture/authorization-system.mdx +886 -0
- package/docs/architecture/bootstrap.mdx +1442 -0
- package/docs/architecture/gateway-architecture.mdx +845 -0
- package/docs/architecture/multi-tenancy.mdx +1150 -0
- package/docs/architecture/overview.mdx +776 -0
- package/docs/architecture/scheduler.mdx +818 -0
- package/docs/architecture/shell.mdx +885 -0
- package/docs/architecture/ui-extension-points.mdx +781 -0
- package/docs/architecture/user-states.mdx +794 -0
- package/docs/development/overview.mdx +21 -0
- package/docs/development/workflow.mdx +914 -0
- package/docs/getting-started/core-concepts.mdx +892 -0
- package/docs/getting-started/installation.mdx +780 -0
- package/docs/getting-started/overview.mdx +83 -0
- package/docs/getting-started/quick-start.mdx +940 -0
- package/docs/guides/adding-documentation-sites.mdx +1367 -0
- package/docs/guides/creating-services.mdx +1736 -0
- package/docs/guides/creating-ui-modules.mdx +1860 -0
- package/docs/guides/identity-providers.mdx +1007 -0
- package/docs/guides/mermaid-diagrams.mdx +212 -0
- package/docs/guides/using-feature-flags.mdx +1059 -0
- package/docs/guides/working-with-rooms.mdx +566 -0
- package/docs/index.mdx +57 -0
- package/docs/platform-cli/commands.mdx +604 -0
- package/docs/platform-cli/overview.mdx +195 -0
- package/package.json +5 -2
- package/skills/ba-platform/platform-cli.skill.md +26 -0
- package/skills/ba-platform/platform.skill.md +35 -0
- package/templates/application-monorepo-template/gitignore +95 -0
- package/templates/bootstrap-service-template/gitignore +57 -0
- package/templates/bootstrap-service-template/src/main.ts +6 -16
- package/templates/customization-ui-module-template/gitignore +73 -0
- package/templates/nestjs-service-module-template/gitignore +56 -0
- package/templates/platform-init-template/{{platformName}}-core/gitignore +97 -0
- package/templates/react-ui-module-template/Dockerfile +1 -1
- package/templates/react-ui-module-template/caddy/Caddyfile +1 -1
- package/templates/react-ui-module-template/gitignore +72 -0
|
@@ -0,0 +1,1442 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Platform Bootstrap
|
|
3
|
+
description: How to bootstrap and configure the Blue Alba Platform with applications, modules, roles, operations, and catalog definitions
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
import { Card, CardGrid, Aside, Tabs, TabItem } from '@astrojs/starlight/components';
|
|
7
|
+
|
|
8
|
+
The Blue Alba Platform uses a **bootstrap mechanism** to initialize and configure platform resources such as applications, modules, authorization definitions (roles and operations), and catalog entries. This enables declarative configuration management across environments.
|
|
9
|
+
|
|
10
|
+
## Overview
|
|
11
|
+
|
|
12
|
+
The Platform is a living, evolving system that supports dynamic changes:
|
|
13
|
+
|
|
14
|
+
<CardGrid stagger>
|
|
15
|
+
<Card title="Add Applications" icon="seti:folder">
|
|
16
|
+
Register new business applications with the platform
|
|
17
|
+
</Card>
|
|
18
|
+
|
|
19
|
+
<Card title="Register Services" icon="puzzle">
|
|
20
|
+
Add microservices and micro-frontends to the catalog
|
|
21
|
+
</Card>
|
|
22
|
+
|
|
23
|
+
<Card title="Define Authorization" icon="shield">
|
|
24
|
+
Declare roles, operations, and permissions
|
|
25
|
+
</Card>
|
|
26
|
+
|
|
27
|
+
<Card title="Configure Modules" icon="setting">
|
|
28
|
+
Customize module behavior with configuration properties
|
|
29
|
+
</Card>
|
|
30
|
+
|
|
31
|
+
<Card title="Update Shared Libraries" icon="seti:npm">
|
|
32
|
+
Register shared libraries for UI modules
|
|
33
|
+
</Card>
|
|
34
|
+
|
|
35
|
+
<Card title="Schedule Jobs" icon="seti:clock">
|
|
36
|
+
Define scheduled jobs declaratively for recurring tasks
|
|
37
|
+
</Card>
|
|
38
|
+
|
|
39
|
+
<Card title="Environment Propagation" icon="random">
|
|
40
|
+
Synchronize configurations across development, staging, and production
|
|
41
|
+
</Card>
|
|
42
|
+
</CardGrid>
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Platform Bootstrap Service
|
|
47
|
+
|
|
48
|
+
Every PAE product includes a **platform-bootstrap-service** - a simple web service that contains all platform definitions as JSON data. When this service starts, it automatically synchronizes these definitions with the platform via REST APIs.
|
|
49
|
+
|
|
50
|
+
### Architecture
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
┌─────────────────────────────────────────────────────────┐
|
|
54
|
+
│ Platform Bootstrap Service │
|
|
55
|
+
│ │
|
|
56
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
57
|
+
│ │ data/ │ │
|
|
58
|
+
│ │ ├── app1/ │ │
|
|
59
|
+
│ │ │ ├── application.json │ │
|
|
60
|
+
│ │ │ ├── modules.json │ │
|
|
61
|
+
│ │ │ ├── operations.json │ │
|
|
62
|
+
│ │ │ ├── roles.json │ │
|
|
63
|
+
│ │ │ └── jobs.json │ │
|
|
64
|
+
│ │ ├── app2/ │ │
|
|
65
|
+
│ │ └── platform/ │ │
|
|
66
|
+
│ └────────────────────────────────────────────────┘ │
|
|
67
|
+
│ │ │
|
|
68
|
+
│ ▼ │
|
|
69
|
+
│ ┌────────────────────────────────────────────────┐ │
|
|
70
|
+
│ │ Bootstrap Sync Engine │ │
|
|
71
|
+
│ │ • Compare JSON with DB state │ │
|
|
72
|
+
│ │ • Create/Update/Delete resources │ │
|
|
73
|
+
│ │ • Call Platform REST APIs │ │
|
|
74
|
+
│ └────────────────────────────────────────────────┘ │
|
|
75
|
+
└───────────────────────┬─────────────────────────────────┘
|
|
76
|
+
│
|
|
77
|
+
▼
|
|
78
|
+
┌─────────────────────────────────────────────────────────┐
|
|
79
|
+
│ Blue Alba Platform │
|
|
80
|
+
│ │
|
|
81
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
82
|
+
│ │ Catalog │ │ Authorization│ │ Tenants │ │
|
|
83
|
+
│ │ Service │ │ Service │ │ Service │ │
|
|
84
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
85
|
+
└─────────────────────────────────────────────────────────┘
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Repository Structure
|
|
91
|
+
|
|
92
|
+
The bootstrap service typically follows this structure:
|
|
93
|
+
|
|
94
|
+
```
|
|
95
|
+
platform-bootstrap-service/
|
|
96
|
+
├── src/
|
|
97
|
+
│ └── main.ts # Bootstrap application entry point
|
|
98
|
+
├── data/
|
|
99
|
+
│ ├── app1/ # Business application 1
|
|
100
|
+
│ │ ├── application.json # Application metadata
|
|
101
|
+
│ │ ├── modules.json # Services and UIs
|
|
102
|
+
│ │ ├── operations.json # Authorization operations
|
|
103
|
+
│ │ ├── roles.json # Authorization roles
|
|
104
|
+
│ │ ├── modules-config.json # Module customizations (optional)
|
|
105
|
+
│ │ ├── jobs.json # Scheduled jobs (optional)
|
|
106
|
+
│ │ └── feature-flags.json # Feature flags (optional)
|
|
107
|
+
│ ├── app2/ # Business application 2
|
|
108
|
+
│ └── platform/ # Platform module overrides
|
|
109
|
+
│ └── modules-config.json # Platform module customizations
|
|
110
|
+
├── package.json
|
|
111
|
+
└── README.md
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Folder Purpose
|
|
115
|
+
|
|
116
|
+
<Tabs>
|
|
117
|
+
<TabItem label="Application Folders">
|
|
118
|
+
**Application-Specific Folders** (e.g., `app1/`, `app2/`)
|
|
119
|
+
|
|
120
|
+
Each folder represents a business application and contains:
|
|
121
|
+
- Application metadata
|
|
122
|
+
- Module definitions (services and UIs)
|
|
123
|
+
- Authorization definitions (operations and roles)
|
|
124
|
+
- Module configurations (optional)
|
|
125
|
+
- Scheduled job definitions (optional)
|
|
126
|
+
|
|
127
|
+
**Example**: `app1/` might represent a "CRM" application with customer management services and UIs.
|
|
128
|
+
</TabItem>
|
|
129
|
+
|
|
130
|
+
<TabItem label="Platform Folder">
|
|
131
|
+
**Platform Folder** (`platform/`)
|
|
132
|
+
|
|
133
|
+
Used to override or customize built-in Platform modules (Shell, Admin UI, etc.):
|
|
134
|
+
- Shell customizations (branding, layout)
|
|
135
|
+
- Admin UI configurations
|
|
136
|
+
- Platform service overrides
|
|
137
|
+
|
|
138
|
+
**Note**: You only need to include modules you want to customize, not all platform modules.
|
|
139
|
+
</TabItem>
|
|
140
|
+
</Tabs>
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Using the Bootstrap Library
|
|
145
|
+
|
|
146
|
+
The bootstrap service uses `@bluealba/pae-bootstrap-lib` to read JSON files and synchronize them with the platform. The library exposes a single function — `bootstrapPlatform` — that handles reading, validating, and sending all definitions to the gateway.
|
|
147
|
+
|
|
148
|
+
### Installation
|
|
149
|
+
|
|
150
|
+
```bash
|
|
151
|
+
npm install @bluealba/pae-bootstrap-lib
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### The `main.ts` Entry Point
|
|
155
|
+
|
|
156
|
+
A minimal bootstrap service looks like this:
|
|
157
|
+
|
|
158
|
+
```typescript
|
|
159
|
+
import { bootstrapPlatform } from '@bluealba/pae-bootstrap-lib';
|
|
160
|
+
import { join } from 'path';
|
|
161
|
+
|
|
162
|
+
bootstrapPlatform({
|
|
163
|
+
path: join(__dirname, '../data'),
|
|
164
|
+
scopedTo: process.env.SERVICE_ACCESS_NAME!,
|
|
165
|
+
initialDelay: 5000, // wait 5s for the gateway to be ready
|
|
166
|
+
retries: 10, // retry up to 10 times with exponential backoff
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
The function reads all JSON files from the specified `path`, resolves any `{{$ENV.X}}` expressions, validates the structure, and sends the full platform definition to the gateway via `POST /bootstrap/sync`.
|
|
171
|
+
|
|
172
|
+
### Options
|
|
173
|
+
|
|
174
|
+
| Option | Type | Required | Description |
|
|
175
|
+
|---|---|---|---|
|
|
176
|
+
| `scopedTo` | `string` | **Yes** | Identifies this bootstrap service as the author of all entities it creates or updates. Must be unique per product. Typically set via `SERVICE_ACCESS_NAME` env var. |
|
|
177
|
+
| `path` | `string` | No | Path to the folder containing the `data/` structure. Defaults to `process.cwd()`. |
|
|
178
|
+
| `exclude` | `string[]` | No | List of application folder names to skip during synchronization. |
|
|
179
|
+
| `throwOnError` | `boolean` | No | If `true`, propagates errors to the caller. Defaults to `false` — errors are logged internally and the process stays alive. See note below. |
|
|
180
|
+
| `retries` | `number` | No | Number of retry attempts after the initial failure. Uses exponential backoff starting at 5s, doubling each attempt, capped at 5 minutes. Default: `0`. |
|
|
181
|
+
| `initialDelay` | `number` | No | Delay in ms before the first attempt. Useful to wait for the gateway to be ready before bootstrapping. Default: `0`. |
|
|
182
|
+
|
|
183
|
+
<Aside type="note" title="throwOnError defaults to false">
|
|
184
|
+
In previous versions, `bootstrapPlatform()` always threw on failure, so callers would typically wrap it in `.catch()` or `try/catch`. The default is now `false` — errors are caught internally, logged, and the process continues running. This is the recommended behavior in containerized environments where the service must keep responding to health checks even if the initial bootstrap fails. If you need the previous behavior — for example in a one-shot init container that should exit non-zero on failure — pass `throwOnError: true` to restore it.
|
|
185
|
+
</Aside>
|
|
186
|
+
|
|
187
|
+
### Required Environment Variables
|
|
188
|
+
|
|
189
|
+
The library reads two environment variables at runtime:
|
|
190
|
+
|
|
191
|
+
| Variable | Description |
|
|
192
|
+
|---|---|
|
|
193
|
+
| `GATEWAY_SERVICE_URL` | Base URL of the platform gateway (e.g. `http://pae-gateway:8080`) |
|
|
194
|
+
| `SERVICE_ACCESS_SECRET` | Bearer token used to authenticate the bootstrap request against the gateway |
|
|
195
|
+
| `SERVICE_ACCESS_NAME` | Identifier used as the `scopedTo` value (conventionally passed into the options) |
|
|
196
|
+
|
|
197
|
+
<Aside type="caution" title="scopedTo is critical">
|
|
198
|
+
The `scopedTo` value is used by the platform to track ownership of every entity created by bootstrap. All subsequent runs use it to decide which entities to update or delete. **Never change this value after the first deployment** — doing so will cause the platform to treat all previously bootstrapped entities as orphaned.
|
|
199
|
+
</Aside>
|
|
200
|
+
|
|
201
|
+
### Complete Example
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
import { bootstrapPlatform } from '@bluealba/pae-bootstrap-lib';
|
|
205
|
+
import { join } from 'path';
|
|
206
|
+
|
|
207
|
+
bootstrapPlatform({
|
|
208
|
+
path: join(__dirname, '../data'),
|
|
209
|
+
scopedTo: process.env.SERVICE_ACCESS_NAME!,
|
|
210
|
+
exclude: ['experimental-app'],
|
|
211
|
+
initialDelay: 5000,
|
|
212
|
+
retries: 10,
|
|
213
|
+
// throwOnError defaults to false — errors are logged internally,
|
|
214
|
+
// the process stays alive and the service keeps responding to health checks
|
|
215
|
+
});
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## Configuration Files
|
|
221
|
+
|
|
222
|
+
### Application Definition
|
|
223
|
+
|
|
224
|
+
Declare application metadata in `application.json`:
|
|
225
|
+
|
|
226
|
+
```json
|
|
227
|
+
{
|
|
228
|
+
"name": "crm",
|
|
229
|
+
"displayName": "Customer Relationship Management",
|
|
230
|
+
"description": "CRM application for managing customer relationships",
|
|
231
|
+
"allowedByDefault": false,
|
|
232
|
+
"allowedTenants": ["tenant-a", "tenant-b"]
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
**Fields**:
|
|
237
|
+
- **name**: Unique application identifier (kebab-case)
|
|
238
|
+
- **displayName**: Human-readable name shown in the UI
|
|
239
|
+
- **description**: Brief description of the application
|
|
240
|
+
- **allowedByDefault**: If `true`, all users have access by default; if `false` (recommended), access must be explicitly granted
|
|
241
|
+
- **allowedTenants**: Optional array of tenant names allowed to access this application. If omitted, existing tenant restrictions are not modified by bootstrap. If set to an empty array `[]`, all restrictions are cleared (application becomes available to all tenants). If populated, only the listed tenants will have access.
|
|
242
|
+
|
|
243
|
+
<Aside type="tip" title="Security Best Practice">
|
|
244
|
+
Set `allowedByDefault` to `false` for business applications to ensure explicit access control. Only platform-wide utilities should default to `true`.
|
|
245
|
+
</Aside>
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### Tenant Application Restrictions
|
|
250
|
+
|
|
251
|
+
The `allowedTenants` field in `application.json` lets bootstrap manage which tenants can access each application. This controls the `tenant_application_restrictions` table.
|
|
252
|
+
|
|
253
|
+
**Semantics**:
|
|
254
|
+
- **`allowedTenants` omitted** — bootstrap does not touch existing restrictions for this application (safe default, no regressions).
|
|
255
|
+
- **`allowedTenants: []`** — all existing restrictions are removed; the application becomes available to all tenants.
|
|
256
|
+
- **`allowedTenants: ["tenant-a", "tenant-b"]`** — restrictions are fully replaced; only the listed tenants have access.
|
|
257
|
+
|
|
258
|
+
Each bootstrap run performs a full replace (delete + re-insert) when the field is present, so the table always reflects the latest declaration.
|
|
259
|
+
|
|
260
|
+
```json
|
|
261
|
+
{
|
|
262
|
+
"name": "crm",
|
|
263
|
+
"allowedByDefault": false,
|
|
264
|
+
"allowedTenants": ["acme-corp", "beta-company"]
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
<Aside type="tip" title="Tenant Names">
|
|
269
|
+
Use the tenant `name` field (not display name or ID). Bootstrap resolves names to IDs at sync time and logs a warning for any unrecognized tenant names without failing the sync.
|
|
270
|
+
</Aside>
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
### Module Definitions
|
|
275
|
+
|
|
276
|
+
Define services and UIs in `modules.json`:
|
|
277
|
+
|
|
278
|
+
<Tabs>
|
|
279
|
+
<TabItem label="Service Module">
|
|
280
|
+
**Backend Service (Microservice)**
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
[
|
|
284
|
+
{
|
|
285
|
+
"name": "@mycompany/crm-service",
|
|
286
|
+
"displayName": "CRM Service",
|
|
287
|
+
"type": "service",
|
|
288
|
+
"baseUrl": "/api/crm",
|
|
289
|
+
"service": {
|
|
290
|
+
"host": "crm-service",
|
|
291
|
+
"port": 4002
|
|
292
|
+
},
|
|
293
|
+
"dependsOn": []
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
**Fields**:
|
|
299
|
+
- **name**: Module name (matches package.json name)
|
|
300
|
+
- **displayName**: Human-readable name
|
|
301
|
+
- **type**: Set to `"service"` for backend services
|
|
302
|
+
- **baseUrl**: URL prefix for this service's endpoints
|
|
303
|
+
- **service.host**: Container/service hostname
|
|
304
|
+
- **service.port**: Service port number
|
|
305
|
+
- **dependsOn**: Array of module names this service depends on
|
|
306
|
+
</TabItem>
|
|
307
|
+
|
|
308
|
+
<TabItem label="UI Module">
|
|
309
|
+
**Frontend UI (Micro-frontend)**
|
|
310
|
+
|
|
311
|
+
```json
|
|
312
|
+
[
|
|
313
|
+
{
|
|
314
|
+
"name": "@mycompany/crm-ui",
|
|
315
|
+
"displayName": "CRM Application",
|
|
316
|
+
"type": "app",
|
|
317
|
+
"baseUrl": "/crm-ui",
|
|
318
|
+
"service": {
|
|
319
|
+
"host": "crm-ui",
|
|
320
|
+
"port": 80
|
|
321
|
+
},
|
|
322
|
+
"ui": {
|
|
323
|
+
"route": "/crm",
|
|
324
|
+
"mountAtSelector": "#pae-shell-ui-content",
|
|
325
|
+
"bundleFile": "mycompany-crm-ui.js",
|
|
326
|
+
"customProps": {
|
|
327
|
+
"theme": "corporate",
|
|
328
|
+
"features": {
|
|
329
|
+
"darkMode": true
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
"dependsOn": [
|
|
334
|
+
"@bluealba/pae-shell-ui"
|
|
335
|
+
]
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Fields**:
|
|
341
|
+
- **type**: Set to `"app"` for UI modules
|
|
342
|
+
- **ui.route**: Browser route where this UI is accessible
|
|
343
|
+
- **ui.mountAtSelector**: DOM selector where the UI mounts
|
|
344
|
+
- **ui.bundleFile**: JavaScript bundle filename
|
|
345
|
+
- **ui.customProps**: Configuration passed to the UI module as single-spa custom props. Supports an optional `tenantOverrides` key — see [Tenant-Specific customProps Overrides](#tenant-specific-customprops-overrides) for details.
|
|
346
|
+
- **dependsOn**: Typically depends on `pae-shell-ui`
|
|
347
|
+
</TabItem>
|
|
348
|
+
</Tabs>
|
|
349
|
+
|
|
350
|
+
<Aside type="note">
|
|
351
|
+
See the [Catalog](/architecture/catalog/) documentation for complete module specification reference.
|
|
352
|
+
</Aside>
|
|
353
|
+
|
|
354
|
+
<Aside type="tip">
|
|
355
|
+
If your service runs on a different host or port locally than it does inside Docker, use `serviceLocal` to declare the local-only values without touching the `service` field. See [Local Development Overrides](#local-development-overrides-servicelocal) for details.
|
|
356
|
+
</Aside>
|
|
357
|
+
|
|
358
|
+
---
|
|
359
|
+
|
|
360
|
+
### Operations Definition
|
|
361
|
+
|
|
362
|
+
Declare authorization operations in `operations.json`:
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
[
|
|
366
|
+
{
|
|
367
|
+
"name": "crm.customers.read",
|
|
368
|
+
"description": "View customer information",
|
|
369
|
+
"roles": ["CRM User", "CRM Admin"]
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
"name": "crm.customers.write",
|
|
373
|
+
"description": "Create and update customers",
|
|
374
|
+
"roles": ["CRM Admin"]
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"name": "crm.customers.delete",
|
|
378
|
+
"description": "Delete customer records",
|
|
379
|
+
"roles": ["CRM Admin"]
|
|
380
|
+
}
|
|
381
|
+
]
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
**Fields**:
|
|
385
|
+
- **name**: Unique operation identifier (use dot notation)
|
|
386
|
+
- **description**: What this operation allows
|
|
387
|
+
- **roles**: Array of role names that include this operation
|
|
388
|
+
|
|
389
|
+
**Naming Convention**:
|
|
390
|
+
```
|
|
391
|
+
<application>.<resource>.<action>
|
|
392
|
+
|
|
393
|
+
Examples:
|
|
394
|
+
- crm.customers.read
|
|
395
|
+
- crm.deals.write
|
|
396
|
+
- inventory.products.delete
|
|
397
|
+
- reports.sales.export
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
---
|
|
401
|
+
|
|
402
|
+
### Roles Definition
|
|
403
|
+
|
|
404
|
+
Declare roles in `roles.json`:
|
|
405
|
+
|
|
406
|
+
```json
|
|
407
|
+
[
|
|
408
|
+
{
|
|
409
|
+
"name": "CRM User",
|
|
410
|
+
"description": "Standard CRM user with read access"
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
"name": "CRM Admin",
|
|
414
|
+
"description": "CRM administrator with full access"
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
**Fields**:
|
|
420
|
+
- **name**: Unique role name
|
|
421
|
+
- **description**: What this role represents
|
|
422
|
+
|
|
423
|
+
**Roles vs Operations**:
|
|
424
|
+
- **Operations**: Granular permissions (e.g., `crm.customers.read`)
|
|
425
|
+
- **Roles**: Groups of operations assigned to users
|
|
426
|
+
- Users are assigned **roles**, which grant them **operations**
|
|
427
|
+
|
|
428
|
+
---
|
|
429
|
+
|
|
430
|
+
### Module Configuration Overrides
|
|
431
|
+
|
|
432
|
+
Override module configurations in `modules-config.json`:
|
|
433
|
+
|
|
434
|
+
```json
|
|
435
|
+
[
|
|
436
|
+
{
|
|
437
|
+
"name": "@bluealba/pae-shell-ui",
|
|
438
|
+
"config": {
|
|
439
|
+
"platformName": "My Company Platform",
|
|
440
|
+
"canToggleNavbarState": true,
|
|
441
|
+
"isMenuCollapsible": true,
|
|
442
|
+
"showOpenMenuButton": true,
|
|
443
|
+
"navbar": {
|
|
444
|
+
"menus": {
|
|
445
|
+
"applications": [
|
|
446
|
+
{
|
|
447
|
+
"type": "app",
|
|
448
|
+
"name": "crm",
|
|
449
|
+
"iconUrl": "/crm-ui/assets/icon.svg"
|
|
450
|
+
}
|
|
451
|
+
]
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
{
|
|
457
|
+
"name": "@mycompany/crm-ui",
|
|
458
|
+
"config": {
|
|
459
|
+
"defaultView": "dashboard",
|
|
460
|
+
"enableExport": true
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
]
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
**Purpose**:
|
|
467
|
+
- Customize module behavior without modifying module code
|
|
468
|
+
- Set product-specific branding and features
|
|
469
|
+
- Configure UI layouts and behavior
|
|
470
|
+
- Pass environment-specific settings
|
|
471
|
+
|
|
472
|
+
<Aside type="tip">
|
|
473
|
+
You don't need to re-declare the entire module definition - only include the `name` and the `config` properties you want to override.
|
|
474
|
+
</Aside>
|
|
475
|
+
|
|
476
|
+
---
|
|
477
|
+
|
|
478
|
+
### Tenant-Specific customProps Overrides
|
|
479
|
+
|
|
480
|
+
The `ui.customProps` object supports an optional reserved key, `tenantOverrides`, that lets you vary the props delivered to a UI module on a per-tenant basis — all from a single module definition.
|
|
481
|
+
|
|
482
|
+
#### How It Works
|
|
483
|
+
|
|
484
|
+
When the gateway builds the catalog response for a tenant, it:
|
|
485
|
+
|
|
486
|
+
1. Reads the module's `ui.customProps` object.
|
|
487
|
+
2. Looks up the current tenant's **name** in the `tenantOverrides` map.
|
|
488
|
+
3. Shallow-merges the matching tenant's overrides on top of the base `customProps`.
|
|
489
|
+
4. Strips the `tenantOverrides` key from the response — clients never see it.
|
|
490
|
+
|
|
491
|
+
If no entry exists for the current tenant in `tenantOverrides`, the base `customProps` values are returned unchanged (still without the `tenantOverrides` key).
|
|
492
|
+
|
|
493
|
+
#### Example
|
|
494
|
+
|
|
495
|
+
```json
|
|
496
|
+
{
|
|
497
|
+
"name": "@mycompany/crm-ui",
|
|
498
|
+
"displayName": "CRM Application",
|
|
499
|
+
"type": "app",
|
|
500
|
+
"baseUrl": "/crm-ui",
|
|
501
|
+
"service": {
|
|
502
|
+
"host": "crm-ui",
|
|
503
|
+
"port": 80
|
|
504
|
+
},
|
|
505
|
+
"ui": {
|
|
506
|
+
"route": "/crm",
|
|
507
|
+
"mountAtSelector": "#pae-shell-ui-content",
|
|
508
|
+
"bundleFile": "mycompany-crm-ui.js",
|
|
509
|
+
"customProps": {
|
|
510
|
+
"platformName": "My Company Platform",
|
|
511
|
+
"canToggleNavbarState": true,
|
|
512
|
+
"isMenuCollapsible": true,
|
|
513
|
+
"tenantOverrides": {
|
|
514
|
+
"acme-corp": {
|
|
515
|
+
"isMenuCollapsible": false,
|
|
516
|
+
"platformName": "ACME Platform"
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
},
|
|
521
|
+
"dependsOn": ["@bluealba/pae-shell-ui"]
|
|
522
|
+
}
|
|
523
|
+
```
|
|
524
|
+
|
|
525
|
+
When the requesting tenant is `acme-corp`, the gateway returns:
|
|
526
|
+
|
|
527
|
+
```json
|
|
528
|
+
{
|
|
529
|
+
"platformName": "ACME Platform",
|
|
530
|
+
"canToggleNavbarState": true,
|
|
531
|
+
"isMenuCollapsible": false
|
|
532
|
+
}
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
When the requesting tenant is anything else, the gateway returns:
|
|
536
|
+
|
|
537
|
+
```json
|
|
538
|
+
{
|
|
539
|
+
"platformName": "My Company Platform",
|
|
540
|
+
"canToggleNavbarState": true,
|
|
541
|
+
"isMenuCollapsible": true
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
<Aside type="note" title="Tenant name, not tenant ID">
|
|
546
|
+
The key inside `tenantOverrides` must match the tenant's **name** (the `name` field on the tenant record), not its numeric ID or its `code`. This is the same value that appears in the tenant list in the Admin UI.
|
|
547
|
+
</Aside>
|
|
548
|
+
|
|
549
|
+
<Aside type="tip">
|
|
550
|
+
`tenantOverrides` is most commonly used in `modules-config.json` overrides for platform-level modules such as `@bluealba/pae-shell-ui`, where you want shell behaviour (for example, collapsible menus or branding strings) to differ between tenants without duplicating module definitions.
|
|
551
|
+
</Aside>
|
|
552
|
+
|
|
553
|
+
---
|
|
554
|
+
|
|
555
|
+
### Jobs Definition
|
|
556
|
+
|
|
557
|
+
Declare scheduled jobs for the application in `jobs.json`:
|
|
558
|
+
|
|
559
|
+
```json
|
|
560
|
+
[
|
|
561
|
+
{
|
|
562
|
+
"name": "crm-health-check",
|
|
563
|
+
"description": "Periodic health check of the CRM service",
|
|
564
|
+
"enabled": true,
|
|
565
|
+
"pattern": "*/5 * * * *",
|
|
566
|
+
"taskType": "HttpInvokerTask",
|
|
567
|
+
"taskConfig": {
|
|
568
|
+
"url": "http://crm-service/health",
|
|
569
|
+
"method": "GET",
|
|
570
|
+
"timeout": 5000
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
"name": "crm-daily-sync",
|
|
575
|
+
"description": "Synchronizes CRM data with the external source every day at 2 AM",
|
|
576
|
+
"enabled": true,
|
|
577
|
+
"pattern": "0 2 * * *",
|
|
578
|
+
"taskType": "HttpInvokerTask",
|
|
579
|
+
"taskConfig": {
|
|
580
|
+
"url": "http://crm-service/api/sync",
|
|
581
|
+
"method": "POST",
|
|
582
|
+
"body": { "type": "full" },
|
|
583
|
+
"headers": { "Content-Type": "application/json" },
|
|
584
|
+
"timeout": 30000
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
]
|
|
588
|
+
```
|
|
589
|
+
|
|
590
|
+
**Fields**:
|
|
591
|
+
- **name**: Unique job name within the application
|
|
592
|
+
- **description**: Optional human-readable description of the job's purpose
|
|
593
|
+
- **enabled**: Whether the job should be active after synchronization (defaults to `true`)
|
|
594
|
+
- **pattern**: Standard cron expression defining the execution schedule
|
|
595
|
+
- **taskType**: The type of task to execute (currently `"HttpInvokerTask"` is supported)
|
|
596
|
+
- **taskConfig**: Task-specific configuration object
|
|
597
|
+
|
|
598
|
+
**`taskConfig` fields for `HttpInvokerTask`**:
|
|
599
|
+
- **url**: Full URL of the endpoint to invoke
|
|
600
|
+
- **method**: HTTP method (`GET`, `POST`, `PUT`, `DELETE`, `PATCH`, etc.)
|
|
601
|
+
- **body**: Optional request body (objects are automatically serialized to JSON)
|
|
602
|
+
- **headers**: Optional custom HTTP headers
|
|
603
|
+
- **auth**: Optional authentication configuration (e.g., `{ "authMethod": "bearer", "token": "..." }`)
|
|
604
|
+
- **timeout**: Optional request timeout in milliseconds
|
|
605
|
+
|
|
606
|
+
**Ownership and Sync Behavior**:
|
|
607
|
+
|
|
608
|
+
The bootstrap sync engine manages the full lifecycle of jobs it defines. Ownership is tracked via the `updatedBy` field — when bootstrap creates or updates a job it sets both `createdBy` and `updatedBy` to its own `scopedTo` value:
|
|
609
|
+
|
|
610
|
+
- Jobs in `jobs.json` that do not yet exist are **created** automatically
|
|
611
|
+
- Jobs that already exist and whose `updatedBy` still matches the bootstrap scope are **updated** to match the latest definition
|
|
612
|
+
- Jobs that were previously created by bootstrap but are no longer present in `jobs.json` are **deleted**
|
|
613
|
+
- Jobs that were originally created by bootstrap but have since been manually edited (i.e., `updatedBy` was changed to a user's username via the Admin UI) are **skipped** — a **WARNING** is logged indicating the record was manually modified
|
|
614
|
+
- Jobs whose `createdBy` does not match the bootstrap scope at all are **skipped silently**
|
|
615
|
+
|
|
616
|
+
<Aside type="note">
|
|
617
|
+
Editing a job in the Admin UI changes its `updatedBy` field to the editing user's username, which effectively transfers ownership away from bootstrap. Bootstrap will not overwrite those manual changes on subsequent runs. To hand ownership back to bootstrap, delete the job and let bootstrap recreate it from `jobs.json`.
|
|
618
|
+
</Aside>
|
|
619
|
+
|
|
620
|
+
This same ownership logic applies to all synchronized entities — not only jobs, but also applications, roles, operations, modules, feature flags, and shared libraries.
|
|
621
|
+
|
|
622
|
+
<Aside type="note">
|
|
623
|
+
See the [Scheduler Module](/_/docs/architecture/scheduler/) documentation for full details on job types, cron syntax, and dynamic parameter support.
|
|
624
|
+
</Aside>
|
|
625
|
+
|
|
626
|
+
---
|
|
627
|
+
|
|
628
|
+
### Feature Flags Definition
|
|
629
|
+
|
|
630
|
+
Declare feature flags for the application in `feature-flags.json`:
|
|
631
|
+
|
|
632
|
+
```json
|
|
633
|
+
[
|
|
634
|
+
{
|
|
635
|
+
"name": "new-checkout-flow",
|
|
636
|
+
"description": "Enable the redesigned checkout experience",
|
|
637
|
+
"visible": true,
|
|
638
|
+
"defaultValue": false,
|
|
639
|
+
"valueRules": []
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
"name": "beta-reporting",
|
|
643
|
+
"description": "Enable advanced reporting for beta users",
|
|
644
|
+
"visible": true,
|
|
645
|
+
"defaultValue": false,
|
|
646
|
+
"valueRules": [
|
|
647
|
+
{
|
|
648
|
+
"name": "beta-user-access",
|
|
649
|
+
"condition": {
|
|
650
|
+
"attribute": "user",
|
|
651
|
+
"operator": "in",
|
|
652
|
+
"value": ["beta-user-1@example.com", "beta-user-2@example.com"]
|
|
653
|
+
},
|
|
654
|
+
"value": true
|
|
655
|
+
}
|
|
656
|
+
]
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
"name": "new-search-experience",
|
|
660
|
+
"description": "Gradual rollout of the new search UI",
|
|
661
|
+
"visible": true,
|
|
662
|
+
"defaultValue": false,
|
|
663
|
+
"valueRules": [
|
|
664
|
+
{
|
|
665
|
+
"name": "acme-10pct-rollout",
|
|
666
|
+
"condition": {
|
|
667
|
+
"attribute": "tenant",
|
|
668
|
+
"operator": "eq",
|
|
669
|
+
"value": "acme-corp"
|
|
670
|
+
},
|
|
671
|
+
"value": true,
|
|
672
|
+
"rollout": {
|
|
673
|
+
"percentage": 10,
|
|
674
|
+
"attribute": "username"
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
]
|
|
678
|
+
}
|
|
679
|
+
]
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
**Fields**:
|
|
683
|
+
- **name**: Unique flag identifier (kebab-case, immutable after creation)
|
|
684
|
+
- **description**: Human-readable description of what the flag controls
|
|
685
|
+
- **visible**: Master switch; set to `false` to disable the flag entirely without deleting it
|
|
686
|
+
- **defaultValue**: Value returned when no value rule matches the evaluation context
|
|
687
|
+
- **valueRules**: Ordered list of conditional rules; the first matching rule's value is returned
|
|
688
|
+
|
|
689
|
+
**Application association**: The flag's application is inferred automatically from the folder that contains the `feature-flags.json` file. The bootstrap library reads the application name from the co-located `application.json` and attaches it to each flag at sync time. You do not include an `applicationName` field in the JSON.
|
|
690
|
+
|
|
691
|
+
**Upsert semantics**:
|
|
692
|
+
|
|
693
|
+
The bootstrap operation is idempotent and performs an **upsert** for each flag:
|
|
694
|
+
|
|
695
|
+
- **Flag does not exist**: Creates the flag with the provided configuration.
|
|
696
|
+
- **Flag already exists**: Updates the flag metadata (`description`, `visible`, `defaultValue`, `application_id` (derived from the enclosing application folder)) and **replaces all existing value rules** with the ones declared in the bootstrap configuration.
|
|
697
|
+
|
|
698
|
+
All operations for a single bootstrap call run in a single database transaction (all-or-nothing). If any flag fails, the entire bootstrap call is rolled back.
|
|
699
|
+
|
|
700
|
+
<Aside type="tip">
|
|
701
|
+
Because bootstrap performs a full value rule replacement on existing flags, keep your bootstrap declarations up to date. Removing a value rule from the bootstrap configuration will remove it from the live flag on the next bootstrap run.
|
|
702
|
+
</Aside>
|
|
703
|
+
|
|
704
|
+
<Aside type="note">
|
|
705
|
+
See the [Feature Flags guide](/guides/using-feature-flags/) for the full reference on value rules, conditions, operators, and percentage rollouts.
|
|
706
|
+
</Aside>
|
|
707
|
+
|
|
708
|
+
---
|
|
709
|
+
|
|
710
|
+
## Environment Variables in Configuration
|
|
711
|
+
|
|
712
|
+
Bootstrap JSON files support the `{{$ENV.VARIABLE_NAME}}` syntax to inject environment variable values at startup. This lets you write a single set of JSON files that works across all environments — development, staging, and production — without hardcoding hostnames, ports, URLs, or any other value that changes per environment.
|
|
713
|
+
|
|
714
|
+
### Syntax
|
|
715
|
+
|
|
716
|
+
The expression language supports four variants:
|
|
717
|
+
|
|
718
|
+
| Variant | Syntax | Behaviour when variable is missing |
|
|
719
|
+
|---|---|---|
|
|
720
|
+
| Required | `{{$ENV.VARIABLE_NAME}}` | Fails immediately with an error |
|
|
721
|
+
| Optional | `{{$ENV.VARIABLE_NAME?}}` | Returns `undefined`; key is omitted from the parent object |
|
|
722
|
+
| Default | `{{$ENV.VARIABLE_NAME?=default_value}}` | Falls back to the literal `default_value` |
|
|
723
|
+
| Chain | `{{$ENV.VARIABLE_NAME?=$ENV.FALLBACK?=literal}}` | Tries each reference left to right; uses the first that resolves |
|
|
724
|
+
|
|
725
|
+
The value is read from the environment variable at bootstrap startup time, before any validation occurs.
|
|
726
|
+
|
|
727
|
+
**Example** — `modules.json` using environment variables for service host and port:
|
|
728
|
+
|
|
729
|
+
```json
|
|
730
|
+
[
|
|
731
|
+
{
|
|
732
|
+
"name": "@mycompany/crm-service",
|
|
733
|
+
"displayName": "CRM Service",
|
|
734
|
+
"type": "service",
|
|
735
|
+
"baseUrl": "/api/crm",
|
|
736
|
+
"service": {
|
|
737
|
+
"host": "{{$ENV.CRM_SERVICE_HOST}}",
|
|
738
|
+
"port": "{{$ENV.CRM_SERVICE_PORT}}"
|
|
739
|
+
},
|
|
740
|
+
"dependsOn": []
|
|
741
|
+
}
|
|
742
|
+
]
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
When the bootstrap service starts with `CRM_SERVICE_HOST=crm-service` and `CRM_SERVICE_PORT=4002`, the resolved configuration becomes:
|
|
746
|
+
|
|
747
|
+
```json
|
|
748
|
+
{
|
|
749
|
+
"host": "crm-service",
|
|
750
|
+
"port": 4002
|
|
751
|
+
}
|
|
752
|
+
```
|
|
753
|
+
|
|
754
|
+
### Optional and Default Values
|
|
755
|
+
|
|
756
|
+
Use the `?` and `?=` suffixes to make expressions tolerant of missing environment variables.
|
|
757
|
+
|
|
758
|
+
**Example** — `modules-config.json` with a mix of required, optional, default, and chained expressions:
|
|
759
|
+
|
|
760
|
+
```json
|
|
761
|
+
{
|
|
762
|
+
"host": "{{$ENV.CRM_HOST}}",
|
|
763
|
+
"port": "{{$ENV.CRM_PORT?=4002}}",
|
|
764
|
+
"debugPort": "{{$ENV.DEBUG_PORT?}}",
|
|
765
|
+
"logLevel": "{{$ENV.LOG_LEVEL?=$ENV.DEFAULT_LOG_LEVEL?=info}}"
|
|
766
|
+
}
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
Given that only `CRM_HOST=crm-service` is set in the environment and none of the others are, the resolved configuration becomes:
|
|
770
|
+
|
|
771
|
+
```json
|
|
772
|
+
{
|
|
773
|
+
"host": "crm-service",
|
|
774
|
+
"port": 4002,
|
|
775
|
+
"logLevel": "info"
|
|
776
|
+
}
|
|
777
|
+
```
|
|
778
|
+
|
|
779
|
+
What happened to each field:
|
|
780
|
+
- **`host`** — resolved normally from the required `CRM_HOST` variable.
|
|
781
|
+
- **`port`** — `CRM_PORT` was not set, so the literal default `4002` was used and coerced to a number.
|
|
782
|
+
- **`debugPort`** — `DEBUG_PORT` was not set and the expression is optional (`?`), so the key is **omitted entirely** from the resolved object.
|
|
783
|
+
- **`logLevel`** — `LOG_LEVEL` was not set, then `DEFAULT_LOG_LEVEL` was not set, so the chain fell through to the literal `"info"`.
|
|
784
|
+
|
|
785
|
+
**Default value type coercion**
|
|
786
|
+
|
|
787
|
+
Literal defaults go through the same coercion rules as regular env var values:
|
|
788
|
+
|
|
789
|
+
| Expression | Default literal | Resolved as |
|
|
790
|
+
|---|---|---|
|
|
791
|
+
| `"{{$ENV.PORT?=8080}}"` | `8080` | `8080` (number) |
|
|
792
|
+
| `"{{$ENV.ENABLED?=true}}"` | `true` | `true` (boolean) |
|
|
793
|
+
| `"{{$ENV.HOST?=localhost}}"` | `localhost` | `"localhost"` (string) |
|
|
794
|
+
|
|
795
|
+
**Fallback chain references**
|
|
796
|
+
|
|
797
|
+
References inside a fallback chain can use any registered namespace, not just `ENV`. The engine tries each segment from left to right and uses the first one that resolves successfully.
|
|
798
|
+
|
|
799
|
+
```json
|
|
800
|
+
{
|
|
801
|
+
"serviceUrl": "{{$ENV.SERVICE_URL?=$ENV.LEGACY_SERVICE_URL?=http://default-service}}"
|
|
802
|
+
}
|
|
803
|
+
```
|
|
804
|
+
|
|
805
|
+
---
|
|
806
|
+
|
|
807
|
+
### Resolution Modes
|
|
808
|
+
|
|
809
|
+
<Tabs>
|
|
810
|
+
<TabItem label="Full-value">
|
|
811
|
+
When the entire JSON string value is a single `{{$ENV.X}}` expression, the result is **automatically coerced to the correct type**:
|
|
812
|
+
|
|
813
|
+
| JSON value | Env var value | Resolved as |
|
|
814
|
+
|---|---|---|
|
|
815
|
+
| `"{{$ENV.PORT}}"` | `"4002"` | `4002` (number) |
|
|
816
|
+
| `"{{$ENV.ENABLED}}"` | `"true"` | `true` (boolean) |
|
|
817
|
+
| `"{{$ENV.ENABLED}}"` | `"false"` | `false` (boolean) |
|
|
818
|
+
| `"{{$ENV.HOST}}"` | `"crm-service"` | `"crm-service"` (string) |
|
|
819
|
+
|
|
820
|
+
This means you can use environment variables for numeric fields like `port` without any extra casting — just write the expression and the bootstrap system handles the type.
|
|
821
|
+
</TabItem>
|
|
822
|
+
|
|
823
|
+
<TabItem label="Inline (embedded in text)">
|
|
824
|
+
When the expression is embedded inside a larger string, it always resolves as a string:
|
|
825
|
+
|
|
826
|
+
```json
|
|
827
|
+
{
|
|
828
|
+
"url": "http://{{$ENV.CRM_HOST}}:{{$ENV.CRM_PORT}}/api/v1"
|
|
829
|
+
}
|
|
830
|
+
```
|
|
831
|
+
|
|
832
|
+
With `CRM_HOST=crm-service` and `CRM_PORT=4002`:
|
|
833
|
+
|
|
834
|
+
```json
|
|
835
|
+
{
|
|
836
|
+
"url": "http://crm-service:4002/api/v1"
|
|
837
|
+
}
|
|
838
|
+
```
|
|
839
|
+
|
|
840
|
+
Multiple expressions can appear in the same string value.
|
|
841
|
+
|
|
842
|
+
When an optional inline expression (`?`) resolves to `undefined`, it collapses to an empty string `""` rather than omitting the key (key omission only applies to full-value mode).
|
|
843
|
+
</TabItem>
|
|
844
|
+
|
|
845
|
+
<TabItem label="Optional and Default">
|
|
846
|
+
The `?` and `?=` suffixes control what happens when a variable is not set:
|
|
847
|
+
|
|
848
|
+
| JSON value | Env var value | Resolved as |
|
|
849
|
+
|---|---|---|
|
|
850
|
+
| `"{{$ENV.PORT?=4002}}"` | not set | `4002` (number, from default) |
|
|
851
|
+
| `"{{$ENV.ENABLED?=true}}"` | not set | `true` (boolean, from default) |
|
|
852
|
+
| `"{{$ENV.HOST?}}"` | not set | key omitted (optional, full-value mode) |
|
|
853
|
+
| `"{{$ENV.HOST?=fallback}}"` | `"my-host"` | `"my-host"` (env var wins over default) |
|
|
854
|
+
|
|
855
|
+
Default literals undergo the same type coercion as live env var values — a default of `4002` is returned as the number `4002`, not the string `"4002"`.
|
|
856
|
+
|
|
857
|
+
When the env var is present, it always takes precedence over any default or fallback in the chain.
|
|
858
|
+
</TabItem>
|
|
859
|
+
</Tabs>
|
|
860
|
+
|
|
861
|
+
### Practical Example: Multi-Environment Setup
|
|
862
|
+
|
|
863
|
+
The same JSON files work in all environments by changing only the environment variables:
|
|
864
|
+
|
|
865
|
+
<Tabs>
|
|
866
|
+
<TabItem label="modules.json">
|
|
867
|
+
```json
|
|
868
|
+
[
|
|
869
|
+
{
|
|
870
|
+
"name": "@mycompany/crm-service",
|
|
871
|
+
"displayName": "CRM Service",
|
|
872
|
+
"type": "service",
|
|
873
|
+
"baseUrl": "/api/crm",
|
|
874
|
+
"service": {
|
|
875
|
+
"host": "{{$ENV.CRM_SERVICE_HOST}}",
|
|
876
|
+
"port": "{{$ENV.CRM_SERVICE_PORT}}"
|
|
877
|
+
},
|
|
878
|
+
"dependsOn": []
|
|
879
|
+
}
|
|
880
|
+
]
|
|
881
|
+
```
|
|
882
|
+
</TabItem>
|
|
883
|
+
|
|
884
|
+
<TabItem label="Development">
|
|
885
|
+
```bash
|
|
886
|
+
# docker-compose.yml (dev)
|
|
887
|
+
CRM_SERVICE_HOST=crm-service
|
|
888
|
+
CRM_SERVICE_PORT=4002
|
|
889
|
+
```
|
|
890
|
+
</TabItem>
|
|
891
|
+
|
|
892
|
+
<TabItem label="Staging">
|
|
893
|
+
```bash
|
|
894
|
+
# docker-compose.yml (staging)
|
|
895
|
+
CRM_SERVICE_HOST=crm-staging.internal
|
|
896
|
+
CRM_SERVICE_PORT=443
|
|
897
|
+
```
|
|
898
|
+
</TabItem>
|
|
899
|
+
|
|
900
|
+
<TabItem label="Production">
|
|
901
|
+
```bash
|
|
902
|
+
# docker-compose.yml (production)
|
|
903
|
+
CRM_SERVICE_HOST=crm.internal.prod
|
|
904
|
+
CRM_SERVICE_PORT=443
|
|
905
|
+
```
|
|
906
|
+
</TabItem>
|
|
907
|
+
</Tabs>
|
|
908
|
+
|
|
909
|
+
<Aside type="caution" title="Required variables must be defined">
|
|
910
|
+
This only applies to **required** expressions (no `?` or `?=` suffix). If the bootstrap service starts and a required environment variable is not set, the bootstrap process will **fail immediately** with an error like:
|
|
911
|
+
|
|
912
|
+
```
|
|
913
|
+
Error reading modules.json file: Expression resolution failed: {{$ENV.CRM_SERVICE_HOST}} is not defined in environment variables
|
|
914
|
+
```
|
|
915
|
+
|
|
916
|
+
Make sure every required `{{$ENV.X}}` expression has a corresponding environment variable configured in your Docker Compose file or container environment.
|
|
917
|
+
|
|
918
|
+
Optional expressions (`{{$ENV.X?}}`) and expressions with defaults (`{{$ENV.X?=value}}`) will **not** fail if the variable is missing — they either omit the key or fall back to the provided default.
|
|
919
|
+
</Aside>
|
|
920
|
+
|
|
921
|
+
<Aside type="tip" title="Supported in all configuration files">
|
|
922
|
+
The `{{$ENV.X}}` syntax works in every bootstrap JSON file:
|
|
923
|
+
`application.json`, `modules.json`, `operations.json`, `roles.json`, `modules-config.json`, `jobs.json`, and `shared-libraries.json`.
|
|
924
|
+
</Aside>
|
|
925
|
+
|
|
926
|
+
---
|
|
927
|
+
|
|
928
|
+
## Local Development Overrides (serviceLocal)
|
|
929
|
+
|
|
930
|
+
When running the platform locally, Docker service hostnames (such as `crm-service` or `habits-service`) do not resolve on the developer's machine. The gateway bootstrap sends those hostnames to the platform, so any request it proxies will fail because `crm-service` is not reachable from the host network.
|
|
931
|
+
|
|
932
|
+
The usual workaround is to set `{{$ENV.*}}` expressions on every `service` field that differs between Docker and local development. The `serviceLocal` feature provides a simpler, more declarative alternative.
|
|
933
|
+
|
|
934
|
+
### How It Works
|
|
935
|
+
|
|
936
|
+
Each module in `modules.json` accepts an optional `serviceLocal` field. It has the same shape as the `service` field (`Partial<ServiceConfig>`), so you only need to include the properties that differ locally.
|
|
937
|
+
|
|
938
|
+
When the bootstrap library starts:
|
|
939
|
+
|
|
940
|
+
- If `LOCAL_MODE=true`, `serviceLocal` is **shallow-merged into `service`**, replacing any overlapping fields. The merged result is what gets sent to the gateway.
|
|
941
|
+
- In all other cases (including when `LOCAL_MODE` is absent or set to any other value), `serviceLocal` is **silently stripped** and the original `service` definition is used unchanged.
|
|
942
|
+
|
|
943
|
+
The gateway never receives the `serviceLocal` field regardless of mode.
|
|
944
|
+
|
|
945
|
+
### Example
|
|
946
|
+
|
|
947
|
+
```json
|
|
948
|
+
[
|
|
949
|
+
{
|
|
950
|
+
"name": "@mycompany/crm-service",
|
|
951
|
+
"displayName": "CRM Service",
|
|
952
|
+
"type": "service",
|
|
953
|
+
"baseUrl": "/api/crm",
|
|
954
|
+
"service": {
|
|
955
|
+
"host": "crm-service",
|
|
956
|
+
"port": 80
|
|
957
|
+
},
|
|
958
|
+
"serviceLocal": {
|
|
959
|
+
"host": "host.docker.internal",
|
|
960
|
+
"port": 3001
|
|
961
|
+
},
|
|
962
|
+
"dependsOn": []
|
|
963
|
+
}
|
|
964
|
+
]
|
|
965
|
+
```
|
|
966
|
+
|
|
967
|
+
With `LOCAL_MODE=true`, the resolved `service` sent to the gateway becomes:
|
|
968
|
+
|
|
969
|
+
```json
|
|
970
|
+
{
|
|
971
|
+
"host": "host.docker.internal",
|
|
972
|
+
"port": 3001
|
|
973
|
+
}
|
|
974
|
+
```
|
|
975
|
+
|
|
976
|
+
Without `LOCAL_MODE` (or with any value other than `'true'`), the original `service` is used:
|
|
977
|
+
|
|
978
|
+
```json
|
|
979
|
+
{
|
|
980
|
+
"host": "crm-service",
|
|
981
|
+
"port": 80
|
|
982
|
+
}
|
|
983
|
+
```
|
|
984
|
+
|
|
985
|
+
### ServiceConfig Fields
|
|
986
|
+
|
|
987
|
+
Both `service` and `serviceLocal` accept the following fields:
|
|
988
|
+
|
|
989
|
+
| Field | Type | Required | Default | Description |
|
|
990
|
+
|---|---|---|---|---|
|
|
991
|
+
| `host` | `string` | Yes (in `service`) | — | Hostname or IP address the gateway uses to reach the service |
|
|
992
|
+
| `port` | `number` | Yes (in `service`) | — | Port number the gateway connects to |
|
|
993
|
+
| `protocol` | `string` | No | `"http"` | Transport protocol. Set to `"https"` if the service uses TLS |
|
|
994
|
+
| `includeBaseURL` | `boolean` | No | `false` | When `true`, the module's `baseUrl` path is forwarded as-is to the service instead of being stripped |
|
|
995
|
+
|
|
996
|
+
In `serviceLocal`, all fields are optional — only include the ones that differ from the Docker definition.
|
|
997
|
+
|
|
998
|
+
### Behavior Summary
|
|
999
|
+
|
|
1000
|
+
| Condition | `serviceLocal` present | Result |
|
|
1001
|
+
|---|---|---|
|
|
1002
|
+
| `LOCAL_MODE=true` | Yes | `serviceLocal` shallow-merged into `service`; merged value sent to gateway |
|
|
1003
|
+
| `LOCAL_MODE=true` | No | `service` sent to gateway unchanged |
|
|
1004
|
+
| `LOCAL_MODE` not set or not `'true'` | Yes | `serviceLocal` stripped; original `service` sent to gateway |
|
|
1005
|
+
| `LOCAL_MODE` not set or not `'true'` | No | `service` sent to gateway unchanged |
|
|
1006
|
+
|
|
1007
|
+
<Aside type="note">
|
|
1008
|
+
`{{$ENV.*}}` expressions work inside `serviceLocal` values just as they do inside `service`. You can use expressions such as `"{{$ENV.CRM_LOCAL_PORT?=4002}}"` within `serviceLocal` to keep local port numbers out of version control.
|
|
1009
|
+
</Aside>
|
|
1010
|
+
|
|
1011
|
+
<Aside type="caution" title="LOCAL_MODE is case-sensitive">
|
|
1012
|
+
The bootstrap library checks for the exact string `'true'`. Values such as `TRUE`, `True`, or `1` will not activate local mode. Set the variable as `LOCAL_MODE=true` in your local environment or `.env` file.
|
|
1013
|
+
</Aside>
|
|
1014
|
+
|
|
1015
|
+
---
|
|
1016
|
+
|
|
1017
|
+
## Bootstrap Execution
|
|
1018
|
+
|
|
1019
|
+
### Startup Sequence
|
|
1020
|
+
|
|
1021
|
+
When the bootstrap service starts:
|
|
1022
|
+
|
|
1023
|
+
```
|
|
1024
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1025
|
+
│ 1. Bootstrap Service Starts │
|
|
1026
|
+
│ • Reads all JSON files from data/ directory │
|
|
1027
|
+
│ • Parses and validates JSON structure │
|
|
1028
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1029
|
+
│
|
|
1030
|
+
▼
|
|
1031
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1032
|
+
│ 2. Synchronize Catalog │
|
|
1033
|
+
│ • Compare modules.json with existing catalog │
|
|
1034
|
+
│ • Create new modules, update existing ones │
|
|
1035
|
+
│ • Apply module-config.json overrides │
|
|
1036
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1037
|
+
│
|
|
1038
|
+
▼
|
|
1039
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1040
|
+
│ 3. Synchronize Shared Libraries │
|
|
1041
|
+
│ • Register UI shared libraries │
|
|
1042
|
+
│ • Update library versions │
|
|
1043
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1044
|
+
│
|
|
1045
|
+
▼
|
|
1046
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1047
|
+
│ 4. Synchronize Applications │
|
|
1048
|
+
│ • Create/update application records │
|
|
1049
|
+
│ • Set application metadata │
|
|
1050
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1051
|
+
│
|
|
1052
|
+
▼
|
|
1053
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1054
|
+
│ 5. Synchronize Roles │
|
|
1055
|
+
│ • Create new roles │
|
|
1056
|
+
│ • Update role descriptions │
|
|
1057
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1058
|
+
│
|
|
1059
|
+
▼
|
|
1060
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1061
|
+
│ 6. Synchronize Operations │
|
|
1062
|
+
│ • Create new operations │
|
|
1063
|
+
│ • Bind operations to roles │
|
|
1064
|
+
│ • Update operation descriptions │
|
|
1065
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1066
|
+
│
|
|
1067
|
+
▼
|
|
1068
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1069
|
+
│ 7. Synchronize Scheduled Jobs │
|
|
1070
|
+
│ • Create new jobs from bootstrap definitions │
|
|
1071
|
+
│ • Update existing bootstrap-owned jobs │
|
|
1072
|
+
│ • Delete bootstrap-owned jobs no longer defined │
|
|
1073
|
+
│ • Skip jobs owned by other sources │
|
|
1074
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1075
|
+
│
|
|
1076
|
+
▼
|
|
1077
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1078
|
+
│ 7.5. Synchronize Tenant Application Restrictions │
|
|
1079
|
+
│ • For each app with allowedTenants defined: │
|
|
1080
|
+
│ - Delete existing restrictions for the app │
|
|
1081
|
+
│ - Re-insert rows for resolved tenant names │
|
|
1082
|
+
│ • Apps without allowedTenants are skipped │
|
|
1083
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1084
|
+
│
|
|
1085
|
+
▼
|
|
1086
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1087
|
+
│ 8. Synchronize Feature Flags │
|
|
1088
|
+
│ • Create/update feature flags from feature-flags.json│
|
|
1089
|
+
│ • Replace value rules for existing flags │
|
|
1090
|
+
│ • Delete bootstrap-owned flags no longer defined │
|
|
1091
|
+
└────────────────────┬────────────────────────────────────┘
|
|
1092
|
+
│
|
|
1093
|
+
▼
|
|
1094
|
+
┌─────────────────────────────────────────────────────────┐
|
|
1095
|
+
│ 9. Bootstrap Complete │
|
|
1096
|
+
│ • Log summary of changes │
|
|
1097
|
+
│ • Service ready to handle requests │
|
|
1098
|
+
└─────────────────────────────────────────────────────────┘
|
|
1099
|
+
```
|
|
1100
|
+
|
|
1101
|
+
### Example Output
|
|
1102
|
+
|
|
1103
|
+
```log
|
|
1104
|
+
2025-01-09 09:54:08 > platform-bootstrap-service@1.0.0 start
|
|
1105
|
+
2025-01-09 09:54:10 [nodemon] starting `ts-node src/main.ts`
|
|
1106
|
+
2025-01-09 09:54:40 Server listening on Port 80
|
|
1107
|
+
2025-01-09 09:54:45 Synchronizing Catalog - START ...
|
|
1108
|
+
2025-01-09 09:54:47 Synchronizing Catalog - COMPLETED
|
|
1109
|
+
2025-01-09 09:54:47 Synchronizing shared-libraries - START ...
|
|
1110
|
+
2025-01-09 09:54:48 >>> shared-libraries to ADD []
|
|
1111
|
+
2025-01-09 09:54:48 >>> shared-libraries to UPDATE []
|
|
1112
|
+
2025-01-09 09:54:48 Synchronizing shared-libraries - COMPLETED
|
|
1113
|
+
2025-01-09 09:54:48 Synchronizing authorization-applications - START ...
|
|
1114
|
+
2025-01-09 09:54:52 >>> authorization-applications to ADD []
|
|
1115
|
+
2025-01-09 09:54:52 >>> authorization-applications to UPDATE ['crm', 'inventory']
|
|
1116
|
+
2025-01-09 09:54:53 Synchronizing authorization-applications - COMPLETED
|
|
1117
|
+
2025-01-09 09:54:53 Synchronizing authorization-roles - START ...
|
|
1118
|
+
2025-01-09 09:54:53 >>> authorization-roles to ADD ['CRM User']
|
|
1119
|
+
2025-01-09 09:54:53 >>> authorization-roles to UPDATE []
|
|
1120
|
+
2025-01-09 09:54:53 Synchronizing authorization-roles - COMPLETED
|
|
1121
|
+
2025-01-09 09:54:53 Synchronizing authorization-operations - START ...
|
|
1122
|
+
2025-01-09 09:54:53 >>> authorization-operations to ADD ['crm.customers.read']
|
|
1123
|
+
2025-01-09 09:54:53 >>> authorization-operations to UPDATE []
|
|
1124
|
+
2025-01-09 09:54:53 Synchronizing authorization-operations - COMPLETED
|
|
1125
|
+
2025-01-09 09:54:53 Synchronizing scheduled-jobs - START ...
|
|
1126
|
+
2025-01-09 09:54:53 [Bootstrap] Created job 'crm-daily-sync'
|
|
1127
|
+
2025-01-09 09:54:53 [Bootstrap] Updated job 'crm-health-check'
|
|
1128
|
+
2025-01-09 09:54:53 Synchronizing scheduled-jobs - COMPLETED
|
|
1129
|
+
```
|
|
1130
|
+
|
|
1131
|
+
---
|
|
1132
|
+
|
|
1133
|
+
## Development Workflow
|
|
1134
|
+
|
|
1135
|
+
### Making Changes
|
|
1136
|
+
|
|
1137
|
+
1. **Edit JSON Files**: Modify the appropriate JSON file in the `data/` directory
|
|
1138
|
+
|
|
1139
|
+
```bash
|
|
1140
|
+
# Example: Add a new operation
|
|
1141
|
+
vim data/crm/operations.json
|
|
1142
|
+
```
|
|
1143
|
+
|
|
1144
|
+
2. **Restart Bootstrap Service**: Changes are applied automatically on restart
|
|
1145
|
+
|
|
1146
|
+
```bash
|
|
1147
|
+
# Docker Compose
|
|
1148
|
+
docker-compose restart platform-bootstrap-service
|
|
1149
|
+
|
|
1150
|
+
# Or with Turbo (for development)
|
|
1151
|
+
npm run dev --workspace=platform-bootstrap-service
|
|
1152
|
+
```
|
|
1153
|
+
|
|
1154
|
+
3. **Verify Changes**: Check the service logs to confirm synchronization
|
|
1155
|
+
|
|
1156
|
+
4. **Commit to Version Control**: Push changes to share with the team
|
|
1157
|
+
|
|
1158
|
+
```bash
|
|
1159
|
+
git add data/
|
|
1160
|
+
git commit -m "Add CRM customer operations"
|
|
1161
|
+
git push
|
|
1162
|
+
```
|
|
1163
|
+
|
|
1164
|
+
### Environment Propagation
|
|
1165
|
+
|
|
1166
|
+
The same bootstrap configuration is used across all environments:
|
|
1167
|
+
|
|
1168
|
+
```
|
|
1169
|
+
Development → Staging → Production
|
|
1170
|
+
↓ ↓ ↓
|
|
1171
|
+
Same JSON Same JSON Same JSON
|
|
1172
|
+
↓ ↓ ↓
|
|
1173
|
+
Bootstrap service automatically synchronizes on startup
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
**Benefits**:
|
|
1177
|
+
- Consistent configuration across environments — the same JSON files deploy everywhere
|
|
1178
|
+
- Use `{{$ENV.VARIABLE_NAME}}` in any JSON value to inject environment-specific values (hostnames, ports, URLs) without duplicating files
|
|
1179
|
+
- Version-controlled platform state
|
|
1180
|
+
- Easy rollback via Git
|
|
1181
|
+
- No manual database updates needed
|
|
1182
|
+
|
|
1183
|
+
---
|
|
1184
|
+
|
|
1185
|
+
## Common Patterns
|
|
1186
|
+
|
|
1187
|
+
### Adding a New Application
|
|
1188
|
+
|
|
1189
|
+
<Tabs>
|
|
1190
|
+
<TabItem label="1. Create Folder">
|
|
1191
|
+
```bash
|
|
1192
|
+
mkdir -p data/my-new-app
|
|
1193
|
+
cd data/my-new-app
|
|
1194
|
+
```
|
|
1195
|
+
</TabItem>
|
|
1196
|
+
|
|
1197
|
+
<TabItem label="2. Define Application">
|
|
1198
|
+
Create `application.json`:
|
|
1199
|
+
```json
|
|
1200
|
+
{
|
|
1201
|
+
"name": "my-new-app",
|
|
1202
|
+
"displayName": "My New Application",
|
|
1203
|
+
"description": "Description of the new application",
|
|
1204
|
+
"allowedByDefault": false,
|
|
1205
|
+
"allowedTenants": ["my-tenant"]
|
|
1206
|
+
}
|
|
1207
|
+
```
|
|
1208
|
+
</TabItem>
|
|
1209
|
+
|
|
1210
|
+
<TabItem label="3. Add Modules">
|
|
1211
|
+
Create `modules.json`:
|
|
1212
|
+
```json
|
|
1213
|
+
[
|
|
1214
|
+
{
|
|
1215
|
+
"name": "@mycompany/my-new-app-service",
|
|
1216
|
+
"displayName": "My New App Service",
|
|
1217
|
+
"type": "service",
|
|
1218
|
+
"baseUrl": "/api/my-new-app",
|
|
1219
|
+
"service": {
|
|
1220
|
+
"host": "my-new-app-service",
|
|
1221
|
+
"port": 4010
|
|
1222
|
+
}
|
|
1223
|
+
},
|
|
1224
|
+
{
|
|
1225
|
+
"name": "@mycompany/my-new-app-ui",
|
|
1226
|
+
"displayName": "My New App UI",
|
|
1227
|
+
"type": "app",
|
|
1228
|
+
"baseUrl": "/my-new-app-ui",
|
|
1229
|
+
"service": {
|
|
1230
|
+
"host": "my-new-app-ui",
|
|
1231
|
+
"port": 80
|
|
1232
|
+
},
|
|
1233
|
+
"ui": {
|
|
1234
|
+
"route": "/my-app",
|
|
1235
|
+
"mountAtSelector": "#pae-shell-ui-content",
|
|
1236
|
+
"bundleFile": "mycompany-my-new-app-ui.js"
|
|
1237
|
+
},
|
|
1238
|
+
"dependsOn": ["@bluealba/pae-shell-ui"]
|
|
1239
|
+
}
|
|
1240
|
+
]
|
|
1241
|
+
```
|
|
1242
|
+
</TabItem>
|
|
1243
|
+
|
|
1244
|
+
<TabItem label="4. Add Authorization">
|
|
1245
|
+
Create `operations.json`:
|
|
1246
|
+
```json
|
|
1247
|
+
[
|
|
1248
|
+
{
|
|
1249
|
+
"name": "my-new-app.read",
|
|
1250
|
+
"description": "View my new app",
|
|
1251
|
+
"roles": ["User"]
|
|
1252
|
+
},
|
|
1253
|
+
{
|
|
1254
|
+
"name": "my-new-app.write",
|
|
1255
|
+
"description": "Edit my new app",
|
|
1256
|
+
"roles": ["Admin"]
|
|
1257
|
+
}
|
|
1258
|
+
]
|
|
1259
|
+
```
|
|
1260
|
+
|
|
1261
|
+
Create `roles.json`:
|
|
1262
|
+
```json
|
|
1263
|
+
[
|
|
1264
|
+
{
|
|
1265
|
+
"name": "My App User",
|
|
1266
|
+
"description": "Standard user of my new app"
|
|
1267
|
+
}
|
|
1268
|
+
]
|
|
1269
|
+
```
|
|
1270
|
+
</TabItem>
|
|
1271
|
+
|
|
1272
|
+
<TabItem label="5. Add Jobs (Optional)">
|
|
1273
|
+
Create `jobs.json` to define recurring scheduled tasks for the application:
|
|
1274
|
+
```json
|
|
1275
|
+
[
|
|
1276
|
+
{
|
|
1277
|
+
"name": "my-new-app-health-check",
|
|
1278
|
+
"description": "Periodic health check of the my-new-app service",
|
|
1279
|
+
"enabled": true,
|
|
1280
|
+
"pattern": "*/5 * * * *",
|
|
1281
|
+
"taskType": "HttpInvokerTask",
|
|
1282
|
+
"taskConfig": {
|
|
1283
|
+
"url": "http://my-new-app-service:4010/health",
|
|
1284
|
+
"method": "GET",
|
|
1285
|
+
"timeout": 5000
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
]
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
Jobs are owned by the bootstrap scope: they will be created, updated, or deleted automatically as the `jobs.json` file changes.
|
|
1292
|
+
</TabItem>
|
|
1293
|
+
|
|
1294
|
+
<TabItem label="6. Restart Bootstrap">
|
|
1295
|
+
```bash
|
|
1296
|
+
docker-compose restart platform-bootstrap-service
|
|
1297
|
+
|
|
1298
|
+
# Watch logs
|
|
1299
|
+
docker-compose logs -f platform-bootstrap-service
|
|
1300
|
+
```
|
|
1301
|
+
</TabItem>
|
|
1302
|
+
</Tabs>
|
|
1303
|
+
|
|
1304
|
+
---
|
|
1305
|
+
|
|
1306
|
+
## Best Practices
|
|
1307
|
+
|
|
1308
|
+
<CardGrid>
|
|
1309
|
+
<Card title="Use Version Control" icon="github">
|
|
1310
|
+
Always commit bootstrap configurations to Git for history and collaboration
|
|
1311
|
+
</Card>
|
|
1312
|
+
|
|
1313
|
+
<Card title="Follow Naming Conventions" icon="pencil">
|
|
1314
|
+
Use consistent naming: kebab-case for names, dot notation for operations
|
|
1315
|
+
</Card>
|
|
1316
|
+
|
|
1317
|
+
<Card title="Document Changes" icon="document">
|
|
1318
|
+
Include clear descriptions for all operations, roles, and applications
|
|
1319
|
+
</Card>
|
|
1320
|
+
|
|
1321
|
+
<Card title="Test Before Production" icon="approve-check">
|
|
1322
|
+
Verify bootstrap changes in development before deploying to production
|
|
1323
|
+
</Card>
|
|
1324
|
+
|
|
1325
|
+
<Card title="Minimal Permissions" icon="shield">
|
|
1326
|
+
Set `allowedByDefault: false` and grant explicit access as needed
|
|
1327
|
+
</Card>
|
|
1328
|
+
|
|
1329
|
+
<Card title="Organize by Application" icon="seti:folder">
|
|
1330
|
+
Keep each application's definitions in its own folder for clarity
|
|
1331
|
+
</Card>
|
|
1332
|
+
</CardGrid>
|
|
1333
|
+
|
|
1334
|
+
---
|
|
1335
|
+
|
|
1336
|
+
## Troubleshooting
|
|
1337
|
+
|
|
1338
|
+
### Bootstrap Service Won't Start
|
|
1339
|
+
|
|
1340
|
+
**Check logs for errors**:
|
|
1341
|
+
```bash
|
|
1342
|
+
docker-compose logs platform-bootstrap-service
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
**Common causes**:
|
|
1346
|
+
- Invalid JSON syntax
|
|
1347
|
+
- Missing required fields
|
|
1348
|
+
- Duplicate names
|
|
1349
|
+
- Network connectivity to Platform APIs
|
|
1350
|
+
|
|
1351
|
+
### Changes Not Applied
|
|
1352
|
+
|
|
1353
|
+
**Verify the bootstrap service restarted**:
|
|
1354
|
+
```bash
|
|
1355
|
+
docker-compose ps platform-bootstrap-service
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
**Check synchronization logs**:
|
|
1359
|
+
```bash
|
|
1360
|
+
docker-compose logs platform-bootstrap-service | grep "Synchronizing"
|
|
1361
|
+
```
|
|
1362
|
+
|
|
1363
|
+
### Module Not Appearing in Catalog
|
|
1364
|
+
|
|
1365
|
+
**Check module definition**:
|
|
1366
|
+
- Verify `name` is unique
|
|
1367
|
+
- Confirm `service.host` and `service.port` are correct
|
|
1368
|
+
- Ensure `dependsOn` modules exist
|
|
1369
|
+
|
|
1370
|
+
**Verify module is accessible**:
|
|
1371
|
+
```bash
|
|
1372
|
+
# Test service endpoint
|
|
1373
|
+
curl http://my-new-app-service:4010/health
|
|
1374
|
+
```
|
|
1375
|
+
|
|
1376
|
+
### Operations Not Working
|
|
1377
|
+
|
|
1378
|
+
**Verify operation binding**:
|
|
1379
|
+
- Check operation name matches in `operations.json`
|
|
1380
|
+
- Confirm role is listed in operation's `roles` array
|
|
1381
|
+
- Verify users have the role assigned
|
|
1382
|
+
|
|
1383
|
+
**Test authorization**:
|
|
1384
|
+
```bash
|
|
1385
|
+
# Get user's authorized resources
|
|
1386
|
+
curl https://platform.com/authz/users/{username}/resources
|
|
1387
|
+
```
|
|
1388
|
+
|
|
1389
|
+
### Environment Variable Not Resolved
|
|
1390
|
+
|
|
1391
|
+
**Error message**:
|
|
1392
|
+
```
|
|
1393
|
+
Error reading <file>.json file: Expression resolution failed: {{$ENV.VARIABLE_NAME}} is not defined in environment variables
|
|
1394
|
+
```
|
|
1395
|
+
|
|
1396
|
+
**Check that the variable is set** in the bootstrap service container:
|
|
1397
|
+
```bash
|
|
1398
|
+
docker-compose exec platform-bootstrap-service env | grep VARIABLE_NAME
|
|
1399
|
+
```
|
|
1400
|
+
|
|
1401
|
+
**Common causes**:
|
|
1402
|
+
- Variable not declared in the `environment:` or `env_file:` section of the bootstrap service in `docker-compose.yml`
|
|
1403
|
+
- Typo in the variable name in the JSON file
|
|
1404
|
+
- `.env` file not loaded or missing
|
|
1405
|
+
|
|
1406
|
+
**Make expressions tolerant of missing variables**:
|
|
1407
|
+
|
|
1408
|
+
If a variable is genuinely optional (e.g. a debug port that is only relevant in some environments), use the `?` or `?=` suffixes instead of requiring the variable to always be present:
|
|
1409
|
+
|
|
1410
|
+
```json
|
|
1411
|
+
{
|
|
1412
|
+
"port": "{{$ENV.CRM_PORT?=4002}}",
|
|
1413
|
+
"debugPort": "{{$ENV.DEBUG_PORT?}}",
|
|
1414
|
+
"logLevel": "{{$ENV.LOG_LEVEL?=$ENV.DEFAULT_LOG_LEVEL?=info}}"
|
|
1415
|
+
}
|
|
1416
|
+
```
|
|
1417
|
+
|
|
1418
|
+
- `?=4002` — uses `4002` when `CRM_PORT` is not set (no error, no missing key)
|
|
1419
|
+
- `?` — omits the `debugPort` key entirely when `DEBUG_PORT` is not set
|
|
1420
|
+
- `?=$ENV.DEFAULT_LOG_LEVEL?=info` — tries a fallback variable, then a literal default
|
|
1421
|
+
|
|
1422
|
+
---
|
|
1423
|
+
|
|
1424
|
+
## Migration from Old Bootstrap
|
|
1425
|
+
|
|
1426
|
+
If migrating from an older bootstrap structure:
|
|
1427
|
+
|
|
1428
|
+
1. **Review Current Definitions**: Export existing catalog and authorization data
|
|
1429
|
+
2. **Create New Structure**: Organize by application following the new folder structure
|
|
1430
|
+
3. **Test Synchronization**: Run bootstrap in development environment
|
|
1431
|
+
4. **Validate Results**: Verify all modules and operations are correctly registered
|
|
1432
|
+
5. **Deploy**: Roll out to staging and production
|
|
1433
|
+
|
|
1434
|
+
---
|
|
1435
|
+
|
|
1436
|
+
## Next Steps
|
|
1437
|
+
|
|
1438
|
+
- **[Catalog Architecture](/_/docs/architecture/catalog/)** - Understanding the application catalog
|
|
1439
|
+
- **[Authorization System](/_/docs/architecture/authorization-system/)** - Deep dive into roles and operations
|
|
1440
|
+
- **[Shell Customization](/_/docs/architecture/shell/)** - Customizing the Platform Shell via bootstrap
|
|
1441
|
+
- **[Scheduler Module](/_/docs/architecture/scheduler/)** - Full reference for job types, cron syntax, and dynamic parameters
|
|
1442
|
+
- **[Feature Flags](/guides/using-feature-flags/)** - Using feature flags in services and React applications
|