@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.
@@ -0,0 +1,118 @@
1
+ # BCP Framework 0.2.2
2
+
3
+ > **Release state:** unreleased development target.
4
+
5
+ BCP Framework `0.2.2` is the **Configuration & Environment v2** milestone.
6
+
7
+ The release keeps the established BCP configuration precedence and adds an optional typed application environment schema, configuration diagnostics and a dedicated CLI validation workflow.
8
+
9
+ ## Highlights
10
+
11
+ ### Typed application environment schema
12
+
13
+ Projects can add:
14
+
15
+ ```text
16
+ bcp.environment.ts
17
+ ```
18
+
19
+ using the public `bcp/config` API:
20
+
21
+ ```ts
22
+ import {
23
+ defineEnvironment,
24
+ } from "bcp/config";
25
+
26
+ export default defineEnvironment({
27
+ DB_HOST: {
28
+ type: "string",
29
+ required: true,
30
+ },
31
+ DB_PORT: {
32
+ type: "number",
33
+ default: 3306,
34
+ },
35
+ SESSION_SECRET: {
36
+ type: "string",
37
+ required: true,
38
+ secret: true,
39
+ minLength: 32,
40
+ },
41
+ });
42
+ ```
43
+
44
+ Supported values include string, number, boolean and absolute HTTP(S) URL rules.
45
+
46
+ ### `bcp config check`
47
+
48
+ New CLI command:
49
+
50
+ ```bash
51
+ bcp config check
52
+ bcp config check --json
53
+ ```
54
+
55
+ It validates project configuration, environment schema values and production diagnostics without printing secret values.
56
+
57
+ ### Startup diagnostics
58
+
59
+ `bcp dev` and `bcp build` validate `bcp.environment.*` before starting the application/build.
60
+
61
+ Schema errors fail early. Production configuration warnings are reported without blocking the build.
62
+
63
+ ### Development schema watching
64
+
65
+ The dev supervisor now watches:
66
+
67
+ ```text
68
+ .env*
69
+ bcp.config.*
70
+ bcp.environment.*
71
+ ```
72
+
73
+ and restarts the worker when any of these configuration sources change.
74
+
75
+ ### Public configuration API
76
+
77
+ `bcp/config` now exposes additive environment/configuration helpers including:
78
+
79
+ ```text
80
+ defineEnvironment
81
+ validateEnvironment
82
+ loadBcpEnvironmentSchema
83
+ diagnoseBcpConfiguration
84
+ assertConfigurationDiagnostics
85
+ ```
86
+
87
+ ## Compatibility
88
+
89
+ `0.2.2` does not intentionally remove public entrypoints from `0.2.1`.
90
+
91
+ Existing projects without `bcp.environment.*` continue to work with the previous `.env` and `bcp.config.*` behavior.
92
+
93
+ The configuration precedence remains:
94
+
95
+ ```text
96
+ CLI > BCP_* environment > bcp.config.* > framework defaults
97
+ ```
98
+
99
+ ## Security
100
+
101
+ A schema variable marked `secret: true` must not use the `BCP_PUBLIC_` prefix. BCP treats that combination as a configuration error because public-prefixed values can be embedded into browser bundles.
102
+
103
+ `secret: true` is metadata for validation/tooling and does not encrypt environment values.
104
+
105
+ ## Validation
106
+
107
+ Before tagging/publishing:
108
+
109
+ ```bash
110
+ npm run typecheck
111
+ npm run test:unit
112
+ npm run test:integration
113
+ npm run test:package
114
+ npm run test:e2e
115
+ npm run rc:check
116
+ ```
117
+
118
+ The final release tag must point at the exact commit that passed the complete RC sequence.
@@ -0,0 +1,77 @@
1
+ # BCP Framework 0.2.3
2
+
3
+ ## Database Platform v2
4
+
5
+ BCP `0.2.3` turns the framework database layer into a provider-neutral SQL platform while preserving the existing `bcp/database` application API.
6
+
7
+ ### Database adapter contract
8
+
9
+ - `BcpDatabase` delegates connection, query, execute, transaction and disconnect behavior through `DatabaseAdapter`.
10
+ - Custom adapters can be injected directly or through lazy factories.
11
+ - Provider implementations are separated into MySQL, PostgreSQL and SQLite runtime modules.
12
+ - Provider drivers remain optional dependencies and are loaded lazily.
13
+
14
+ ### PostgreSQL
15
+
16
+ - Added built-in PostgreSQL support through `pg`.
17
+ - `DATABASE_URL=postgresql://...` selects PostgreSQL automatically.
18
+ - `DB_DRIVER=postgresql`, `postgres` and `pg` are supported provider names.
19
+ - Query parameters use PostgreSQL `$1`, `$2`, ... placeholders.
20
+ - Transactions use dedicated pool clients with `BEGIN`, `COMMIT`, `ROLLBACK` and guaranteed client release.
21
+
22
+ ### SQLite
23
+
24
+ - Added built-in SQLite support through `better-sqlite3`.
25
+ - Supports file paths, `:memory:`, `sqlite:` and `file:` database locations.
26
+ - Common `.sqlite`, `.sqlite3` and `.db` `DATABASE_URL` values infer SQLite automatically.
27
+ - File-backed databases create missing parent directories before opening the native database.
28
+ - Adapter operations are serialized so async transaction callbacks do not interleave unrelated operations on one SQLite connection.
29
+
30
+ ### Connection lifecycle
31
+
32
+ - Added explicit `database.connect()`.
33
+ - Added explicit `database.disconnect()`.
34
+ - Existing `database.close()` remains backward compatible and aliases the disconnect lifecycle.
35
+ - Disconnecting resets the facade so a later operation can initialize a fresh provider connection.
36
+ - Failed adapter initialization resets cleanly and can be retried.
37
+ - `close()` is safe after failed adapter initialization.
38
+
39
+ ### Migration consistency
40
+
41
+ The existing CLI remains unchanged:
42
+
43
+ ```bash
44
+ bcp db create create_users
45
+ bcp db migrate
46
+ bcp db status
47
+ bcp db rollback
48
+ ```
49
+
50
+ Migration bookkeeping now selects its internal SQL dialect from the active database provider:
51
+
52
+ - MySQL uses `AUTO_INCREMENT` and `?` placeholders.
53
+ - PostgreSQL uses `BIGSERIAL` and `$1`, `$2` placeholders.
54
+ - SQLite uses `INTEGER PRIMARY KEY AUTOINCREMENT` and `?` placeholders.
55
+
56
+ BCP only makes the internal `_bcp_migrations` bookkeeping provider-aware. Application migration SQL is intentionally not translated between SQL dialects.
57
+
58
+ ### Packaging and validation
59
+
60
+ - Added Database Platform v2 package smoke coverage.
61
+ - Packed framework validation checks provider modules, bundled optional-driver references, lifecycle APIs and migration dialect support.
62
+ - Added unit coverage for SQLite option resolution, provider inference, lifecycle reconnect behavior, failed adapter initialization and migration dialects.
63
+
64
+ ## Compatibility
65
+
66
+ `0.2.3` does not intentionally remove public application entrypoints from the `0.2.2` baseline.
67
+
68
+ Existing MySQL applications can continue using:
69
+
70
+ ```ts
71
+ import {
72
+ db,
73
+ createDatabase,
74
+ } from "bcp/database";
75
+ ```
76
+
77
+ without changing their application-facing database calls.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chidchanun/bcp",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "BCP Framework - a React full-stack framework with file-based routing, SSR, APIs, middleware, islands, caching and standalone production builds.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -9,6 +9,7 @@ export type CliCommand =
9
9
  | "update"
