@chidchanun/bcp 0.2.1 → 0.2.3
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 +255 -211
- package/docs/README.md +118 -79
- package/docs/api-manifest.json +5 -4
- package/docs/api-reference.md +33 -4
- package/docs/configuration.md +96 -1
- package/docs/database-migrations.md +79 -19
- package/docs/database.md +197 -28
- package/docs/docs-web-manifest.json +6 -3
- package/docs/environment-validation.md +224 -0
- package/docs/platform-manifest.json +20 -5
- package/docs/releases/0.2.2.md +118 -0
- package/docs/releases/0.2.3.md +77 -0
- package/package.json +1 -1
- package/packages/cli/src/args.ts +50 -9
- package/packages/cli/src/bootstrap.ts +22 -7
- package/packages/cli/src/configuration.ts +235 -0
- package/packages/cli/src/database-migrations.ts +91 -21
- package/packages/cli/src/index.ts +101 -1
- package/packages/client/src/config.ts +26 -0
- package/packages/client/src/database-mysql.ts +291 -0
- package/packages/client/src/database-postgresql.ts +355 -0
- package/packages/client/src/database-sqlite.ts +445 -0
- package/packages/client/src/database.mjs +709 -57
- package/packages/client/src/database.ts +347 -182
- package/packages/config/src/diagnostics.ts +226 -0
- package/packages/config/src/environment-loader.ts +101 -0
- package/packages/config/src/environment-schema.ts +677 -0
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
resolveBcpConfig,
|
|
5
|
+
type ResolveBcpConfigOverrides,
|
|
6
|
+
type ResolvedBcpConfig,
|
|
7
|
+
} from "./index.js";
|
|
8
|
+
import {
|
|
9
|
+
loadBcpEnvironmentSchema,
|
|
10
|
+
} from "./environment-loader.js";
|
|
11
|
+
import {
|
|
12
|
+
validateEnvironment,
|
|
13
|
+
type BcpEnvironmentValidationResult,
|
|
14
|
+
} from "./environment-schema.js";
|
|
15
|
+
|
|
16
|
+
export type BcpConfigurationDiagnosticSeverity =
|
|
17
|
+
| "error"
|
|
18
|
+
| "warning";
|
|
19
|
+
|
|
20
|
+
export interface BcpConfigurationDiagnostic {
|
|
21
|
+
severity: BcpConfigurationDiagnosticSeverity;
|
|
22
|
+
code: string;
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface BcpConfigurationDiagnosticsReport {
|
|
27
|
+
ok: boolean;
|
|
28
|
+
mode: string;
|
|
29
|
+
configFile: string | null;
|
|
30
|
+
environmentSchemaFile: string | null;
|
|
31
|
+
resolvedConfig: ResolvedBcpConfig;
|
|
32
|
+
environment: {
|
|
33
|
+
checked: number;
|
|
34
|
+
present: number;
|
|
35
|
+
defaults: number;
|
|
36
|
+
};
|
|
37
|
+
diagnostics: BcpConfigurationDiagnostic[];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface DiagnoseBcpConfigurationOptions {
|
|
41
|
+
rootDirectory: string;
|
|
42
|
+
mode: string;
|
|
43
|
+
environment?: NodeJS.ProcessEnv;
|
|
44
|
+
overrides?: ResolveBcpConfigOverrides;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function diagnoseBcpConfiguration(
|
|
48
|
+
options: DiagnoseBcpConfigurationOptions
|
|
49
|
+
): Promise<BcpConfigurationDiagnosticsReport> {
|
|
50
|
+
const environment =
|
|
51
|
+
options.environment ??
|
|
52
|
+
process.env;
|
|
53
|
+
const resolved =
|
|
54
|
+
await resolveBcpConfig(
|
|
55
|
+
options.rootDirectory,
|
|
56
|
+
options.overrides ?? {},
|
|
57
|
+
environment
|
|
58
|
+
);
|
|
59
|
+
const loadedSchema =
|
|
60
|
+
await loadBcpEnvironmentSchema(
|
|
61
|
+
options.rootDirectory
|
|
62
|
+
);
|
|
63
|
+
const environmentValidation =
|
|
64
|
+
validateEnvironment(
|
|
65
|
+
loadedSchema.schema,
|
|
66
|
+
environment
|
|
67
|
+
);
|
|
68
|
+
const diagnostics:
|
|
69
|
+
BcpConfigurationDiagnostic[] = [];
|
|
70
|
+
|
|
71
|
+
appendEnvironmentDiagnostics(
|
|
72
|
+
diagnostics,
|
|
73
|
+
environmentValidation
|
|
74
|
+
);
|
|
75
|
+
appendRuntimeDiagnostics(
|
|
76
|
+
diagnostics,
|
|
77
|
+
options.mode,
|
|
78
|
+
resolved.config,
|
|
79
|
+
environment
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
ok:
|
|
84
|
+
!diagnostics.some(
|
|
85
|
+
(diagnostic) =>
|
|
86
|
+
diagnostic.severity ===
|
|
87
|
+
"error"
|
|
88
|
+
),
|
|
89
|
+
mode:
|
|
90
|
+
options.mode,
|
|
91
|
+
configFile:
|
|
92
|
+
resolved.file
|
|
93
|
+
? path.basename(
|
|
94
|
+
resolved.file
|
|
95
|
+
)
|
|
96
|
+
: null,
|
|
97
|
+
environmentSchemaFile:
|
|
98
|
+
loadedSchema.file
|
|
99
|
+
? path.basename(
|
|
100
|
+
loadedSchema.file
|
|
101
|
+
)
|
|
102
|
+
: null,
|
|
103
|
+
resolvedConfig:
|
|
104
|
+
resolved.config,
|
|
105
|
+
environment: {
|
|
106
|
+
checked:
|
|
107
|
+
environmentValidation.checked,
|
|
108
|
+
present:
|
|
109
|
+
environmentValidation.present,
|
|
110
|
+
defaults:
|
|
111
|
+
environmentValidation.defaults,
|
|
112
|
+
},
|
|
113
|
+
diagnostics,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function assertConfigurationDiagnostics(
|
|
118
|
+
report: BcpConfigurationDiagnosticsReport
|
|
119
|
+
): void {
|
|
120
|
+
if (
|
|
121
|
+
report.ok
|
|
122
|
+
) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const errors =
|
|
127
|
+
report.diagnostics
|
|
128
|
+
.filter(
|
|
129
|
+
(diagnostic) =>
|
|
130
|
+
diagnostic.severity ===
|
|
131
|
+
"error"
|
|
132
|
+
)
|
|
133
|
+
.map(
|
|
134
|
+
(diagnostic) =>
|
|
135
|
+
diagnostic.message
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
throw new Error(
|
|
139
|
+
`BCP Configuration Error:\n${errors.map((message) => `- ${message}`).join("\n")}`
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function appendEnvironmentDiagnostics(
|
|
144
|
+
diagnostics: BcpConfigurationDiagnostic[],
|
|
145
|
+
validation: BcpEnvironmentValidationResult
|
|
146
|
+
): void {
|
|
147
|
+
for (
|
|
148
|
+
const issue
|
|
149
|
+
of validation.issues
|
|
150
|
+
) {
|
|
151
|
+
diagnostics.push({
|
|
152
|
+
severity:
|
|
153
|
+
issue.severity,
|
|
154
|
+
code:
|
|
155
|
+
`environment.${issue.code}`,
|
|
156
|
+
message:
|
|
157
|
+
issue.message,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function appendRuntimeDiagnostics(
|
|
163
|
+
diagnostics: BcpConfigurationDiagnostic[],
|
|
164
|
+
mode: string,
|
|
165
|
+
config: ResolvedBcpConfig,
|
|
166
|
+
environment: NodeJS.ProcessEnv
|
|
167
|
+
): void {
|
|
168
|
+
if (
|
|
169
|
+
mode !== "production"
|
|
170
|
+
) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (
|
|
175
|
+
config.build.sourceMaps
|
|
176
|
+
) {
|
|
177
|
+
diagnostics.push({
|
|
178
|
+
severity: "warning",
|
|
179
|
+
code: "production.source_maps",
|
|
180
|
+
message:
|
|
181
|
+
"Production source maps are enabled. Confirm that exposing source information is intentional.",
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (
|
|
186
|
+
config.security.poweredByHeader
|
|
187
|
+
) {
|
|
188
|
+
diagnostics.push({
|
|
189
|
+
severity: "warning",
|
|
190
|
+
code: "production.powered_by",
|
|
191
|
+
message:
|
|
192
|
+
"security.poweredByHeader is enabled in production.",
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (
|
|
197
|
+
config.security.contentSecurityPolicy ===
|
|
198
|
+
false
|
|
199
|
+
) {
|
|
200
|
+
diagnostics.push({
|
|
201
|
+
severity: "warning",
|
|
202
|
+
code: "production.csp_disabled",
|
|
203
|
+
message:
|
|
204
|
+
"Content-Security-Policy is disabled. Configure a policy when the application can support one.",
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const trustProxy =
|
|
209
|
+
environment.BCP_TRUST_PROXY
|
|
210
|
+
?.trim()
|
|
211
|
+
.toLowerCase();
|
|
212
|
+
|
|
213
|
+
if (
|
|
214
|
+
trustProxy === "true" ||
|
|
215
|
+
trustProxy === "1" ||
|
|
216
|
+
trustProxy === "yes" ||
|
|
217
|
+
trustProxy === "on"
|
|
218
|
+
) {
|
|
219
|
+
diagnostics.push({
|
|
220
|
+
severity: "warning",
|
|
221
|
+
code: "production.trust_proxy",
|
|
222
|
+
message:
|
|
223
|
+
"BCP_TRUST_PROXY is enabled. Ensure untrusted clients cannot bypass the trusted reverse proxy/load balancer.",
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
pathToFileURL,
|
|
5
|
+
} from "node:url";
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
assertEnvironmentSchema,
|
|
9
|
+
type BcpEnvironmentSchema,
|
|
10
|
+
} from "./environment-schema.js";
|
|
11
|
+
|
|
12
|
+
const ENVIRONMENT_SCHEMA_FILES = [
|
|
13
|
+
"bcp.environment.ts",
|
|
14
|
+
"bcp.environment.mts",
|
|
15
|
+
"bcp.environment.js",
|
|
16
|
+
"bcp.environment.mjs",
|
|
17
|
+
] as const;
|
|
18
|
+
|
|
19
|
+
export interface LoadedBcpEnvironmentSchema {
|
|
20
|
+
file: string | null;
|
|
21
|
+
schema: BcpEnvironmentSchema;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getEnvironmentSchemaFileNames(): string[] {
|
|
25
|
+
return [
|
|
26
|
+
...ENVIRONMENT_SCHEMA_FILES,
|
|
27
|
+
];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function loadBcpEnvironmentSchema(
|
|
31
|
+
rootDirectory: string
|
|
32
|
+
): Promise<LoadedBcpEnvironmentSchema> {
|
|
33
|
+
const matches =
|
|
34
|
+
ENVIRONMENT_SCHEMA_FILES
|
|
35
|
+
.map(
|
|
36
|
+
(fileName) =>
|
|
37
|
+
path.join(
|
|
38
|
+
rootDirectory,
|
|
39
|
+
fileName
|
|
40
|
+
)
|
|
41
|
+
)
|
|
42
|
+
.filter(
|
|
43
|
+
(filePath) =>
|
|
44
|
+
fs.existsSync(
|
|
45
|
+
filePath
|
|
46
|
+
)
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
if (
|
|
50
|
+
matches.length > 1
|
|
51
|
+
) {
|
|
52
|
+
throw new Error(
|
|
53
|
+
`BCP Framework: multiple environment schema files found: ${matches.map((file) => path.basename(file)).join(", ")}. Keep only one bcp.environment file.`
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
matches.length === 0
|
|
59
|
+
) {
|
|
60
|
+
return {
|
|
61
|
+
file: null,
|
|
62
|
+
schema: {},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const file =
|
|
67
|
+
matches[0];
|
|
68
|
+
const url =
|
|
69
|
+
pathToFileURL(
|
|
70
|
+
file
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
url.searchParams.set(
|
|
74
|
+
"bcp-environment",
|
|
75
|
+
`${Date.now()}-${Math.random()}`
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
const module =
|
|
79
|
+
await import(
|
|
80
|
+
url.href
|
|
81
|
+
);
|
|
82
|
+
const schema =
|
|
83
|
+
module.default;
|
|
84
|
+
|
|
85
|
+
if (
|
|
86
|
+
schema === undefined
|
|
87
|
+
) {
|
|
88
|
+
throw new Error(
|
|
89
|
+
`BCP Framework: ${path.basename(file)} must export a default environment schema.`
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
assertEnvironmentSchema(
|
|
94
|
+
schema
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
file,
|
|
99
|
+
schema,
|
|
100
|
+
};
|
|
101
|
+
}
|