rake-db 2.3.22 → 2.3.24
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/.env.local +2 -2
- package/.turbo/turbo-test.log +8 -8
- package/.turbo/turbo-test:ci.log +22 -0
- package/CHANGELOG.md +13 -0
- package/dist/index.js +52 -34
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +51 -33
- package/dist/index.mjs.map +1 -1
- package/jest-setup.ts +1 -1
- package/package.json +5 -3
- package/src/commands/createOrDrop.test.ts +50 -5
- package/src/commands/createOrDrop.ts +43 -9
- package/src/common.test.ts +11 -17
- package/src/common.ts +35 -31
- package/tsconfig.json +1 -1
package/jest-setup.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "rake-db",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.24",
|
|
4
4
|
"description": "Migrations tool for Postgresql DB",
|
|
5
5
|
"homepage": "https://orchid-orm.netlify.app/guide/migration-setup-and-overview.html",
|
|
6
6
|
"repository": {
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"author": "Roman Kushyn",
|
|
41
41
|
"license": "ISC",
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"enquirer": "^2.3.6",
|
|
44
43
|
"pluralize": "^8.0.0",
|
|
45
|
-
"pqb": "0.9.
|
|
44
|
+
"pqb": "0.9.13",
|
|
45
|
+
"prompts": "^2.4.2"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@swc/core": "^1.2.210",
|
|
@@ -51,6 +51,7 @@
|
|
|
51
51
|
"@types/jest": "^28.1.2",
|
|
52
52
|
"@types/node": "^18.0.1",
|
|
53
53
|
"@types/pluralize": "^0.0.29",
|
|
54
|
+
"@types/prompts": "^2.4.2",
|
|
54
55
|
"dotenv": "^16.0.3",
|
|
55
56
|
"jest": "^28.1.2",
|
|
56
57
|
"rimraf": "^3.0.2",
|
|
@@ -65,6 +66,7 @@
|
|
|
65
66
|
"scripts": {
|
|
66
67
|
"db": "ts-node -r tsconfig-paths/register app/dbScript.ts",
|
|
67
68
|
"test": "jest",
|
|
69
|
+
"test:ci": "jest --coverage --coverageReporters json-summary",
|
|
68
70
|
"build": "rimraf ./dist/ && rollup -c --rollup.config"
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -28,6 +28,11 @@ Adapter.prototype.query = queryMock;
|
|
|
28
28
|
const logMock = jest.fn();
|
|
29
29
|
console.log = logMock;
|
|
30
30
|
|
|
31
|
+
const config = {
|
|
32
|
+
...migrationConfigDefaults,
|
|
33
|
+
basePath: __dirname,
|
|
34
|
+
};
|
|
35
|
+
|
|
31
36
|
describe('createOrDrop', () => {
|
|
32
37
|
beforeEach(() => {
|
|
33
38
|
jest.clearAllMocks();
|
|
@@ -37,7 +42,7 @@ describe('createOrDrop', () => {
|
|
|
37
42
|
it('should create database when user is an admin', async () => {
|
|
38
43
|
queryMock.mockResolvedValueOnce(undefined);
|
|
39
44
|
|
|
40
|
-
await createDb(options,
|
|
45
|
+
await createDb(options, config);
|
|
41
46
|
|
|
42
47
|
expect(queryMock.mock.calls).toEqual([
|
|
43
48
|
[`CREATE DATABASE "dbname" OWNER "user"`],
|
|
@@ -53,7 +58,7 @@ describe('createOrDrop', () => {
|
|
|
53
58
|
|
|
54
59
|
await createDb(
|
|
55
60
|
[options, { ...options, database: 'dbname-test' }],
|
|
56
|
-
|
|
61
|
+
config,
|
|
57
62
|
);
|
|
58
63
|
|
|
59
64
|
expect(queryMock.mock.calls).toEqual([
|
|
@@ -70,7 +75,7 @@ describe('createOrDrop', () => {
|
|
|
70
75
|
it('should inform if database already exists', async () => {
|
|
71
76
|
queryMock.mockRejectedValueOnce({ code: '42P04' });
|
|
72
77
|
|
|
73
|
-
await createDb(options,
|
|
78
|
+
await createDb(options, config);
|
|
74
79
|
|
|
75
80
|
expect(queryMock.mock.calls).toEqual([
|
|
76
81
|
[`CREATE DATABASE "dbname" OWNER "user"`],
|
|
@@ -79,10 +84,27 @@ describe('createOrDrop', () => {
|
|
|
79
84
|
expect(createSchemaMigrations).toHaveBeenCalled();
|
|
80
85
|
});
|
|
81
86
|
|
|
87
|
+
it('should inform if ssl is required', async () => {
|
|
88
|
+
queryMock.mockRejectedValueOnce({
|
|
89
|
+
code: 'XX000',
|
|
90
|
+
message: 'sslmode=require',
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
await createDb(options, config);
|
|
94
|
+
|
|
95
|
+
expect(queryMock.mock.calls).toEqual([
|
|
96
|
+
[`CREATE DATABASE "dbname" OWNER "user"`],
|
|
97
|
+
]);
|
|
98
|
+
expect(logMock.mock.calls).toEqual([
|
|
99
|
+
['SSL is required: append ?ssl=true to the database url string'],
|
|
100
|
+
]);
|
|
101
|
+
expect(createSchemaMigrations).not.toHaveBeenCalled();
|
|
102
|
+
});
|
|
103
|
+
|
|
82
104
|
it('should ask and use admin credentials when cannot connect', async () => {
|
|
83
105
|
queryMock.mockRejectedValueOnce({ code: '42501' });
|
|
84
106
|
|
|
85
|
-
await createDb(options,
|
|
107
|
+
await createDb(options, config);
|
|
86
108
|
|
|
87
109
|
expect(setAdminCredentialsToOptions).toHaveBeenCalled();
|
|
88
110
|
expect(queryMock.mock.calls).toEqual([
|
|
@@ -90,6 +112,9 @@ describe('createOrDrop', () => {
|
|
|
90
112
|
[`CREATE DATABASE "dbname" OWNER "user"`],
|
|
91
113
|
]);
|
|
92
114
|
expect(logMock.mock.calls).toEqual([
|
|
115
|
+
[
|
|
116
|
+
`Permission denied to create database.\nDon't use this command for database service providers, only for a local db.`,
|
|
117
|
+
],
|
|
93
118
|
[`Database dbname successfully created`],
|
|
94
119
|
]);
|
|
95
120
|
expect(createSchemaMigrations).toHaveBeenCalled();
|
|
@@ -132,6 +157,23 @@ describe('createOrDrop', () => {
|
|
|
132
157
|
expect(logMock.mock.calls).toEqual([[`Database dbname does not exist`]]);
|
|
133
158
|
});
|
|
134
159
|
|
|
160
|
+
it('should inform if ssl is required', async () => {
|
|
161
|
+
queryMock.mockRejectedValueOnce({
|
|
162
|
+
code: 'XX000',
|
|
163
|
+
message: 'sslmode=require',
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
await createDb(options, config);
|
|
167
|
+
|
|
168
|
+
expect(queryMock.mock.calls).toEqual([
|
|
169
|
+
[`CREATE DATABASE "dbname" OWNER "user"`],
|
|
170
|
+
]);
|
|
171
|
+
expect(logMock.mock.calls).toEqual([
|
|
172
|
+
['SSL is required: append ?ssl=true to the database url string'],
|
|
173
|
+
]);
|
|
174
|
+
expect(createSchemaMigrations).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
|
|
135
177
|
it('should ask and use admin credentials when cannot connect', async () => {
|
|
136
178
|
queryMock.mockRejectedValueOnce({ code: '42501' });
|
|
137
179
|
|
|
@@ -143,6 +185,9 @@ describe('createOrDrop', () => {
|
|
|
143
185
|
[`DROP DATABASE "dbname"`],
|
|
144
186
|
]);
|
|
145
187
|
expect(logMock.mock.calls).toEqual([
|
|
188
|
+
[
|
|
189
|
+
`Permission denied to drop database.\nDon't use this command for database service providers, only for a local db.`,
|
|
190
|
+
],
|
|
146
191
|
[`Database dbname was successfully dropped`],
|
|
147
192
|
]);
|
|
148
193
|
});
|
|
@@ -152,7 +197,7 @@ describe('createOrDrop', () => {
|
|
|
152
197
|
it('should drop and create database', async () => {
|
|
153
198
|
queryMock.mockResolvedValue(undefined);
|
|
154
199
|
|
|
155
|
-
await resetDb(options,
|
|
200
|
+
await resetDb(options, config);
|
|
156
201
|
|
|
157
202
|
expect(queryMock.mock.calls).toEqual([
|
|
158
203
|
[`DROP DATABASE "dbname"`],
|
|
@@ -12,16 +12,31 @@ import { migrate } from './migrateOrRollback';
|
|
|
12
12
|
const execute = async (
|
|
13
13
|
options: AdapterOptions,
|
|
14
14
|
sql: string,
|
|
15
|
-
): Promise<
|
|
15
|
+
): Promise<
|
|
16
|
+
'ok' | 'already' | 'forbidden' | 'ssl required' | { error: unknown }
|
|
17
|
+
> => {
|
|
16
18
|
const db = new Adapter(options);
|
|
19
|
+
|
|
17
20
|
try {
|
|
18
21
|
await db.query(sql);
|
|
19
22
|
return 'ok';
|
|
20
23
|
} catch (error) {
|
|
21
24
|
const err = error as Record<string, unknown>;
|
|
25
|
+
|
|
26
|
+
if (
|
|
27
|
+
typeof err.message === 'string' &&
|
|
28
|
+
err.message.includes('sslmode=require')
|
|
29
|
+
) {
|
|
30
|
+
return 'ssl required';
|
|
31
|
+
}
|
|
32
|
+
|
|
22
33
|
if (err.code === '42P04' || err.code === '3D000') {
|
|
23
34
|
return 'already';
|
|
24
|
-
} else if (
|
|
35
|
+
} else if (
|
|
36
|
+
err.code === '42501' ||
|
|
37
|
+
(typeof err.message === 'string' &&
|
|
38
|
+
err.message.includes('password authentication failed'))
|
|
39
|
+
) {
|
|
25
40
|
return 'forbidden';
|
|
26
41
|
} else {
|
|
27
42
|
return { error };
|
|
@@ -39,7 +54,7 @@ const createOrDrop = async (
|
|
|
39
54
|
sql(params: { database: string; user: string }): string;
|
|
40
55
|
successMessage(params: { database: string }): string;
|
|
41
56
|
alreadyMessage(params: { database: string }): string;
|
|
42
|
-
|
|
57
|
+
create?: boolean;
|
|
43
58
|
},
|
|
44
59
|
) => {
|
|
45
60
|
const params = getDatabaseAndUserFromOptions(options);
|
|
@@ -52,19 +67,38 @@ const createOrDrop = async (
|
|
|
52
67
|
console.log(args.successMessage(params));
|
|
53
68
|
} else if (result === 'already') {
|
|
54
69
|
console.log(args.alreadyMessage(params));
|
|
70
|
+
} else if (result === 'ssl required') {
|
|
71
|
+
console.log('SSL is required: append ?ssl=true to the database url string');
|
|
72
|
+
return;
|
|
55
73
|
} else if (result === 'forbidden') {
|
|
56
|
-
|
|
74
|
+
let message = `Permission denied to ${
|
|
75
|
+
args.create ? 'create' : 'drop'
|
|
76
|
+
} database.`;
|
|
77
|
+
|
|
78
|
+
const host = adminOptions.databaseURL
|
|
79
|
+
? new URL(adminOptions.databaseURL).hostname
|
|
80
|
+
: adminOptions.host;
|
|
81
|
+
|
|
82
|
+
const isLocal = host === 'localhost';
|
|
83
|
+
if (!isLocal) {
|
|
84
|
+
message += `\nDon't use this command for database service providers, only for a local db.`;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log(message);
|
|
88
|
+
|
|
89
|
+
const updatedOptions = await setAdminCredentialsToOptions(
|
|
57
90
|
options,
|
|
58
|
-
|
|
59
|
-
config,
|
|
60
|
-
args,
|
|
91
|
+
args.create,
|
|
61
92
|
);
|
|
93
|
+
if (!updatedOptions) return;
|
|
94
|
+
|
|
95
|
+
await createOrDrop(options, updatedOptions, config, args);
|
|
62
96
|
return;
|
|
63
97
|
} else {
|
|
64
98
|
throw result.error;
|
|
65
99
|
}
|
|
66
100
|
|
|
67
|
-
if (!args.
|
|
101
|
+
if (!args.create) return;
|
|
68
102
|
|
|
69
103
|
const db = new Adapter(options);
|
|
70
104
|
await createSchemaMigrations(db, config);
|
|
@@ -86,7 +120,7 @@ export const createDb = async (
|
|
|
86
120
|
alreadyMessage({ database }) {
|
|
87
121
|
return `Database ${database} already exists`;
|
|
88
122
|
},
|
|
89
|
-
|
|
123
|
+
create: true,
|
|
90
124
|
});
|
|
91
125
|
}
|
|
92
126
|
};
|
package/src/common.test.ts
CHANGED
|
@@ -14,22 +14,13 @@ import {
|
|
|
14
14
|
sortDesc,
|
|
15
15
|
migrationConfigDefaults,
|
|
16
16
|
} from './common';
|
|
17
|
-
import
|
|
17
|
+
import prompts from 'prompts';
|
|
18
18
|
import { Adapter } from 'pqb';
|
|
19
19
|
import { readdir } from 'fs/promises';
|
|
20
20
|
import path from 'path';
|
|
21
|
+
import { asMock } from './test-utils';
|
|
21
22
|
|
|
22
|
-
jest.mock('
|
|
23
|
-
class Snippet {
|
|
24
|
-
constructor(public params: Record<string, unknown>) {}
|
|
25
|
-
run() {}
|
|
26
|
-
}
|
|
27
|
-
Snippet.prototype.run = jest.fn();
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
Snippet,
|
|
31
|
-
};
|
|
32
|
-
});
|
|
23
|
+
jest.mock('prompts', () => jest.fn());
|
|
33
24
|
|
|
34
25
|
jest.mock('fs/promises', () => ({
|
|
35
26
|
readdir: jest.fn(),
|
|
@@ -41,6 +32,7 @@ describe('common', () => {
|
|
|
41
32
|
describe('processRakeDbConfig', () => {
|
|
42
33
|
it('should return config with defaults', () => {
|
|
43
34
|
const result = processRakeDbConfig({
|
|
35
|
+
basePath: __dirname,
|
|
44
36
|
migrationsPath: 'custom-path',
|
|
45
37
|
});
|
|
46
38
|
|
|
@@ -125,11 +117,13 @@ describe('common', () => {
|
|
|
125
117
|
|
|
126
118
|
describe('setAdminCredentialsToOptions', () => {
|
|
127
119
|
beforeEach(() => {
|
|
128
|
-
(
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
120
|
+
asMock(prompts).mockResolvedValueOnce({
|
|
121
|
+
confirm: true,
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
asMock(prompts).mockResolvedValueOnce({
|
|
125
|
+
user: 'admin-user',
|
|
126
|
+
password: 'admin-password',
|
|
133
127
|
});
|
|
134
128
|
});
|
|
135
129
|
|
package/src/common.ts
CHANGED
|
@@ -7,10 +7,10 @@ import {
|
|
|
7
7
|
QueryLogOptions,
|
|
8
8
|
singleQuote,
|
|
9
9
|
} from 'pqb';
|
|
10
|
-
import Enquirer from 'enquirer';
|
|
11
10
|
import path from 'path';
|
|
12
11
|
import { readdir } from 'fs/promises';
|
|
13
12
|
import { RakeDbAst } from './ast';
|
|
13
|
+
import prompts from 'prompts';
|
|
14
14
|
|
|
15
15
|
type Db = DbResult<DefaultColumnTypes>;
|
|
16
16
|
|
|
@@ -152,40 +152,44 @@ export const setAdapterOptions = (
|
|
|
152
152
|
}
|
|
153
153
|
};
|
|
154
154
|
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
{
|
|
168
|
-
name: 'password',
|
|
169
|
-
},
|
|
170
|
-
],
|
|
171
|
-
values: {
|
|
172
|
-
user: 'postgres',
|
|
173
|
-
password: '',
|
|
155
|
+
export const setAdminCredentialsToOptions = async (
|
|
156
|
+
options: AdapterOptions,
|
|
157
|
+
create?: boolean,
|
|
158
|
+
): Promise<AdapterOptions | undefined> => {
|
|
159
|
+
const confirm = await prompts([
|
|
160
|
+
{
|
|
161
|
+
message: `Would you like to share admin credentials to ${
|
|
162
|
+
create ? 'create' : 'drop'
|
|
163
|
+
} a database`,
|
|
164
|
+
type: 'confirm',
|
|
165
|
+
name: 'confirm',
|
|
166
|
+
initial: true,
|
|
174
167
|
},
|
|
175
|
-
|
|
176
|
-
});
|
|
168
|
+
]);
|
|
177
169
|
|
|
178
|
-
|
|
179
|
-
|
|
170
|
+
if (!confirm.confirm) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
180
173
|
|
|
181
|
-
|
|
182
|
-
|
|
174
|
+
const values = await prompts([
|
|
175
|
+
{
|
|
176
|
+
message: 'Enter admin user:',
|
|
177
|
+
type: 'text',
|
|
178
|
+
name: 'user',
|
|
179
|
+
initial: 'postgres',
|
|
180
|
+
min: 1,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
message: 'Enter admin password:',
|
|
184
|
+
type: 'password',
|
|
185
|
+
name: 'password',
|
|
186
|
+
},
|
|
187
|
+
]);
|
|
183
188
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
return setAdapterOptions(options, values);
|
|
189
|
+
return setAdapterOptions(options, {
|
|
190
|
+
...values,
|
|
191
|
+
password: values.password || undefined,
|
|
192
|
+
});
|
|
189
193
|
};
|
|
190
194
|
|
|
191
195
|
export const createSchemaMigrations = async (
|