@getstrata/bootstrap 0.2.47 → 0.2.48
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 +4 -0
- package/dist/_.._/_.._/index.html +174 -0
- package/dist/bootstrap/discoverModules.d.ts +2 -1
- package/dist/bootstrap/inProcessCron.d.ts +4 -0
- package/dist/bootstrap/metricsRoutes.d.ts +1 -1
- package/dist/bootstrap/public-api.d.ts +0 -1
- package/dist/entries/applicationRegistry.js +8 -8
- package/dist/entries/buildModuleRoutes.js +13 -7
- package/dist/entries/buildWebModuleRoutes.js +12 -6
- package/dist/entries/cache/modelCacheTags.js +9 -3
- package/dist/entries/config.js +19 -19
- package/dist/entries/context.js +33 -51
- package/dist/entries/contracts.js +4 -4
- package/dist/entries/createRoutes.js +45 -116
- package/dist/entries/createSpaRoutes.js +2 -2
- package/dist/entries/createWebRoutes.js +14 -8
- package/dist/entries/dependencies.js +32 -50
- package/dist/entries/discoverModules.js +10 -3
- package/dist/entries/health.js +2 -2
- package/dist/entries/http/securedRouteModelBinding.js +2 -2
- package/dist/entries/httpKernel.js +6 -6
- package/dist/entries/listeners/invalidateCacheOnModelWrite.js +7 -1
- package/dist/entries/metricsRoutes.js +31 -5
- package/dist/entries/providers/view.js +2 -2
- package/dist/entries/providers.js +7 -1
- package/dist/entries/routeRegistry.js +2 -2
- package/dist/entries/secretsGuard.js +23 -56
- package/dist/entries/web/forms.js +2 -2
- package/dist/entries/web/routing.js +9 -9
- package/dist/entries/web/server.js +2 -2
- package/dist/index.js +90 -176
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -28,4 +28,8 @@ import {
|
|
|
28
28
|
import { createHttpKernel, createAppContext, coreProviders } from "@getstrata/bootstrap";
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
`assertProductionSecrets` (`@getstrata/bootstrap/secretsGuard`) is env-only. It still rejects the well-known WorkHub test tokens.
|
|
32
|
+
|
|
33
|
+
These subpaths remain WorkHub-oriented and are not a generic starter API: `@getstrata/bootstrap/createRoutes` (includes SCIM), `@getstrata/bootstrap/schedule`, and `@getstrata/bootstrap/createWebRoutes` (redirects `/` to `/organizations`).
|
|
34
|
+
|
|
31
35
|
See the [strata](https://github.com/EyK-26/strata) monorepo reference app for full module patterns.
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>WorkHub API</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root {
|
|
9
|
+
color-scheme: dark;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
body {
|
|
13
|
+
font-family: sans-serif;
|
|
14
|
+
background-color: #000;
|
|
15
|
+
color: #fff;
|
|
16
|
+
margin: 0;
|
|
17
|
+
padding: 2rem;
|
|
18
|
+
line-height: 1.5;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
h1,
|
|
22
|
+
h2 {
|
|
23
|
+
margin-top: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
section {
|
|
27
|
+
margin-bottom: 2rem;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
ul {
|
|
31
|
+
padding-left: 1.25rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
li {
|
|
35
|
+
padding: 0.35rem 0;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
a {
|
|
39
|
+
color: #fff;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
code,
|
|
43
|
+
input,
|
|
44
|
+
button {
|
|
45
|
+
font: inherit;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
form {
|
|
49
|
+
display: flex;
|
|
50
|
+
flex-wrap: wrap;
|
|
51
|
+
gap: 0.5rem;
|
|
52
|
+
margin: 0.75rem 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
input {
|
|
56
|
+
border: 1px solid #555;
|
|
57
|
+
background-color: #111;
|
|
58
|
+
color: #fff;
|
|
59
|
+
padding: 0.5rem 0.75rem;
|
|
60
|
+
border-radius: 0.35rem;
|
|
61
|
+
min-width: 14rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
button {
|
|
65
|
+
border: 1px solid #777;
|
|
66
|
+
background-color: #222;
|
|
67
|
+
color: #fff;
|
|
68
|
+
padding: 0.5rem 0.9rem;
|
|
69
|
+
border-radius: 0.35rem;
|
|
70
|
+
cursor: pointer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.hint {
|
|
74
|
+
color: #bbb;
|
|
75
|
+
font-size: 0.95rem;
|
|
76
|
+
}
|
|
77
|
+
</style>
|
|
78
|
+
<script type="module" crossorigin src="../../index-sfreg6q3.js"></script></head>
|
|
79
|
+
|
|
80
|
+
<body>
|
|
81
|
+
<h1>WorkHub API</h1>
|
|
82
|
+
<p class="hint">
|
|
83
|
+
Framework reference domain for organizations, projects, tasks, and
|
|
84
|
+
comments.
|
|
85
|
+
</p>
|
|
86
|
+
|
|
87
|
+
<section>
|
|
88
|
+
<h2>Collections</h2>
|
|
89
|
+
<ul>
|
|
90
|
+
<li><a href="/organizations">/organizations</a></li>
|
|
91
|
+
<li><a href="/projects?include=organization">/projects</a></li>
|
|
92
|
+
<li><a href="/tasks?include=project">/tasks</a></li>
|
|
93
|
+
<li><a href="/comments">/comments</a></li>
|
|
94
|
+
<li><a href="/reports/summary">/reports/summary</a></li>
|
|
95
|
+
</ul>
|
|
96
|
+
</section>
|
|
97
|
+
|
|
98
|
+
<section>
|
|
99
|
+
<h2>Lookup by ID</h2>
|
|
100
|
+
|
|
101
|
+
<form data-prefix="/organizations/">
|
|
102
|
+
<input
|
|
103
|
+
type="number"
|
|
104
|
+
min="1"
|
|
105
|
+
name="id"
|
|
106
|
+
placeholder="Organization ID"
|
|
107
|
+
required
|
|
108
|
+
/>
|
|
109
|
+
<button type="submit">Open /organizations/:id</button>
|
|
110
|
+
</form>
|
|
111
|
+
|
|
112
|
+
<form data-prefix="/projects/">
|
|
113
|
+
<input
|
|
114
|
+
type="number"
|
|
115
|
+
min="1"
|
|
116
|
+
name="id"
|
|
117
|
+
placeholder="Project ID"
|
|
118
|
+
required
|
|
119
|
+
/>
|
|
120
|
+
<button type="submit">Open /projects/:id</button>
|
|
121
|
+
</form>
|
|
122
|
+
|
|
123
|
+
<form data-prefix="/tasks/">
|
|
124
|
+
<input
|
|
125
|
+
type="number"
|
|
126
|
+
min="1"
|
|
127
|
+
name="id"
|
|
128
|
+
placeholder="Task ID"
|
|
129
|
+
required
|
|
130
|
+
/>
|
|
131
|
+
<button type="submit">Open /tasks/:id</button>
|
|
132
|
+
</form>
|
|
133
|
+
|
|
134
|
+
<form data-prefix="/tasks/" data-suffix="/comments">
|
|
135
|
+
<input
|
|
136
|
+
type="number"
|
|
137
|
+
min="1"
|
|
138
|
+
name="id"
|
|
139
|
+
placeholder="Task ID"
|
|
140
|
+
required
|
|
141
|
+
/>
|
|
142
|
+
<button type="submit">Open /tasks/:id/comments</button>
|
|
143
|
+
</form>
|
|
144
|
+
|
|
145
|
+
<form data-prefix="/reports/organizations/">
|
|
146
|
+
<input
|
|
147
|
+
type="number"
|
|
148
|
+
min="1"
|
|
149
|
+
name="id"
|
|
150
|
+
placeholder="Organization ID"
|
|
151
|
+
required
|
|
152
|
+
/>
|
|
153
|
+
<button type="submit">Open /reports/organizations/:id</button>
|
|
154
|
+
</form>
|
|
155
|
+
</section>
|
|
156
|
+
|
|
157
|
+
<script>
|
|
158
|
+
for (const form of document.querySelectorAll("form[data-prefix]")) {
|
|
159
|
+
form.addEventListener("submit", (event) => {
|
|
160
|
+
event.preventDefault();
|
|
161
|
+
const prefix = form.getAttribute("data-prefix");
|
|
162
|
+
const suffix = form.getAttribute("data-suffix") ?? "";
|
|
163
|
+
const input = form.querySelector('input[name="id"]');
|
|
164
|
+
|
|
165
|
+
if (!prefix || !input?.value) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
window.location.href = `${prefix}${input.value}${suffix}`;
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
</script>
|
|
173
|
+
</body>
|
|
174
|
+
</html>
|
|
@@ -5,5 +5,6 @@ interface DiscoverModulesOptions {
|
|
|
5
5
|
declare function configureModulesDirectory(modulesDir: string): void;
|
|
6
6
|
declare function ensureModulesLoaded(options?: DiscoverModulesOptions): Promise<AppModule[]>;
|
|
7
7
|
declare function discoverModules(): AppModule[];
|
|
8
|
+
declare function resetDiscoverModulesForTests(): void;
|
|
8
9
|
export type { DiscoverModulesOptions };
|
|
9
|
-
export { configureModulesDirectory, discoverModules, ensureModulesLoaded };
|
|
10
|
+
export { configureModulesDirectory, discoverModules, ensureModulesLoaded, resetDiscoverModulesForTests, };
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export type { ScheduledTask } from "@getstrata/core/scheduler/schedule";
|
|
5
5
|
export { appSchedule, runDueScheduledTasks, Schedule } from "@getstrata/core/scheduler/schedule";
|
|
6
|
-
export { scheduleRunCommand } from "../cli/commands/scheduleRun.ts";
|
|
7
6
|
export { resolveApplicationAuth, resolveApplicationCache, resolveApplicationConfig, resolveApplicationDependencies, resolveApplicationEventBus, resolveApplicationLogger, resolveApplicationPolicyGate, resolveApplicationQueue, setActiveApplicationContext, } from "./applicationRegistry.ts";
|
|
8
7
|
export type { BuildModuleRoutesOptions } from "./buildModuleRoutes.ts";
|
|
9
8
|
export { buildModuleRoutes } from "./buildModuleRoutes.ts";
|
|
@@ -14,13 +14,13 @@ import {
|
|
|
14
14
|
setActiveApplicationContext
|
|
15
15
|
} from "@getstrata/core/runtime/applicationRegistry";
|
|
16
16
|
export {
|
|
17
|
-
|
|
18
|
-
resolveApplicationQueue,
|
|
19
|
-
resolveApplicationPolicyGate,
|
|
20
|
-
resolveApplicationLogger,
|
|
21
|
-
resolveApplicationEventBus,
|
|
22
|
-
resolveApplicationDependencies,
|
|
23
|
-
resolveApplicationConfig,
|
|
17
|
+
resolveApplicationAuth,
|
|
24
18
|
resolveApplicationCache,
|
|
25
|
-
|
|
19
|
+
resolveApplicationConfig,
|
|
20
|
+
resolveApplicationDependencies,
|
|
21
|
+
resolveApplicationEventBus,
|
|
22
|
+
resolveApplicationLogger,
|
|
23
|
+
resolveApplicationPolicyGate,
|
|
24
|
+
resolveApplicationQueue,
|
|
25
|
+
setActiveApplicationContext
|
|
26
26
|
};
|
|
@@ -173,7 +173,7 @@ class HttpKernel {
|
|
|
173
173
|
return this.wrap(["api", "authenticated"], handler);
|
|
174
174
|
}
|
|
175
175
|
wrapWeb(handler) {
|
|
176
|
-
return handler;
|
|
176
|
+
return this.wrap("web", handler);
|
|
177
177
|
}
|
|
178
178
|
wrapWebPublicRead(handler) {
|
|
179
179
|
if (isPublicReadsEnabled()) {
|
|
@@ -183,19 +183,19 @@ class HttpKernel {
|
|
|
183
183
|
}
|
|
184
184
|
wrapWebAuthenticated(handler) {
|
|
185
185
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
186
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
186
|
+
return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
187
187
|
}
|
|
188
188
|
wrapWebAbility(ability, handler) {
|
|
189
189
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
190
190
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
191
191
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
192
192
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
193
|
-
return withMiddleware(...middleware)(handler);
|
|
193
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
194
194
|
}
|
|
195
195
|
wrapWebGlobalAdmin(handler) {
|
|
196
196
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
197
197
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
198
|
-
return withMiddleware(...middleware)(handler);
|
|
198
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
199
199
|
}
|
|
200
200
|
wrapAuthenticated(handler) {
|
|
201
201
|
return this.wrap("authenticated", handler);
|
|
@@ -294,7 +294,7 @@ function resolveModulesDirectory(options) {
|
|
|
294
294
|
if (state.configuredModulesDir) {
|
|
295
295
|
return state.configuredModulesDir;
|
|
296
296
|
}
|
|
297
|
-
|
|
297
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
298
298
|
}
|
|
299
299
|
async function loadDiscoveredModules(options) {
|
|
300
300
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -328,6 +328,12 @@ async function ensureModulesLoaded(options) {
|
|
|
328
328
|
function discoverModules() {
|
|
329
329
|
return readDiscoverModulesState().appModules;
|
|
330
330
|
}
|
|
331
|
+
function resetDiscoverModulesForTests() {
|
|
332
|
+
const state = readDiscoverModulesState();
|
|
333
|
+
state.configuredModulesDir = undefined;
|
|
334
|
+
state.appModules.length = 0;
|
|
335
|
+
state.modulesReady = undefined;
|
|
336
|
+
}
|
|
331
337
|
// ../../src/bootstrap/prefixRouteMap.ts
|
|
332
338
|
function prefixRouteMap(prefix, routes) {
|
|
333
339
|
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
@@ -409,6 +415,6 @@ function buildModuleRoutes(dependencies, options = {}) {
|
|
|
409
415
|
return applyMiddlewareToRoutes(registerOpenApiRouteMap(prefixedModuleRoutes, ["global", "api"]), middleware);
|
|
410
416
|
}
|
|
411
417
|
export {
|
|
412
|
-
|
|
413
|
-
|
|
418
|
+
buildModuleRoutes,
|
|
419
|
+
registerOpenApiRouteMap
|
|
414
420
|
};
|
|
@@ -176,7 +176,7 @@ class HttpKernel {
|
|
|
176
176
|
return this.wrap(["api", "authenticated"], handler);
|
|
177
177
|
}
|
|
178
178
|
wrapWeb(handler) {
|
|
179
|
-
return handler;
|
|
179
|
+
return this.wrap("web", handler);
|
|
180
180
|
}
|
|
181
181
|
wrapWebPublicRead(handler) {
|
|
182
182
|
if (isPublicReadsEnabled()) {
|
|
@@ -186,19 +186,19 @@ class HttpKernel {
|
|
|
186
186
|
}
|
|
187
187
|
wrapWebAuthenticated(handler) {
|
|
188
188
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
189
|
-
return withMiddleware(createRequireWebAuthMiddleware(auth))(handler);
|
|
189
|
+
return this.wrap("web", withMiddleware(createRequireWebAuthMiddleware(auth))(handler));
|
|
190
190
|
}
|
|
191
191
|
wrapWebAbility(ability, handler) {
|
|
192
192
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
193
193
|
const abilityChecker = this.dependencies.container.resolve(CORE_TOKEN_SERVICE_TOKEN);
|
|
194
194
|
const requireAbility = createRequireAbilityMiddleware(abilityChecker);
|
|
195
195
|
const middleware = [createRequireWebAuthMiddleware(auth), requireAbility(ability)];
|
|
196
|
-
return withMiddleware(...middleware)(handler);
|
|
196
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
197
197
|
}
|
|
198
198
|
wrapWebGlobalAdmin(handler) {
|
|
199
199
|
const auth = this.dependencies.container.resolve(CORE_AUTH_TOKEN);
|
|
200
200
|
const middleware = [createRequireWebAuthMiddleware(auth), createRequireGlobalAdminMiddleware()];
|
|
201
|
-
return withMiddleware(...middleware)(handler);
|
|
201
|
+
return this.wrap("web", withMiddleware(...middleware)(handler));
|
|
202
202
|
}
|
|
203
203
|
wrapAuthenticated(handler) {
|
|
204
204
|
return this.wrap("authenticated", handler);
|
|
@@ -297,7 +297,7 @@ function resolveModulesDirectory(options) {
|
|
|
297
297
|
if (state.configuredModulesDir) {
|
|
298
298
|
return state.configuredModulesDir;
|
|
299
299
|
}
|
|
300
|
-
|
|
300
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
301
301
|
}
|
|
302
302
|
async function loadDiscoveredModules(options) {
|
|
303
303
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -331,6 +331,12 @@ async function ensureModulesLoaded(options) {
|
|
|
331
331
|
function discoverModules() {
|
|
332
332
|
return readDiscoverModulesState().appModules;
|
|
333
333
|
}
|
|
334
|
+
function resetDiscoverModulesForTests() {
|
|
335
|
+
const state = readDiscoverModulesState();
|
|
336
|
+
state.configuredModulesDir = undefined;
|
|
337
|
+
state.appModules.length = 0;
|
|
338
|
+
state.modulesReady = undefined;
|
|
339
|
+
}
|
|
334
340
|
// ../../src/bootstrap/prefixRouteMap.ts
|
|
335
341
|
function prefixRouteMap(prefix, routes) {
|
|
336
342
|
const normalizedPrefix = prefix.replace(/\/$/, "");
|
|
@@ -419,7 +425,7 @@ function buildWebModuleRoutes(dependencies, options = {}) {
|
|
|
419
425
|
routeRegistry.clear();
|
|
420
426
|
}
|
|
421
427
|
const kernel = createHttpKernel(dependencies);
|
|
422
|
-
const middleware =
|
|
428
|
+
const middleware = kernel.globalMiddleware();
|
|
423
429
|
const moduleRoutes = { ...seedRoutes };
|
|
424
430
|
for (const module of modules) {
|
|
425
431
|
if (!module.webRoutes) {
|
|
@@ -26,7 +26,7 @@ function resolveModulesDirectory(options) {
|
|
|
26
26
|
if (state.configuredModulesDir) {
|
|
27
27
|
return state.configuredModulesDir;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
30
30
|
}
|
|
31
31
|
async function loadDiscoveredModules(options) {
|
|
32
32
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -60,6 +60,12 @@ async function ensureModulesLoaded(options) {
|
|
|
60
60
|
function discoverModules() {
|
|
61
61
|
return readDiscoverModulesState().appModules;
|
|
62
62
|
}
|
|
63
|
+
function resetDiscoverModulesForTests() {
|
|
64
|
+
const state = readDiscoverModulesState();
|
|
65
|
+
state.configuredModulesDir = undefined;
|
|
66
|
+
state.appModules.length = 0;
|
|
67
|
+
state.modulesReady = undefined;
|
|
68
|
+
}
|
|
63
69
|
|
|
64
70
|
// ../../src/bootstrap/cache/modelCacheTags.ts
|
|
65
71
|
function cacheTagsForModelWrite(tableName, action) {
|
|
@@ -73,6 +79,6 @@ function discoverModelTableNames() {
|
|
|
73
79
|
return discoverModules().map((module) => module.tableName).filter((tableName) => tableName !== undefined);
|
|
74
80
|
}
|
|
75
81
|
export {
|
|
76
|
-
|
|
77
|
-
|
|
82
|
+
cacheTagsForModelWrite,
|
|
83
|
+
discoverModelTableNames
|
|
78
84
|
};
|
package/dist/entries/config.js
CHANGED
|
@@ -25,24 +25,24 @@ var DEFAULT_CACHE_DRIVER = "array";
|
|
|
25
25
|
var DEFAULT_API_TOKEN = "";
|
|
26
26
|
var DEFAULT_QUEUE_DRIVER = "sync";
|
|
27
27
|
export {
|
|
28
|
-
|
|
29
|
-
DEFAULT_QUEUE_DRIVER,
|
|
30
|
-
DEFAULT_CACHE_TTL_MS,
|
|
31
|
-
DEFAULT_CACHE_MAX_ENTRIES,
|
|
32
|
-
DEFAULT_CACHE_DRIVER,
|
|
33
|
-
DEFAULT_APP_PORT,
|
|
34
|
-
DEFAULT_API_TOKEN,
|
|
35
|
-
DATABASE_URL_CONFIG_KEY,
|
|
36
|
-
CORE_TOKEN_SERVICE_TOKEN,
|
|
37
|
-
CORE_QUEUE_TOKEN,
|
|
38
|
-
CORE_POLICY_GATE_TOKEN,
|
|
39
|
-
CORE_EVENT_BUS_TOKEN,
|
|
40
|
-
CORE_CONFIG_TOKEN,
|
|
41
|
-
CORE_CACHE_TOKEN,
|
|
42
|
-
CORE_AUTH_TOKEN,
|
|
43
|
-
CACHE_TTL_MS_CONFIG_KEY,
|
|
44
|
-
CACHE_MAX_ENTRIES_CONFIG_KEY,
|
|
45
|
-
CACHE_DRIVER_CONFIG_KEY,
|
|
28
|
+
APP_PORT_CONFIG_KEY,
|
|
46
29
|
AUTH_DEV_HEADERS_CONFIG_KEY,
|
|
47
|
-
|
|
30
|
+
CACHE_DRIVER_CONFIG_KEY,
|
|
31
|
+
CACHE_MAX_ENTRIES_CONFIG_KEY,
|
|
32
|
+
CACHE_TTL_MS_CONFIG_KEY,
|
|
33
|
+
CORE_AUTH_TOKEN,
|
|
34
|
+
CORE_CACHE_TOKEN,
|
|
35
|
+
CORE_CONFIG_TOKEN,
|
|
36
|
+
CORE_EVENT_BUS_TOKEN,
|
|
37
|
+
CORE_POLICY_GATE_TOKEN,
|
|
38
|
+
CORE_QUEUE_TOKEN,
|
|
39
|
+
CORE_TOKEN_SERVICE_TOKEN,
|
|
40
|
+
DATABASE_URL_CONFIG_KEY,
|
|
41
|
+
DEFAULT_API_TOKEN,
|
|
42
|
+
DEFAULT_APP_PORT,
|
|
43
|
+
DEFAULT_CACHE_DRIVER,
|
|
44
|
+
DEFAULT_CACHE_MAX_ENTRIES,
|
|
45
|
+
DEFAULT_CACHE_TTL_MS,
|
|
46
|
+
DEFAULT_QUEUE_DRIVER,
|
|
47
|
+
REDIS_URL_CONFIG_KEY
|
|
48
48
|
};
|
package/dist/entries/context.js
CHANGED
|
@@ -50,7 +50,7 @@ function resolveModulesDirectory(options) {
|
|
|
50
50
|
if (state.configuredModulesDir) {
|
|
51
51
|
return state.configuredModulesDir;
|
|
52
52
|
}
|
|
53
|
-
|
|
53
|
+
throw new Error("configureModulesDirectory() must be called before discovering modules. WorkHub and the starter do this from preload.");
|
|
54
54
|
}
|
|
55
55
|
async function loadDiscoveredModules(options) {
|
|
56
56
|
const modulesDirectory = resolveModulesDirectory(options);
|
|
@@ -84,6 +84,12 @@ async function ensureModulesLoaded(options) {
|
|
|
84
84
|
function discoverModules() {
|
|
85
85
|
return readDiscoverModulesState().appModules;
|
|
86
86
|
}
|
|
87
|
+
function resetDiscoverModulesForTests() {
|
|
88
|
+
const state = readDiscoverModulesState();
|
|
89
|
+
state.configuredModulesDir = undefined;
|
|
90
|
+
state.appModules.length = 0;
|
|
91
|
+
state.modulesReady = undefined;
|
|
92
|
+
}
|
|
87
93
|
// ../../src/bootstrap/providers/auth.ts
|
|
88
94
|
import {
|
|
89
95
|
AuthManager,
|
|
@@ -500,55 +506,28 @@ var coreProviders = [
|
|
|
500
506
|
viewProvider
|
|
501
507
|
];
|
|
502
508
|
|
|
503
|
-
// ../../src/config/features.ts
|
|
504
|
-
function readFeatureFlags() {
|
|
505
|
-
return {
|
|
506
|
-
webhooks: (process.env.FEATURE_WEBHOOKS ?? "true") !== "false",
|
|
507
|
-
fullTextSearch: (process.env.FEATURE_SEARCH ?? "true") !== "false",
|
|
508
|
-
auditLog: (process.env.FEATURE_AUDIT_LOG ?? "true") !== "false",
|
|
509
|
-
oauthLogin: (process.env.FEATURE_OAUTH ?? "true") !== "false",
|
|
510
|
-
samlLogin: (process.env.FEATURE_SAML ?? "false") === "true",
|
|
511
|
-
scim: (process.env.FEATURE_SCIM ?? "true") !== "false",
|
|
512
|
-
billing: (process.env.FEATURE_BILLING ?? "true") !== "false",
|
|
513
|
-
siemExport: (process.env.FEATURE_SIEM_EXPORT ?? "true") !== "false",
|
|
514
|
-
publicReads: (process.env.FEATURE_PUBLIC_READS ?? "true") !== "false",
|
|
515
|
-
emailVerification: (process.env.FEATURE_EMAIL_VERIFICATION ?? "false") === "true",
|
|
516
|
-
mfa: (process.env.FEATURE_MFA ?? "false") === "true"
|
|
517
|
-
};
|
|
518
|
-
}
|
|
519
|
-
var featureFlags = readFeatureFlags();
|
|
520
|
-
function isFeatureEnabled(feature) {
|
|
521
|
-
return readFeatureFlags()[feature];
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
// ../../src/domain/auth.ts
|
|
525
|
-
var TEST_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
526
|
-
var TEST_MEMBER_API_TOKEN = "workhub-member-test-token";
|
|
527
|
-
|
|
528
|
-
// ../../src/domain/scim.ts
|
|
529
|
-
var TEST_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
530
|
-
var DEFAULT_SCIM_BEARER_TOKEN = TEST_SCIM_BEARER_TOKEN;
|
|
531
|
-
var SCIM_SCHEMAS = {
|
|
532
|
-
user: "urn:ietf:params:scim:schemas:core:2.0:User",
|
|
533
|
-
group: "urn:ietf:params:scim:schemas:core:2.0:Group",
|
|
534
|
-
listResponse: "urn:ietf:params:scim:api:messages:2.0:ListResponse",
|
|
535
|
-
patchOp: "urn:ietf:params:scim:api:messages:2.0:PatchOp",
|
|
536
|
-
serviceProviderConfig: "urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"
|
|
537
|
-
};
|
|
538
|
-
|
|
539
509
|
// ../../src/bootstrap/secretsGuard.ts
|
|
540
|
-
var
|
|
541
|
-
var
|
|
510
|
+
var DEFAULT_ADMIN_API_TOKEN = "workhub-admin-test-token";
|
|
511
|
+
var DEFAULT_MEMBER_API_TOKEN = "workhub-member-test-token";
|
|
512
|
+
var DEFAULT_SCIM_BEARER_TOKEN = "workhub-scim-test-token";
|
|
513
|
+
var DEFAULT_TOKENS = new Set([DEFAULT_ADMIN_API_TOKEN, DEFAULT_MEMBER_API_TOKEN]);
|
|
514
|
+
var DEFAULT_SCIM_TOKENS = new Set([DEFAULT_SCIM_BEARER_TOKEN]);
|
|
515
|
+
function isEnabled(value, defaultEnabled) {
|
|
516
|
+
if (value === undefined) {
|
|
517
|
+
return defaultEnabled;
|
|
518
|
+
}
|
|
519
|
+
return defaultEnabled ? value !== "false" : value === "true";
|
|
520
|
+
}
|
|
542
521
|
function assertProductionSecrets(env = process.env) {
|
|
543
|
-
const appEnv = env.APP_ENV ??
|
|
522
|
+
const appEnv = env.APP_ENV ?? "local";
|
|
544
523
|
if (appEnv !== "production") {
|
|
545
524
|
return;
|
|
546
525
|
}
|
|
547
|
-
const adminToken = env.ADMIN_API_TOKEN ??
|
|
548
|
-
const memberToken = env.MEMBER_API_TOKEN ??
|
|
526
|
+
const adminToken = env.ADMIN_API_TOKEN ?? DEFAULT_ADMIN_API_TOKEN;
|
|
527
|
+
const memberToken = env.MEMBER_API_TOKEN ?? DEFAULT_MEMBER_API_TOKEN;
|
|
549
528
|
const scimToken = env.SCIM_BEARER_TOKEN ?? DEFAULT_SCIM_BEARER_TOKEN;
|
|
550
|
-
const encryptionEnabled = env.FEATURE_FIELD_ENCRYPTION
|
|
551
|
-
const devHeadersEnabled = (env.AUTH_DEV_HEADERS
|
|
529
|
+
const encryptionEnabled = isEnabled(env.FEATURE_FIELD_ENCRYPTION, true);
|
|
530
|
+
const devHeadersEnabled = isEnabled(env.AUTH_DEV_HEADERS, true);
|
|
552
531
|
if (devHeadersEnabled) {
|
|
553
532
|
throw new Error("Production startup blocked: set AUTH_DEV_HEADERS=false to disable development auth headers.");
|
|
554
533
|
}
|
|
@@ -561,18 +540,17 @@ function assertProductionSecrets(env = process.env) {
|
|
|
561
540
|
if (encryptionEnabled && !env.KMS_ENCRYPTION_KEY?.trim()) {
|
|
562
541
|
throw new Error("Production startup blocked: set KMS_ENCRYPTION_KEY when field encryption is enabled.");
|
|
563
542
|
}
|
|
564
|
-
if (!env.SIEM_EXPORT_URL?.trim() &&
|
|
543
|
+
if (!env.SIEM_EXPORT_URL?.trim() && isEnabled(env.FEATURE_SIEM_EXPORT, true)) {
|
|
565
544
|
console.warn("[secrets] SIEM_EXPORT_URL is not configured; audit logs remain database-only.");
|
|
566
545
|
}
|
|
567
|
-
|
|
568
|
-
if (billingEnabled && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
|
|
546
|
+
if (isEnabled(env.FEATURE_BILLING, true) && !env.STRIPE_WEBHOOK_SECRET?.trim()) {
|
|
569
547
|
throw new Error("Production startup blocked: set STRIPE_WEBHOOK_SECRET when billing webhooks are enabled.");
|
|
570
548
|
}
|
|
571
549
|
const corsOrigins = (env.CORS_ALLOWED_ORIGINS ?? "*").split(",").map((origin) => origin.trim());
|
|
572
550
|
if (corsOrigins.includes("*")) {
|
|
573
551
|
throw new Error("Production startup blocked: set explicit CORS_ALLOWED_ORIGINS instead of wildcard.");
|
|
574
552
|
}
|
|
575
|
-
if ((env.FEATURE_PUBLIC_READS
|
|
553
|
+
if (isEnabled(env.FEATURE_PUBLIC_READS, true)) {
|
|
576
554
|
throw new Error("Production startup blocked: set FEATURE_PUBLIC_READS=false for authenticated-only reads.");
|
|
577
555
|
}
|
|
578
556
|
if (!env.OAUTH_STATE_SECRET?.trim()) {
|
|
@@ -584,6 +562,10 @@ function assertProductionSecrets(env = process.env) {
|
|
|
584
562
|
if (!env.API_TOKEN_DEFAULT_EXPIRY_DAYS?.trim()) {
|
|
585
563
|
throw new Error("Production startup blocked: set API_TOKEN_DEFAULT_EXPIRY_DAYS to enforce token rotation.");
|
|
586
564
|
}
|
|
565
|
+
const frontendMode = (env.FRONTEND_MODE ?? "api").trim();
|
|
566
|
+
if (frontendMode === "server-htmx" && !env.SESSION_SECRET?.trim()) {
|
|
567
|
+
throw new Error("Production startup blocked: set SESSION_SECRET when FRONTEND_MODE=server-htmx.");
|
|
568
|
+
}
|
|
587
569
|
}
|
|
588
570
|
|
|
589
571
|
// ../../src/bootstrap/context.ts
|
|
@@ -636,8 +618,8 @@ var appContext = {
|
|
|
636
618
|
}
|
|
637
619
|
};
|
|
638
620
|
export {
|
|
639
|
-
|
|
640
|
-
createAppContext,
|
|
621
|
+
appContext,
|
|
641
622
|
collectProviders,
|
|
642
|
-
|
|
623
|
+
createAppContext,
|
|
624
|
+
runProviderPhase
|
|
643
625
|
};
|
|
@@ -12,9 +12,9 @@ import {
|
|
|
12
12
|
resolveService
|
|
13
13
|
} from "@getstrata/core/contracts/di";
|
|
14
14
|
export {
|
|
15
|
-
|
|
16
|
-
getRequiredDependency,
|
|
17
|
-
assertAppDependenciesComplete,
|
|
15
|
+
ConfigStore,
|
|
18
16
|
ServiceContainer,
|
|
19
|
-
|
|
17
|
+
assertAppDependenciesComplete,
|
|
18
|
+
getRequiredDependency,
|
|
19
|
+
resolveService
|
|
20
20
|
};
|