@o3co/create-auth-provider 0.16.0 → 0.17.0-rc.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/README.ja.md +66 -21
- package/README.md +76 -31
- package/dist/index.d.mts +33 -10
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +118 -59
- package/dist/internal/template-filter.d.mts +7 -10
- package/dist/internal/template-filter.d.mts.map +1 -1
- package/dist/internal/template-filter.mjs +7 -10
- package/package.json +6 -4
- package/templates/standalone/.dockerignore +15 -2
- package/templates/standalone/.env.example +59 -29
- package/templates/standalone/Dockerfile +6 -2
- package/templates/standalone/README.ja.md +324 -169
- package/templates/standalone/README.md +618 -208
- package/templates/standalone/config/application.conf +211 -352
- package/templates/standalone/config/clients.yaml +2 -0
- package/templates/standalone/config/clients.yaml.example +4 -0
- package/templates/standalone/config/development.conf +13 -4
- package/templates/standalone/config/mailpit.conf +8 -0
- package/templates/standalone/config/production.conf +5 -6
- package/templates/standalone/config/reference.conf +418 -0
- package/templates/standalone/docker-compose.mailpit.yml +36 -0
- package/templates/standalone/docker-compose.production.yml +63 -32
- package/templates/standalone/docker-compose.yml +13 -3
- package/templates/standalone/gitignore +1 -0
- package/templates/standalone/package.json +9 -6
- package/templates/standalone/src/__tests__/adapters.test.mts +419 -0
- package/templates/standalone/src/__tests__/admission-lifecycle.test.mts +87 -0
- package/templates/standalone/src/__tests__/all-modules-composition.fixture.mts +287 -198
- package/templates/standalone/src/__tests__/all-modules-composition.multi.test.mts +71 -35
- package/templates/standalone/src/__tests__/all-modules-composition.test.mts +148 -94
- package/templates/standalone/src/__tests__/audit-sink.test.mts +84 -81
- package/templates/standalone/src/__tests__/config-notices.test.mts +154 -0
- package/templates/standalone/src/__tests__/configPath.test.mts +13 -11
- package/templates/standalone/src/__tests__/core-federations.test.mts +249 -0
- package/templates/standalone/src/__tests__/core-sections.test.mts +190 -0
- package/templates/standalone/src/__tests__/device-code-store-client-module.test.mts +10 -15
- package/templates/standalone/src/__tests__/documented-env-overrides.test.mts +970 -288
- package/templates/standalone/src/__tests__/federation-grants-composition.test.mts +421 -86
- package/templates/standalone/src/__tests__/federation-grants-survive-logout.test.mts +87 -72
- package/templates/standalone/src/__tests__/federation-token-refresh-outage.test.mts +16 -8
- package/templates/standalone/src/__tests__/federation-types.test.mts +239 -0
- package/templates/standalone/src/__tests__/google-session.fixture.mts +97 -93
- package/templates/standalone/src/__tests__/host-routes.test.mts +4 -11
- package/templates/standalone/src/__tests__/library-reference-resolution.smoke.test.mts +1 -1
- package/templates/standalone/src/__tests__/library-references.fixture.mts +188 -0
- package/templates/standalone/src/__tests__/lifecycle-pending-close.test.mts +216 -0
- package/templates/standalone/src/__tests__/listen.test.mts +6 -13
- package/templates/standalone/src/__tests__/logger-projection.test.mts +1 -1
- package/templates/standalone/src/__tests__/mail-sender.test.mts +134 -0
- package/templates/standalone/src/__tests__/mailpit-compose.test.mts +326 -0
- package/templates/standalone/src/__tests__/metrics.test.mts +4 -9
- package/templates/standalone/src/__tests__/mfa-routes-budget.test.mts +122 -0
- package/templates/standalone/src/__tests__/mfa-switch.redis.test.mts +123 -0
- package/templates/standalone/src/__tests__/mfa-switch.test.mts +825 -0
- package/templates/standalone/src/__tests__/own-modules.test.mts +1486 -0
- package/templates/standalone/src/__tests__/post-logout-redirect.test.mts +10 -14
- package/templates/standalone/src/__tests__/redis-connection-error-log.test.mts +1 -5
- package/templates/standalone/src/__tests__/redis-rate-limiter-config-survives.test.mts +116 -81
- package/templates/standalone/src/__tests__/refresh-token-family-client-module.test.mts +25 -31
- package/templates/standalone/src/__tests__/replica-safety.test.mts +265 -146
- package/templates/standalone/src/__tests__/router-error-answers.test.mts +4 -7
- package/templates/standalone/src/__tests__/scaffold-assets.test.mts +278 -103
- package/templates/standalone/src/__tests__/sections.test.mts +480 -0
- package/templates/standalone/src/__tests__/session-lifecycle.test.mts +215 -0
- package/templates/standalone/src/__tests__/session-logout-invalidates-token.test.mts +69 -61
- package/templates/standalone/src/__tests__/session-logout-lifecycle.test.mts +130 -0
- package/templates/standalone/src/__tests__/session-requirements.test.mts +382 -0
- package/templates/standalone/src/__tests__/shipped-config-notices.test.mts +270 -0
- package/templates/standalone/src/__tests__/shutdown.test.mts +163 -96
- package/templates/standalone/src/__tests__/smoke.test.mts +176 -258
- package/templates/standalone/src/__tests__/store-credential.test.mts +88 -52
- package/templates/standalone/src/__tests__/supertest-loopback.test.mts +182 -11
- package/templates/standalone/src/__tests__/three-tier-resolution.test.mts +131 -102
- package/templates/standalone/src/__tests__/two-phase-config.test.mts +604 -0
- package/templates/standalone/src/__tests__/upstream-amr.acceptance.test.mts +382 -0
- package/templates/standalone/src/adapters.mts +162 -0
- package/templates/standalone/src/app.mts +65 -79
- package/templates/standalone/src/bootRefusal.mts +117 -0
- package/templates/standalone/src/buildModules.mts +220 -309
- package/templates/standalone/src/configPath.mts +449 -29
- package/templates/standalone/src/listen.mts +12 -18
- package/templates/standalone/src/logger.mts +31 -60
- package/templates/standalone/src/metrics.mts +24 -47
- package/templates/standalone/src/mfaSwitch.mts +285 -0
- package/templates/standalone/src/modules.mts +423 -629
- package/templates/standalone/src/rootRenames.mts +96 -0
- package/templates/standalone/src/routes.mts +8 -14
- package/templates/standalone/src/sections.mts +367 -0
- package/templates/standalone/src/shutdown.mts +45 -123
- package/templates/standalone/tests/index.test.js +6 -5
- package/templates/standalone/tests/package-lock.json +1615 -2223
- package/templates/standalone/tests/package.json +2 -2
- package/templates/standalone/tests/vitest.config.mjs +26 -0
- package/templates/standalone/vitest.config.mts +8 -7
- package/templates/standalone/vitest.supertest-loopback.mts +97 -69
- package/templates/versions.json +18 -15
- package/templates/standalone/src/__tests__/access-token-lifetime-alias.test.mts +0 -135
- package/templates/standalone/src/__tests__/google-federation-config-module.test.mts +0 -199
- package/templates/standalone/src/__tests__/oidc-federation-config-module.test.mts +0 -138
|
@@ -14,56 +14,49 @@
|
|
|
14
14
|
* limitations under the License.
|
|
15
15
|
*/
|
|
16
16
|
import { fileURLToPath } from "node:url";
|
|
17
|
-
import {
|
|
18
|
-
import { parseFile } from "@o3co/ts.hocon";
|
|
19
|
-
import { validate } from "@o3co/ts.hocon/zod";
|
|
17
|
+
import { createApp } from "@o3co/auth-provider-core";
|
|
20
18
|
import express from "express";
|
|
21
19
|
import helmet from "helmet";
|
|
22
20
|
import { buildModules } from "./buildModules.mjs";
|
|
23
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
configDefaultsFor,
|
|
23
|
+
readLogging,
|
|
24
|
+
readOwnLayers,
|
|
25
|
+
readSwitches,
|
|
26
|
+
resolveConfigPaths,
|
|
27
|
+
resolveForBoot,
|
|
28
|
+
type Switches,
|
|
29
|
+
} from "./configPath.mjs";
|
|
24
30
|
import { listen } from "./listen.mjs";
|
|
25
31
|
import { createAppLogger } from "./logger.mjs";
|
|
26
32
|
import { createMetrics } from "./metrics.mjs";
|
|
27
33
|
import { mountRoutes } from "./routes.mjs";
|
|
28
|
-
import {
|
|
34
|
+
import { installGracefulShutdown } from "./shutdown.mjs";
|
|
29
35
|
|
|
30
|
-
// Step 1:
|
|
36
|
+
// Step 1: the configuration, in two phases (`configPath.mts`; template
|
|
37
|
+
// README, "Environment-specific config overlay").
|
|
31
38
|
// ENV = CONFIG_ENV || NODE_ENV || "development"; missing {ENV}.conf is fatal.
|
|
32
|
-
//
|
|
33
|
-
// 3-tier HOCON precedence (highest → lowest):
|
|
34
|
-
// 1. {env}.conf — environment-specific overrides (e.g. production.conf)
|
|
35
|
-
// 2. application.conf — template-level consumer delta
|
|
36
|
-
// 3. reference.conf — library defaults shipped in @o3co/auth-provider-core
|
|
37
39
|
const env = process.env.CONFIG_ENV || process.env.NODE_ENV || "development";
|
|
38
40
|
const configDir = new URL("../config/", import.meta.url);
|
|
39
41
|
const configDirPath = fileURLToPath(configDir);
|
|
40
42
|
const { applicationConfPath, envConfPath } = resolveConfigPaths(configDirPath, env);
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
);
|
|
43
|
+
// Read once, so both phases read the same thing.
|
|
44
|
+
const own = readOwnLayers([envConfPath, applicationConfPath]);
|
|
45
|
+
// Phase one: what the template reads before it knows its modules, only for
|
|
46
|
+
// those choices — its own `adapters` and MFA switch (`mfaMode`, MFA_MODE),
|
|
47
|
+
// and whether federation grants are installed.
|
|
48
|
+
const switches: Switches = readSwitches(own);
|
|
48
49
|
|
|
49
|
-
//
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
|
|
53
|
-
// nothing about what the modules emit.
|
|
54
|
-
const logger = createAppLogger(config);
|
|
50
|
+
// From the `logging` module's section, so the level holds from the first line;
|
|
51
|
+
// wired into `bootstrapComponents` so every module logs through it (template
|
|
52
|
+
// README, "Logging").
|
|
53
|
+
const logger = createAppLogger(readLogging(own));
|
|
55
54
|
|
|
56
55
|
await (async (): Promise<void> => {
|
|
57
56
|
// Step 2: Create the Express app and apply base security middleware.
|
|
57
|
+
// `trust proxy` is set from the `http` module's `httpSettings` once boot
|
|
58
|
+
// has it (step 3), before a request can arrive.
|
|
58
59
|
const app = express();
|
|
59
|
-
// `false` | `true` | a hop count | a list of IPs / CIDR ranges / named
|
|
60
|
-
// ranges — the schema hands Express exactly the shapes `trust proxy`
|
|
61
|
-
// already understands, validated at boot so a typo'd range fails here
|
|
62
|
-
// rather than becoming a rule that silently never matches (#292). Prefer
|
|
63
|
-
// naming the proxy: `true` believes a forwarded client address from anyone
|
|
64
|
-
// who can reach this process, and `req.ip` is what every IP-keyed rate
|
|
65
|
-
// limit is bucketed on.
|
|
66
|
-
app.set("trust proxy", config.http.trustProxy);
|
|
67
60
|
app.use(
|
|
68
61
|
helmet({
|
|
69
62
|
contentSecurityPolicy: {
|
|
@@ -81,70 +74,63 @@ await (async (): Promise<void> => {
|
|
|
81
74
|
const metrics = createMetrics();
|
|
82
75
|
app.use(metrics.middleware);
|
|
83
76
|
|
|
84
|
-
// Step 3:
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
// `BuilderContext.lifecycle` and drained on `handle.dispose()`.
|
|
91
|
-
// bootstrapComponents carries only host-
|
|
92
|
-
// environment values (config + pathResolver per A2-γ §4 worked example);
|
|
93
|
-
// every other component flows through composition-root-local modules.
|
|
94
|
-
// `buildModules` is the single source of truth for the module list — it
|
|
95
|
-
// gates federation modules on `config.federations.<name>.enabled`, which
|
|
96
|
-
// the standalone scaffold defaults to false.
|
|
77
|
+
// Step 3: boot the auth pipeline. `buildModules` is the single source of
|
|
78
|
+
// truth for the module list and its order; `bootstrapComponents` carries
|
|
79
|
+
// only host-environment values, every other component comes from the
|
|
80
|
+
// modules. `environment` is the name the config was selected by: the Redis
|
|
81
|
+
// federation-token store's plaintext guard reads it as well as NODE_ENV,
|
|
82
|
+
// so `CONFIG_ENV=production` is production to the guard too.
|
|
97
83
|
//
|
|
98
|
-
// `
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
|
|
84
|
+
// Phase two: `createApp` parses the configuration as resolved once, with
|
|
85
|
+
// every loaded module's schema (`configPath.mts`), and names each section
|
|
86
|
+
// nothing loaded reads by the configuration's defaults. From here on the
|
|
87
|
+
// template reads the parsed configuration.
|
|
88
|
+
const modules = buildModules(switches, { environment: env });
|
|
103
89
|
const handle = await createApp({
|
|
104
|
-
modules
|
|
90
|
+
modules,
|
|
105
91
|
bootstrapComponents: {
|
|
106
|
-
config,
|
|
92
|
+
config: resolveForBoot(own, modules, switches),
|
|
93
|
+
configDefaults: configDefaultsFor(modules),
|
|
107
94
|
pathResolver: import.meta.resolve,
|
|
108
95
|
logger,
|
|
109
96
|
},
|
|
110
97
|
});
|
|
98
|
+
// The `http` module's settings: core's slot and the host's own.
|
|
99
|
+
const { httpSettings, httpHostSettings } = handle.components;
|
|
100
|
+
if (httpSettings === undefined || httpHostSettings === undefined) {
|
|
101
|
+
throw new Error("createApp booted without the http module's settings");
|
|
102
|
+
}
|
|
103
|
+
// `false` | `true` | a hop count | a list of IPs / CIDR ranges / named
|
|
104
|
+
// ranges: the shapes `trust proxy` understands, validated at boot so a
|
|
105
|
+
// typo'd range fails there rather than silently never matching. Prefer
|
|
106
|
+
// naming the proxy: `true` believes a forwarded client address from anyone
|
|
107
|
+
// who can reach this process, and every IP-keyed rate limit buckets on
|
|
108
|
+
// `req.ip`.
|
|
109
|
+
app.set("trust proxy", httpSettings.trustProxy);
|
|
111
110
|
|
|
112
|
-
// Step 4: the host
|
|
113
|
-
// auth router
|
|
114
|
-
//
|
|
115
|
-
// then the composed auth router, then the terminal error handler. See
|
|
116
|
-
// `routes.mts` for what each is and why the order is what it is.
|
|
111
|
+
// Step 4: the host routes (liveness, readiness, metrics), then the composed
|
|
112
|
+
// auth router, then the terminal error handler; `routes.mts` says why in
|
|
113
|
+
// that order.
|
|
117
114
|
mountRoutes(app, {
|
|
118
115
|
router: handle.router,
|
|
119
116
|
probes: handle.readinessProbes,
|
|
120
|
-
readinessTimeoutMs:
|
|
117
|
+
readinessTimeoutMs: httpHostSettings.readinessTimeoutMs,
|
|
121
118
|
metrics,
|
|
122
119
|
logger,
|
|
123
120
|
});
|
|
124
|
-
// Step 5: start the HTTP server.
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
const server = await listen(app, config.http.port, logger);
|
|
121
|
+
// Step 5: start the HTTP server. A port that cannot be bound fails boot
|
|
122
|
+
// with that error (`listen.mts`).
|
|
123
|
+
const server = await listen(app, httpHostSettings.port, logger);
|
|
128
124
|
|
|
129
|
-
// Step 6:
|
|
130
|
-
//
|
|
131
|
-
// (
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
// from the code you deploy. See `shutdown.mts` for the stated guarantees
|
|
136
|
-
// and for the deadline the previous implementation did not have. Size
|
|
137
|
-
// `drainTimeoutMs` below your orchestrator's kill grace period.
|
|
138
|
-
//
|
|
139
|
-
// #593 slice 7: with federation grants on, cleanup gets the longest refresh
|
|
140
|
-
// tail the configured budgets allow plus a margin — 45 s under the shipped
|
|
141
|
-
// ones — rather than inheriting the drain's ten: the dispose is what waits
|
|
142
|
-
// for a rotated credential's write. The compose files size
|
|
143
|
-
// `stop_grace_period` to cover the drain, this and an exit margin for the
|
|
144
|
-
// shipped budgets; a raised budget raises both.
|
|
125
|
+
// Step 6: graceful shutdown (`shutdown.mts`). Size `drainTimeoutMs` below
|
|
126
|
+
// your orchestrator's kill grace period. Cleanup gets at least the
|
|
127
|
+
// allowance the modules registered with their cleanups (45 s with
|
|
128
|
+
// federation grants on under the shipped budgets) rather than only the
|
|
129
|
+
// drain's ten; the template README, "Shutdown guarantees", sizes the grace
|
|
130
|
+
// period for it.
|
|
145
131
|
installGracefulShutdown(server, {
|
|
146
132
|
logger,
|
|
147
133
|
cleanup: () => handle.dispose(),
|
|
148
|
-
|
|
134
|
+
cleanupAllowanceMs: () => handle.cleanupAllowanceMs,
|
|
149
135
|
});
|
|
150
136
|
})();
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026 1o1 Co. Ltd.
|
|
3
|
+
*
|
|
4
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
* you may not use this file except in compliance with the License.
|
|
6
|
+
* You may obtain a copy of the License at
|
|
7
|
+
*
|
|
8
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
*
|
|
10
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
* See the License for the specific language governing permissions and
|
|
14
|
+
* limitations under the License.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The template's refusals before boot, as core's `BootError`: each under the
|
|
19
|
+
* reason boot raises for the same case, at the stage boot raises it in
|
|
20
|
+
* (`validateManifests`), so that an alert keyed on a boot error's reason sees
|
|
21
|
+
* every refusal to start. The template's checks stay its own; only how a
|
|
22
|
+
* refusal is reported is boot's.
|
|
23
|
+
*
|
|
24
|
+
* The details name a module as boot does. A key of the composition root's
|
|
25
|
+
* own — the `adapters` section, the `mfaMode` switch — is named after itself,
|
|
26
|
+
* as boot names core's own section "core": a module with a section under
|
|
27
|
+
* either name is refused (`resolveForBoot`), so the name points at the key.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
BootError,
|
|
32
|
+
type ConfigPathRelocatedDetails,
|
|
33
|
+
type ConfigValidationFailedDetails,
|
|
34
|
+
type EnvironmentVariableRenamedDetails,
|
|
35
|
+
} from "@o3co/auth-provider-core";
|
|
36
|
+
|
|
37
|
+
/** A Zod issue as `config-validation-failed` carries one: its path is the path in the configuration. */
|
|
38
|
+
export type ConfigIssue = ConfigValidationFailedDetails["issues"][number];
|
|
39
|
+
|
|
40
|
+
/** The stage boot raises the configuration's refusals in. */
|
|
41
|
+
const STAGE = "validateManifests";
|
|
42
|
+
|
|
43
|
+
/** An issue at `path` of the configuration that no schema raised: what is wrong there, in `message`. */
|
|
44
|
+
export function customIssue(path: readonly string[], message: string): ConfigIssue {
|
|
45
|
+
return { code: "custom", path: [...path], message } as ConfigIssue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** `issues`, each with `prefix`, the path its schema read at, in front of its own path. */
|
|
49
|
+
export function issuesAt(prefix: readonly string[], issues: readonly ConfigIssue[]): ConfigIssue[] {
|
|
50
|
+
return issues.map((issue) => ({ ...issue, path: [...prefix, ...issue.path] }) as ConfigIssue);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* `config-validation-failed`: `issues` at their paths, refused in the
|
|
55
|
+
* sections `modules` read, each at its `schemaPath`.
|
|
56
|
+
*/
|
|
57
|
+
export function configRefused(
|
|
58
|
+
message: string,
|
|
59
|
+
issues: readonly ConfigIssue[],
|
|
60
|
+
modules: ConfigValidationFailedDetails["modules"],
|
|
61
|
+
): BootError {
|
|
62
|
+
return new BootError({
|
|
63
|
+
message,
|
|
64
|
+
reason: "config-validation-failed",
|
|
65
|
+
stage: STAGE,
|
|
66
|
+
details: { reason: "config-validation-failed", issues: [...issues], modules: [...modules] },
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* `config-validation-failed` for one key, at `path`, of the section `module`
|
|
72
|
+
* reads at its own name: `message` is the refusal and the issue's message.
|
|
73
|
+
*/
|
|
74
|
+
export function keyRefused(message: string, module: string, path: readonly string[]): BootError {
|
|
75
|
+
return configRefused(message, [customIssue(path, message)], [{ module, schemaPath: module }]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** `config-path-relocated`: each key still set at a path it moved from. */
|
|
79
|
+
export function pathsRelocated(
|
|
80
|
+
message: string,
|
|
81
|
+
relocated: ConfigPathRelocatedDetails["relocated"],
|
|
82
|
+
): BootError {
|
|
83
|
+
return new BootError({
|
|
84
|
+
message,
|
|
85
|
+
reason: "config-path-relocated",
|
|
86
|
+
stage: STAGE,
|
|
87
|
+
details: { reason: "config-path-relocated", relocated: [...relocated] },
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `environment-variable-renamed`: each old variable name set, with no value. */
|
|
92
|
+
export function variablesRenamed(
|
|
93
|
+
message: string,
|
|
94
|
+
renamed: EnvironmentVariableRenamedDetails["renamed"],
|
|
95
|
+
): BootError {
|
|
96
|
+
return new BootError({
|
|
97
|
+
message,
|
|
98
|
+
reason: "environment-variable-renamed",
|
|
99
|
+
stage: STAGE,
|
|
100
|
+
details: { reason: "environment-variable-renamed", renamed: [...renamed] },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** `module-section-path-invalid`: `module`'s section is read at `at`, which `problem` says it may not be. */
|
|
105
|
+
export function sectionPathRefused(
|
|
106
|
+
message: string,
|
|
107
|
+
module: string,
|
|
108
|
+
at: string,
|
|
109
|
+
problem: string,
|
|
110
|
+
): BootError {
|
|
111
|
+
return new BootError({
|
|
112
|
+
message,
|
|
113
|
+
reason: "module-section-path-invalid",
|
|
114
|
+
stage: STAGE,
|
|
115
|
+
details: { reason: "module-section-path-invalid", module, at, problem },
|
|
116
|
+
});
|
|
117
|
+
}
|