10
10
  | "db"
11
11
  | "generate"
12
+ | "config"
12
13
  | "doctor"
13
14
  | "inspect"
14
15
  | "help"
@@ -28,6 +29,10 @@ export type GenerateCliKind =
28
29
  | "migration"
29
30
  | "help";
30
31
 
32
+ export type ConfigCliAction =
33
+ | "check"
34
+ | "help";
35
+
31
36
  export interface CliOptions {
32
37
  command: CliCommand;
33
38
 
@@ -54,6 +59,8 @@ export interface CliOptions {
54
59
  generateKind?: GenerateCliKind;
55
60
 
56
61
  generateName?: string;
62
+
63
+ configAction?: ConfigCliAction;
57
64
  }
58
65
 
59
66
  export function parseCliArgs(
@@ -97,6 +104,9 @@ export function parseCliArgs(
97
104
  let generateName:
98
105
  string | undefined;
99
106
 
107
+ let configAction:
108
+ ConfigCliAction | undefined;
109
+
100
110
  let commandSet = false;
101
111
 
102
112
  for (
@@ -120,6 +130,11 @@ export function parseCliArgs(
120
130
  command === "generate"
121
131
  ) {
122
132
  generateKind = "help";
133
+ } else if (
134
+ commandSet &&
135
+ command === "config"
136
+ ) {
137
+ configAction = "help";
123
138
  } else {
124
139
  command = "help";
125
140
  }
@@ -268,13 +283,6 @@ export function parseCliArgs(
268
283
  );
269
284
  }
270
285
 
271
- /**
272
- * npm compatibility
273
- *
274
- * Some npm environments may consume an unknown script
275
- * option such as --port and leave only its numeric value
276
- * in argv. Accept a bare number for dev/start servers.
277
- */
278
286
  if (
279
287
  isPortShortcut(argument) &&
280
288
  (
@@ -374,6 +382,32 @@ export function parseCliArgs(
374
382
  );
375
383
  }
376
384
 
385
+ if (
386
+ commandSet &&
387
+ command === "config"
388
+ ) {
389
+ if (
390
+ configAction === undefined
391
+ ) {
392
+ if (
393
+ argument === "check" ||
394
+ argument === "help"
395
+ ) {
396
+ configAction =
397
+ argument;
398
+ continue;
399
+ }
400
+
401
+ throw new Error(
402
+ `Unknown config command: ${argument}`
403
+ );
404
+ }
405
+
406
+ throw new Error(
407
+ `Unexpected argument: ${argument}`
408
+ );
409
+ }
410
+
377
411
  if (commandSet) {
378
412
  throw new Error(
379
413
  `Unexpected argument: ${argument}`
@@ -388,6 +422,7 @@ export function parseCliArgs(
388
422
  argument === "update" ||
389
423
  argument === "db" ||
390
424
  argument === "generate" ||
425
+ argument === "config" ||
391
426
  argument === "doctor" ||
392
427
  argument === "inspect" ||
393
428
  argument === "help" ||
@@ -418,10 +453,11 @@ export function parseCliArgs(
418
453
  if (
419
454
  json &&
420
455
  command !== "doctor" &&
421
- command !== "inspect"
456
+ command !== "inspect" &&
457
+ command !== "config"
422
458
  ) {
423
459
  throw new Error(
424
- "Option --json is only valid with `bcp doctor` or `bcp inspect`."
460
+ "Option --json is only valid with `bcp doctor`, `bcp inspect`, or `bcp config`."
425
461
  );
426
462
  }
427
463
 
@@ -464,6 +500,11 @@ export function parseCliArgs(
464
500
  generateName,
465
501
  }
466
502
  : {}),
503
+ ...(command === "config"
504
+ ? {
505
+ configAction,
506
+ }
507
+ : {}),
467
508
  };
468
509
  }
469
510
 
@@ -14,6 +14,9 @@ import {
14
14
  getConfigFileNames,
15
15
  resolveBcpConfig,
16
16
  } from "../../config/src/index.js";
17
+ import {
18
+ getEnvironmentSchemaFileNames,
19
+ } from "../../config/src/environment-loader.js";
17
20
 
18
21
  import {
19
22
  getEnvironmentFileNames,
@@ -86,12 +89,6 @@ async function runCli(
86
89
  );
87
90
  }
88
91
 
89
- /*
90
- * A standalone start uses the configuration frozen at build time.
91
- * Runtime BCP_* environment variables and CLI server overrides stay
92
- * available to server.mjs, but source bcp.config.* must not replace
93
- * build-dependent settings after the artifact has been created.
94
- */
95
92
  if (
96
93
  cliOptions.command !==
97
94
  "start"
@@ -159,9 +156,20 @@ async function runDevSupervisor(
159
156
  )
160
157
  );
161
158
 
159
+ const environmentSchemaFiles =
160
+ getEnvironmentSchemaFileNames()
161
+ .map(
162
+ (fileName) =>
163
+ path.join(
164
+ rootDirectory,
165
+ fileName
166
+ )
167
+ );
168
+
162
169
  const watchedFiles = [
163
170
  ...envFiles,
164
171
  ...configFiles,
172
+ ...environmentSchemaFiles,
165
173
  ];
166
174
 
167
175
  const bootstrapFile =
@@ -360,7 +368,11 @@ async function runDevSupervisor(
360
368
  "bcp.config."
361
369
  )
362
370
  ? "Config"
363
- : "Env";
371
+ : fileName.startsWith(
372
+ "bcp.environment."
373
+ )
374
+ ? "Environment Schema"
375
+ : "Env";
364
376
 
365
377
  console.log("");
366
378
  console.log(
@@ -486,6 +498,9 @@ async function runDevSupervisor(
486
498
  console.log(
487
499
  `[BCP Config] Watching: ${getConfigFileNames().join(", ")}`
488
500
  );
501
+ console.log(
502
+ `[BCP Environment Schema] Watching: ${getEnvironmentSchemaFileNames().join(", ")}`
503
+ );
489
504
 
490
505
  startChild(
491
506
  false
@@ -0,0 +1,235 @@
1
+ import {
2
+ loadEnvironment,
3
+ } from "../../env/src/index.js";
4
+ import {
5
+ type ResolveBcpConfigOverrides,
6
+ } from "../../config/src/index.js";
7
+ import {
8
+ loadBcpEnvironmentSchema,
9
+ } from "../../config/src/environment-loader.js";
10
+ import {
11
+ applyEnvironmentDefaults,
12
+ } from "../../config/src/environment-schema.js";
13
+ import {
14
+ assertConfigurationDiagnostics,
15
+ diagnoseBcpConfiguration,
16
+ type BcpConfigurationDiagnosticsReport,
17
+ } from "../../config/src/diagnostics.js";
18
+
19
+ export interface RunConfigurationCheckOptions {
20
+ rootDirectory: string;
21
+ mode?: "development" | "production" | "test";
22
+ json?: boolean;
23
+ }
24
+
25
+ export async function runConfigurationCheck(
26
+ options: RunConfigurationCheckOptions
27
+ ): Promise<BcpConfigurationDiagnosticsReport> {
28
+ const mode =
29
+ options.mode ??
30
+ resolveCheckMode();
31
+ const loadedEnvironment =
32
+ loadEnvironment(
33
+ options.rootDirectory,
34
+ mode
35
+ );
36
+ const schema =
37
+ await loadBcpEnvironmentSchema(
38
+ options.rootDirectory
39
+ );
40
+ const appliedDefaults =
41
+ applyEnvironmentDefaults(
42
+ schema.schema,
43
+ process.env
44
+ );
45
+ const report =
46
+ await diagnoseBcpConfiguration({
47
+ rootDirectory:
48
+ options.rootDirectory,
49
+ mode,
50
+ environment:
51
+ process.env,
52
+ });
53
+
54
+ if (
55
+ options.json
56
+ ) {
57
+ console.log(
58
+ JSON.stringify(
59
+ {
60
+ ...report,
61
+ environmentFiles:
62
+ loadedEnvironment.files,
63
+ appliedDefaults,
64
+ },
65
+ null,
66
+ 2
67
+ )
68
+ );
69
+
70
+ if (
71
+ !report.ok
72
+ ) {
73
+ process.exitCode =
74
+ 1;
75
+
76
+ return report;
77
+ }
78
+ } else {
79
+ printConfigurationReport(
80
+ report,
81
+ loadedEnvironment.files,
82
+ appliedDefaults
83
+ );
84
+ }
85
+
86
+ assertConfigurationDiagnostics(
87
+ report
88
+ );
89
+
90
+ return report;
91
+ }
92
+
93
+ export async function runStartupConfigurationDiagnostics(
94
+ rootDirectory: string,
95
+ mode: "development" | "production",
96
+ overrides: ResolveBcpConfigOverrides = {}
97
+ ): Promise<BcpConfigurationDiagnosticsReport> {
98
+ const schema =
99
+ await loadBcpEnvironmentSchema(
100
+ rootDirectory
101
+ );
102
+ const appliedDefaults =
103
+ applyEnvironmentDefaults(
104
+ schema.schema,
105
+ process.env
106
+ );
107
+ const report =
108
+ await diagnoseBcpConfiguration({
109
+ rootDirectory,
110
+ mode,
111
+ environment:
112
+ process.env,
113
+ overrides,
114
+ });
115
+
116
+ if (
117
+ report.environmentSchemaFile
118
+ ) {
119
+ console.log(
120
+ `[BCP Config] Environment schema: ${report.environmentSchemaFile} (${report.environment.checked} variable(s), ${appliedDefaults} default(s) applied)`
121
+ );
122
+ }
123
+
124
+ for (
125
+ const diagnostic
126
+ of report.diagnostics
127
+ ) {
128
+ if (
129
+ diagnostic.severity ===
130
+ "warning"
131
+ ) {
132
+ console.warn(
133
+ `[BCP Config] Warning: ${diagnostic.message}`
134
+ );
135
+ }
136
+ }
137
+
138
+ assertConfigurationDiagnostics(
139
+ report
140
+ );
141
+
142
+ return report;
143
+ }
144
+
145
+ function printConfigurationReport(
146
+ report: BcpConfigurationDiagnosticsReport,
147
+ environmentFiles: string[],
148
+ appliedDefaults: number
149
+ ): void {
150
+ const errors =
151
+ report.diagnostics.filter(
152
+ (diagnostic) =>
153
+ diagnostic.severity ===
154
+ "error"
155
+ );
156
+ const warnings =
157
+ report.diagnostics.filter(
158
+ (diagnostic) =>
159
+ diagnostic.severity ===
160
+ "warning"
161
+ );
162
+
163
+ console.log("");
164
+ console.log(
165
+ `BCP Configuration Check (${report.mode})`
166
+ );
167
+ console.log("");
168
+ console.log(
169
+ ` Config: ${report.configFile ?? "defaults"}`
170
+ );
171
+ console.log(
172
+ ` Environment files: ${environmentFiles.length > 0 ? environmentFiles.join(", ") : "(none)"}`
173
+ );
174
+ console.log(
175
+ ` Environment schema: ${report.environmentSchemaFile ?? "(none)"}`
176
+ );
177
+ console.log(
178
+ ` Variables: ${report.environment.present}/${report.environment.checked} provided | ${appliedDefaults} default(s) applied`
179
+ );
180
+ console.log(
181
+ ` Server: ${report.resolvedConfig.server.hostname}:${report.resolvedConfig.server.port}`
182
+ );
183
+ console.log(
184
+ ` Build: minify=${report.resolvedConfig.build.minify} sourceMaps=${report.resolvedConfig.build.sourceMaps}`
185
+ );
186
+ console.log(
187
+ ` Diagnostics: ${errors.length} error(s), ${warnings.length} warning(s)`
188
+ );
189
+
190
+ if (
191
+ report.diagnostics.length >
192
+ 0
193
+ ) {
194
+ console.log("");
195
+
196
+ for (
197
+ const diagnostic
198
+ of report.diagnostics
199
+ ) {
200
+ console.log(
201
+ ` ${diagnostic.severity === "error" ? "✖" : "⚠"} ${diagnostic.message}`
202
+ );
203
+ }
204
+ }
205
+
206
+ if (
207
+ report.ok
208
+ ) {
209
+ console.log("");
210
+ console.log(
211
+ "[BCP Config] Configuration check passed."
212
+ );
213
+ }
214
+
215
+ console.log("");
216
+ }
217
+
218
+ function resolveCheckMode():
219
+ "development" | "production" | "test" {
220
+ if (
221
+ process.env.NODE_ENV ===
222
+ "production"
223
+ ) {
224
+ return "production";
225
+ }
226
+
227
+ if (
228
+ process.env.NODE_ENV ===
229
+ "test"
230
+ ) {
231
+ return "test";
232
+ }
233
+
234
+ return "development";
235
+ }