@chidchanun/bcp 0.1.14 → 0.1.16
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/docs/authentication.md +201 -0
- package/docs/database-migrations.md +105 -0
- package/docs/releases/0.1.16.md +53 -0
- package/package.json +6 -1
- package/packages/bundler/src/client-boundary.ts +13 -3
- package/packages/cli/src/args.ts +71 -1
- package/packages/cli/src/database-migrations.ts +708 -0
- package/packages/cli/src/index.ts +112 -1
- package/packages/client/src/auth.ts +14 -0
- package/packages/server/src/auth.ts +376 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Authentication
|
|
2
|
+
|
|
3
|
+
BCP Framework 0.1.16 adds a server-only authentication layer through `bcp/auth`.
|
|
4
|
+
It builds on the signed JWT cookie/session primitives from `bcp/server` and provides a higher-level API for application authentication.
|
|
5
|
+
|
|
6
|
+
## Environment
|
|
7
|
+
|
|
8
|
+
Set a session secret with at least 32 bytes:
|
|
9
|
+
|
|
10
|
+
```env
|
|
11
|
+
BCP_SESSION_SECRET=replace-with-a-long-random-secret-at-least-32-bytes
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The default cookie is `bcp_session`. It is HttpOnly, uses `SameSite=Lax`, has a 12-hour lifetime, and is Secure automatically when `NODE_ENV=production`.
|
|
15
|
+
|
|
16
|
+
## Basic usage
|
|
17
|
+
|
|
18
|
+
```ts
|
|
19
|
+
import {
|
|
20
|
+
auth,
|
|
21
|
+
login,
|
|
22
|
+
logout,
|
|
23
|
+
} from "bcp/auth";
|
|
24
|
+
|
|
25
|
+
export async function POST() {
|
|
26
|
+
await login({
|
|
27
|
+
id: 42,
|
|
28
|
+
email: "user@example.com",
|
|
29
|
+
role: "admin",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
return Response.json({
|
|
33
|
+
ok: true,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Read the current authenticated session:
|
|
39
|
+
|
|
40
|
+
```ts
|
|
41
|
+
import {
|
|
42
|
+
auth,
|
|
43
|
+
} from "bcp/auth";
|
|
44
|
+
|
|
45
|
+
const session =
|
|
46
|
+
await auth<{
|
|
47
|
+
id: number;
|
|
48
|
+
email: string;
|
|
49
|
+
role: string;
|
|
50
|
+
}>();
|
|
51
|
+
|
|
52
|
+
if (!session) {
|
|
53
|
+
// Not authenticated.
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
console.log(
|
|
57
|
+
session?.user.id
|
|
58
|
+
);
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
`getSession()` is an alias for `auth()` when that naming is clearer in application code.
|
|
62
|
+
|
|
63
|
+
## Typed auth factory
|
|
64
|
+
|
|
65
|
+
For application-wide types and cookie settings, create a typed auth instance:
|
|
66
|
+
|
|
67
|
+
```ts
|
|
68
|
+
import {
|
|
69
|
+
createAuth,
|
|
70
|
+
} from "bcp/auth";
|
|
71
|
+
|
|
72
|
+
interface AppUser {
|
|
73
|
+
id: number;
|
|
74
|
+
email: string;
|
|
75
|
+
role: "admin" | "user";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface AppSessionData {
|
|
79
|
+
tenantId: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export const appAuth =
|
|
83
|
+
createAuth<
|
|
84
|
+
AppUser,
|
|
85
|
+
AppSessionData
|
|
86
|
+
>({
|
|
87
|
+
cookieName:
|
|
88
|
+
"app_session",
|
|
89
|
+
expiresIn:
|
|
90
|
+
60 * 60 * 8,
|
|
91
|
+
issuer:
|
|
92
|
+
"my-app",
|
|
93
|
+
audience:
|
|
94
|
+
"web",
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Login with typed session data:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
await appAuth.login(
|
|
102
|
+
{
|
|
103
|
+
id: 42,
|
|
104
|
+
email: "user@example.com",
|
|
105
|
+
role: "admin",
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
data: {
|
|
109
|
+
tenantId:
|
|
110
|
+
"tenant-1",
|
|
111
|
+
},
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Read it later:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const session =
|
|
120
|
+
await appAuth.auth();
|
|
121
|
+
|
|
122
|
+
console.log(
|
|
123
|
+
session?.user.email
|
|
124
|
+
);
|
|
125
|
+
console.log(
|
|
126
|
+
session?.data?.tenantId
|
|
127
|
+
);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Logout
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
await logout();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or with a factory:
|
|
137
|
+
|
|
138
|
+
```ts
|
|
139
|
+
await appAuth.logout();
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Logout expires the configured authentication cookie.
|
|
143
|
+
|
|
144
|
+
## Session rotation
|
|
145
|
+
|
|
146
|
+
Each auth login receives a unique `sid` (session identifier). `rotateSession()` keeps the current user and session data but issues a new `sid`, JWT, expiry window, and cookie.
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const rotated =
|
|
150
|
+
await appAuth.rotateSession();
|
|
151
|
+
|
|
152
|
+
if (rotated) {
|
|
153
|
+
console.log(
|
|
154
|
+
rotated.sid
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Rotation returns `null` when no valid auth session exists.
|
|
160
|
+
|
|
161
|
+
A useful policy is to rotate after a security-sensitive event such as a privilege change or successful re-authentication.
|
|
162
|
+
|
|
163
|
+
## Session shape
|
|
164
|
+
|
|
165
|
+
An authenticated session contains the user, optional application session data, and signed JWT claims:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
{
|
|
169
|
+
sid: string;
|
|
170
|
+
user: AppUser;
|
|
171
|
+
data?: AppSessionData;
|
|
172
|
+
iat: number;
|
|
173
|
+
exp: number;
|
|
174
|
+
iss?: string;
|
|
175
|
+
aud?: string | string[];
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Security notes
|
|
180
|
+
|
|
181
|
+
- `bcp/auth` is server-only and must not be imported into pages or client islands.
|
|
182
|
+
- Never put passwords, password hashes, API secrets, access keys, or other sensitive credentials in the auth user/session payload. JWT cookie payloads are signed, not encrypted.
|
|
183
|
+
- Keep `BCP_SESSION_SECRET` out of source control and use a strong random value of at least 32 bytes.
|
|
184
|
+
- Authentication verifies identity/session state. Application authorization such as roles and permissions belongs in route guards or server actions.
|
|
185
|
+
- Use HTTPS in production so Secure cookies are transmitted only over encrypted connections.
|
|
186
|
+
|
|
187
|
+
## create-bcp-app
|
|
188
|
+
|
|
189
|
+
When `JWT Cookie` authentication is selected, generated applications use `createAuth()` internally and expose helpers from `lib/auth.ts`:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
import {
|
|
193
|
+
auth,
|
|
194
|
+
getSession,
|
|
195
|
+
login,
|
|
196
|
+
logout,
|
|
197
|
+
rotateSession,
|
|
198
|
+
} from "@/lib/auth";
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The generated `authenticateCredentials()` intentionally returns `null` until the application implements its own user lookup and password verification strategy.
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Database Migrations
|
|
2
|
+
|
|
3
|
+
BCP Framework `0.1.15` adds MySQL migration commands to the `bcp` CLI.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
Database migrations currently use the MySQL adapter from `bcp/database`.
|
|
8
|
+
|
|
9
|
+
Configure the same environment variables used by your application:
|
|
10
|
+
|
|
11
|
+
```env
|
|
12
|
+
DB_HOST=localhost
|
|
13
|
+
DB_PORT=3306
|
|
14
|
+
DB_USER=root
|
|
15
|
+
DB_PASSWORD=
|
|
16
|
+
DB_NAME=bcp_app
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
A project using migrations must have `mysql2` installed. Applications created with the MySQL preset already include it.
|
|
20
|
+
|
|
21
|
+
## Create a migration
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
bcp db create create_users
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
BCP creates an ordered TypeScript file in `migrations/`:
|
|
28
|
+
|
|
29
|
+
```text
|
|
30
|
+
migrations/
|
|
31
|
+
20260827040506_create_users.ts
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
A generated migration exports `up()` and `down()`:
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
import type {
|
|
38
|
+
TransactionDatabase,
|
|
39
|
+
} from "bcp/database";
|
|
40
|
+
|
|
41
|
+
export async function up(
|
|
42
|
+
db: TransactionDatabase
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
await db.execute(`
|
|
45
|
+
CREATE TABLE users (
|
|
46
|
+
id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
|
47
|
+
email VARCHAR(255) NOT NULL UNIQUE
|
|
48
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
|
|
49
|
+
`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export async function down(
|
|
53
|
+
db: TransactionDatabase
|
|
54
|
+
): Promise<void> {
|
|
55
|
+
await db.execute(
|
|
56
|
+
"DROP TABLE users"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Migration filenames use a UTC timestamp prefix so migrations have a stable execution order.
|
|
62
|
+
|
|
63
|
+
## Run pending migrations
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
bcp db migrate
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
BCP creates the internal `_bcp_migrations` table when needed, detects files that have not been applied, and runs all pending migrations in filename order.
|
|
70
|
+
|
|
71
|
+
All migrations applied by one `bcp db migrate` command share the same batch number. Each individual migration runs inside its own database transaction. The migration record is inserted in the same transaction as `up()`.
|
|
72
|
+
|
|
73
|
+
## Check status
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
bcp db status
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The command reports applied and pending migration files together with the batch number for applied migrations.
|
|
80
|
+
|
|
81
|
+
## Roll back
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
bcp db rollback
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Rollback reverses only the latest migration batch. Migrations in that batch run from newest to oldest, and each `down()` runs in a transaction together with removal of its migration record.
|
|
88
|
+
|
|
89
|
+
BCP refuses to roll back an applied migration when its migration file is missing.
|
|
90
|
+
|
|
91
|
+
## Project root
|
|
92
|
+
|
|
93
|
+
All database commands support the normal BCP project-root option:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
bcp db status --root ./apps/admin
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Environment files
|
|
100
|
+
|
|
101
|
+
Database commands load the BCP development environment files before connecting, so the same local database settings used by `bcp dev` can be reused by migration commands.
|
|
102
|
+
|
|
103
|
+
## Current scope
|
|
104
|
+
|
|
105
|
+
`0.1.15` migration execution supports MySQL. PostgreSQL, SQLite and MongoDB presets remain available to `create-bcp-app`, but framework-managed migrations for those adapters are planned for later releases.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# BCP Framework 0.1.16
|
|
2
|
+
|
|
3
|
+
## Authentication Core
|
|
4
|
+
|
|
5
|
+
BCP 0.1.16 introduces the server-only `bcp/auth` entrypoint.
|
|
6
|
+
|
|
7
|
+
### New APIs
|
|
8
|
+
|
|
9
|
+
- `auth()` reads and validates the current authentication session.
|
|
10
|
+
- `getSession()` is an auth-focused alias for `auth()`.
|
|
11
|
+
- `login(user, options)` creates an authenticated JWT cookie session.
|
|
12
|
+
- `logout(options)` expires the authentication cookie.
|
|
13
|
+
- `rotateSession(options)` preserves the authenticated user/session data while issuing a new session identifier and token.
|
|
14
|
+
- `createAuth<User, SessionData>(defaults)` creates a typed application auth instance with shared cookie/token configuration.
|
|
15
|
+
|
|
16
|
+
### Session identity
|
|
17
|
+
|
|
18
|
+
Every login now receives a random UUID `sid`. Session rotation always issues a new `sid`, so rotation produces a distinct signed token even if it happens within the same second.
|
|
19
|
+
|
|
20
|
+
### Security
|
|
21
|
+
|
|
22
|
+
- `bcp/auth` is exported as server-only in the package manifest.
|
|
23
|
+
- The client-boundary validator blocks `bcp/auth` from page/client graphs.
|
|
24
|
+
- The same validator now explicitly blocks direct `bcp/database` imports from client graphs as well.
|
|
25
|
+
- Existing JWT signing, expiration, issuer/audience validation, HttpOnly cookies, production Secure defaults, and minimum 32-byte session secrets remain provided by the underlying session runtime.
|
|
26
|
+
|
|
27
|
+
### create-bcp-app
|
|
28
|
+
|
|
29
|
+
The JWT Cookie preset now uses the framework auth core through `createAuth()` while keeping the existing starter helper names and API routes compatible.
|
|
30
|
+
|
|
31
|
+
Generated `lib/auth.ts` also exposes:
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
export const auth = frameworkAuth.auth;
|
|
35
|
+
export const getSession = frameworkAuth.getSession;
|
|
36
|
+
export const login = frameworkAuth.login;
|
|
37
|
+
export const logout = frameworkAuth.logout;
|
|
38
|
+
export const rotateSession = frameworkAuth.rotateSession;
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`authenticateCredentials()` still returns `null` until the application implements its own user lookup and password verification.
|
|
42
|
+
|
|
43
|
+
### Tests
|
|
44
|
+
|
|
45
|
+
0.1.16 adds regression coverage for:
|
|
46
|
+
|
|
47
|
+
- typed login/read/logout lifecycle;
|
|
48
|
+
- typed `createAuth()` defaults;
|
|
49
|
+
- session rotation and changing `sid` values;
|
|
50
|
+
- invalid auth payload rejection;
|
|
51
|
+
- published `bcp/auth` export wiring;
|
|
52
|
+
- client-boundary blocking for auth/database;
|
|
53
|
+
- JWT Cookie scaffold integration with the framework auth core.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chidchanun/bcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
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",
|
|
@@ -48,6 +48,11 @@
|
|
|
48
48
|
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
49
49
|
"default": "./packages/client/src/database.ts"
|
|
50
50
|
},
|
|
51
|
+
"./auth": {
|
|
52
|
+
"types": "./packages/client/src/auth.ts",
|
|
53
|
+
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
54
|
+
"default": "./packages/client/src/auth.ts"
|
|
55
|
+
},
|
|
51
56
|
"./server": {
|
|
52
57
|
"types": "./packages/client/src/server.ts",
|
|
53
58
|
"browser": "./packages/client/src/server-only.browser.mjs",
|
|
@@ -22,7 +22,13 @@ const MODULE_EXTENSIONS = [
|
|
|
22
22
|
".cjs",
|
|
23
23
|
] as const;
|
|
24
24
|
|
|
25
|
-
const SERVER_ONLY_IMPORTS =
|
|
25
|
+
const SERVER_ONLY_IMPORTS =
|
|
26
|
+
new Set([
|
|
27
|
+
"bcp/server",
|
|
28
|
+
"bcp/server-only",
|
|
29
|
+
"bcp/database",
|
|
30
|
+
"bcp/auth",
|
|
31
|
+
]);
|
|
26
32
|
|
|
27
33
|
export function validateClientBoundaries(
|
|
28
34
|
routes: Route[],
|
|
@@ -129,13 +135,17 @@ function visitClientModule(
|
|
|
129
135
|
source
|
|
130
136
|
)
|
|
131
137
|
) {
|
|
132
|
-
if (
|
|
138
|
+
if (
|
|
139
|
+
SERVER_ONLY_IMPORTS.has(
|
|
140
|
+
specifier
|
|
141
|
+
)
|
|
142
|
+
) {
|
|
133
143
|
throw new Error(
|
|
134
144
|
`BCP Framework: ${formatApplicationPath(
|
|
135
145
|
rootDirectory,
|
|
136
146
|
filePath
|
|
137
147
|
)} imports ${specifier} but is reachable from the client bundle for ${routeName}. Move the server dependency behind an API route.`
|
|
138
|
-
)
|
|
148
|
+
);
|
|
139
149
|
}
|
|
140
150
|
|
|
141
151
|
const dependency =
|
package/packages/cli/src/args.ts
CHANGED
|
@@ -7,9 +7,17 @@ export type CliCommand =
|
|
|
7
7
|
| "start"
|
|
8
8
|
| "routes"
|
|
9
9
|
| "update"
|
|
10
|
+
| "db"
|
|
10
11
|
| "help"
|
|
11
12
|
| "version";
|
|
12
13
|
|
|
14
|
+
export type DatabaseCliAction =
|
|
15
|
+
| "migrate"
|
|
16
|
+
| "status"
|
|
17
|
+
| "rollback"
|
|
18
|
+
| "create"
|
|
19
|
+
| "help";
|
|
20
|
+
|
|
13
21
|
export interface CliOptions {
|
|
14
22
|
command: CliCommand;
|
|
15
23
|
|
|
@@ -24,6 +32,10 @@ export interface CliOptions {
|
|
|
24
32
|
updateCheck?: boolean;
|
|
25
33
|
|
|
26
34
|
updateDryRun?: boolean;
|
|
35
|
+
|
|
36
|
+
dbAction?: DatabaseCliAction;
|
|
37
|
+
|
|
38
|
+
dbMigrationName?: string;
|
|
27
39
|
}
|
|
28
40
|
|
|
29
41
|
export function parseCliArgs(
|
|
@@ -49,6 +61,12 @@ export function parseCliArgs(
|
|
|
49
61
|
let updateDryRun =
|
|
50
62
|
false;
|
|
51
63
|
|
|
64
|
+
let dbAction:
|
|
65
|
+
DatabaseCliAction | undefined;
|
|
66
|
+
|
|
67
|
+
let dbMigrationName:
|
|
68
|
+
string | undefined;
|
|
69
|
+
|
|
52
70
|
let commandSet = false;
|
|
53
71
|
|
|
54
72
|
for (
|
|
@@ -62,7 +80,14 @@ export function parseCliArgs(
|
|
|
62
80
|
argument === "-h" ||
|
|
63
81
|
argument === "--help"
|
|
64
82
|
) {
|
|
65
|
-
|
|
83
|
+
if (
|
|
84
|
+
commandSet &&
|
|
85
|
+
command === "db"
|
|
86
|
+
) {
|
|
87
|
+
dbAction = "help";
|
|
88
|
+
} else {
|
|
89
|
+
command = "help";
|
|
90
|
+
}
|
|
66
91
|
continue;
|
|
67
92
|
}
|
|
68
93
|
|
|
@@ -221,6 +246,44 @@ export function parseCliArgs(
|
|
|
221
246
|
continue;
|
|
222
247
|
}
|
|
223
248
|
|
|
249
|
+
if (
|
|
250
|
+
commandSet &&
|
|
251
|
+
command === "db"
|
|
252
|
+
) {
|
|
253
|
+
if (
|
|
254
|
+
dbAction === undefined
|
|
255
|
+
) {
|
|
256
|
+
if (
|
|
257
|
+
argument === "migrate" ||
|
|
258
|
+
argument === "status" ||
|
|
259
|
+
argument === "rollback" ||
|
|
260
|
+
argument === "create" ||
|
|
261
|
+
argument === "help"
|
|
262
|
+
) {
|
|
263
|
+
dbAction =
|
|
264
|
+
argument;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
throw new Error(
|
|
269
|
+
`Unknown database command: ${argument}`
|
|
270
|
+
);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (
|
|
274
|
+
dbAction === "create" &&
|
|
275
|
+
dbMigrationName === undefined
|
|
276
|
+
) {
|
|
277
|
+
dbMigrationName =
|
|
278
|
+
argument;
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
throw new Error(
|
|
283
|
+
`Unexpected argument: ${argument}`
|
|
284
|
+
);
|
|
285
|
+
}
|
|
286
|
+
|
|
224
287
|
if (commandSet) {
|
|
225
288
|
throw new Error(
|
|
226
289
|
`Unexpected argument: ${argument}`
|
|
@@ -233,6 +296,7 @@ export function parseCliArgs(
|
|
|
233
296
|
argument === "start" ||
|
|
234
297
|
argument === "routes" ||
|
|
235
298
|
argument === "update" ||
|
|
299
|
+
argument === "db" ||
|
|
236
300
|
argument === "help" ||
|
|
237
301
|
argument === "version"
|
|
238
302
|
) {
|
|
@@ -266,6 +330,12 @@ export function parseCliArgs(
|
|
|
266
330
|
updateTarget,
|
|
267
331
|
updateCheck,
|
|
268
332
|
updateDryRun,
|
|
333
|
+
...(command === "db"
|
|
334
|
+
? {
|
|
335
|
+
dbAction,
|
|
336
|
+
dbMigrationName,
|
|
337
|
+
}
|
|
338
|
+
: {}),
|
|
269
339
|
};
|
|
270
340
|
}
|
|
271
341
|
|