rake-db 2.3.30 → 2.3.31

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.
Files changed (52) hide show
  1. package/package.json +11 -21
  2. package/.env +0 -1
  3. package/.env.local +0 -2
  4. package/.turbo/turbo-check.log +0 -23
  5. package/.turbo/turbo-test.log +0 -22
  6. package/.turbo/turbo-test:ci.log +0 -22
  7. package/CHANGELOG.md +0 -395
  8. package/app/dbScript.ts +0 -33
  9. package/app/migrations/20221017181504_createUser.ts +0 -14
  10. package/app/migrations/20221017200111_createProfile.ts +0 -10
  11. package/app/migrations/20221017200252_createChat.ts +0 -9
  12. package/app/migrations/20221017200326_createChatUser.ts +0 -10
  13. package/app/migrations/20221017200900_createMessage.ts +0 -12
  14. package/app/migrations/20221017201235_createGeoSchema.ts +0 -5
  15. package/app/migrations/20221017210011_createCountry.ts +0 -8
  16. package/app/migrations/20221017210133_createCity.ts +0 -9
  17. package/app/migrations/20221105202843_createUniqueTable.ts +0 -12
  18. package/jest-setup.ts +0 -3
  19. package/rollup.config.js +0 -3
  20. package/src/ast.ts +0 -130
  21. package/src/commands/createOrDrop.test.ts +0 -214
  22. package/src/commands/createOrDrop.ts +0 -151
  23. package/src/commands/generate.test.ts +0 -136
  24. package/src/commands/generate.ts +0 -93
  25. package/src/commands/migrateOrRollback.test.ts +0 -267
  26. package/src/commands/migrateOrRollback.ts +0 -190
  27. package/src/common.test.ts +0 -295
  28. package/src/common.ts +0 -353
  29. package/src/errors.ts +0 -3
  30. package/src/index.ts +0 -8
  31. package/src/migration/change.test.ts +0 -16
  32. package/src/migration/change.ts +0 -15
  33. package/src/migration/changeTable.test.ts +0 -897
  34. package/src/migration/changeTable.ts +0 -566
  35. package/src/migration/createTable.test.ts +0 -384
  36. package/src/migration/createTable.ts +0 -193
  37. package/src/migration/migration.test.ts +0 -430
  38. package/src/migration/migration.ts +0 -518
  39. package/src/migration/migrationUtils.ts +0 -307
  40. package/src/migration/tableMethods.ts +0 -8
  41. package/src/pull/astToMigration.test.ts +0 -275
  42. package/src/pull/astToMigration.ts +0 -173
  43. package/src/pull/dbStructure.test.ts +0 -180
  44. package/src/pull/dbStructure.ts +0 -413
  45. package/src/pull/pull.test.ts +0 -115
  46. package/src/pull/pull.ts +0 -22
  47. package/src/pull/structureToAst.test.ts +0 -841
  48. package/src/pull/structureToAst.ts +0 -372
  49. package/src/rakeDb.test.ts +0 -131
  50. package/src/rakeDb.ts +0 -84
  51. package/src/test-utils.ts +0 -64
  52. package/tsconfig.json +0 -12
@@ -1,372 +0,0 @@
1
- import { DbStructure } from './dbStructure';
2
- import { RakeDbAst } from '../ast';
3
- import {
4
- columnsByType,
5
- ColumnsShape,
6
- ForeignKeyOptions,
7
- instantiateColumn,
8
- singleQuote,
9
- TableData,
10
- } from 'pqb';
11
- import { getForeignKeyName, getIndexName } from '../migration/migrationUtils';
12
-
13
- const matchMap = {
14
- s: undefined,
15
- f: 'FULL',
16
- p: 'PARTIAL',
17
- };
18
-
19
- const fkeyActionMap = {
20
- a: undefined, // default
21
- r: 'RESTRICT',
22
- c: 'CASCADE',
23
- n: 'SET NULL',
24
- d: 'SET DEFAULT',
25
- };
26
-
27
- type Data = {
28
- schemas: string[];
29
- tables: DbStructure.Table[];
30
- columns: DbStructure.Column[];
31
- primaryKeys: DbStructure.PrimaryKey[];
32
- indexes: DbStructure.Index[];
33
- foreignKeys: DbStructure.ForeignKey[];
34
- extensions: DbStructure.Extension[];
35
- enums: DbStructure.Enum[];
36
- };
37
-
38
- type PendingTables = Record<
39
- string,
40
- { table: DbStructure.Table; dependsOn: Set<string> }
41
- >;
42
-
43
- export const structureToAst = async (db: DbStructure): Promise<RakeDbAst[]> => {
44
- const ast: RakeDbAst[] = [];
45
-
46
- const data = await getData(db);
47
-
48
- for (const name of data.schemas) {
49
- if (name === 'public') continue;
50
-
51
- ast.push({
52
- type: 'schema',
53
- action: 'create',
54
- name,
55
- });
56
- }
57
-
58
- const pendingTables: PendingTables = {};
59
- for (const table of data.tables) {
60
- const key = `${table.schemaName}.${table.name}`;
61
- const dependsOn = new Set<string>();
62
-
63
- for (const fk of data.foreignKeys) {
64
- if (fk.schemaName !== table.schemaName || fk.tableName !== table.name)
65
- continue;
66
-
67
- const otherKey = `${fk.foreignTableSchemaName}.${fk.foreignTableName}`;
68
- if (otherKey !== key) {
69
- dependsOn.add(otherKey);
70
- }
71
- }
72
-
73
- pendingTables[key] = { table, dependsOn };
74
- }
75
-
76
- for (const key in pendingTables) {
77
- const { table, dependsOn } = pendingTables[key];
78
- if (!dependsOn.size) {
79
- pushTableAst(ast, data, table, pendingTables);
80
- }
81
- }
82
-
83
- const outerFKeys: [DbStructure.ForeignKey, DbStructure.Table][] = [];
84
-
85
- for (const it of data.extensions) {
86
- ast.push({
87
- type: 'extension',
88
- action: 'create',
89
- name: it.name,
90
- schema: it.schemaName === 'public' ? undefined : it.schemaName,
91
- version: it.version,
92
- });
93
- }
94
-
95
- for (const it of data.enums) {
96
- ast.push({
97
- type: 'enum',
98
- action: 'create',
99
- name: it.name,
100
- schema: it.schemaName === 'public' ? undefined : it.schemaName,
101
- values: it.values,
102
- });
103
- }
104
-
105
- for (const key in pendingTables) {
106
- const innerFKeys: DbStructure.ForeignKey[] = [];
107
- const { table } = pendingTables[key];
108
-
109
- for (const fkey of data.foreignKeys) {
110
- if (fkey.schemaName !== table.schemaName || fkey.tableName !== table.name)
111
- continue;
112
-
113
- const otherKey = `${fkey.foreignTableSchemaName}.${fkey.foreignTableName}`;
114
- if (!pendingTables[otherKey] || otherKey === key) {
115
- innerFKeys.push(fkey);
116
- } else {
117
- outerFKeys.push([fkey, table]);
118
- }
119
- }
120
-
121
- pushTableAst(ast, data, table, pendingTables, innerFKeys);
122
- }
123
-
124
- for (const [fkey, table] of outerFKeys) {
125
- ast.push({
126
- ...foreignKeyToAst(fkey),
127
- type: 'foreignKey',
128
- action: 'create',
129
- tableSchema: table.schemaName === 'public' ? undefined : table.schemaName,
130
- tableName: fkey.tableName,
131
- });
132
- }
133
-
134
- return ast;
135
- };
136
-
137
- const getData = async (db: DbStructure): Promise<Data> => {
138
- const [
139
- schemas,
140
- tables,
141
- columns,
142
- primaryKeys,
143
- indexes,
144
- foreignKeys,
145
- extensions,
146
- enums,
147
- ] = await Promise.all([
148
- db.getSchemas(),
149
- db.getTables(),
150
- db.getColumns(),
151
- db.getPrimaryKeys(),
152
- db.getIndexes(),
153
- db.getForeignKeys(),
154
- db.getExtensions(),
155
- db.getEnums(),
156
- ]);
157
-
158
- return {
159
- schemas,
160
- tables,
161
- columns,
162
- primaryKeys,
163
- indexes,
164
- foreignKeys,
165
- extensions,
166
- enums,
167
- };
168
- };
169
-
170
- const makeBelongsToTable =
171
- (schema: string | undefined, table: string) =>
172
- (item: { schemaName: string; tableName: string }) =>
173
- item.schemaName === schema && item.tableName === table;
174
-
175
- const getIsSerial = (item: DbStructure.Column) => {
176
- if (item.type === 'int2' || item.type === 'int4' || item.type === 'int8') {
177
- const { default: def, schemaName, tableName, name } = item;
178
- const seq = `${tableName}_${name}_seq`;
179
- if (
180
- def &&
181
- (def === `nextval(${singleQuote(`${seq}`)}::regclass)` ||
182
- def === `nextval(${singleQuote(`"${seq}"`)}::regclass)` ||
183
- def === `nextval(${singleQuote(`${schemaName}.${seq}`)}::regclass)` ||
184
- def === `nextval(${singleQuote(`"${schemaName}".${seq}`)}::regclass)` ||
185
- def === `nextval(${singleQuote(`${schemaName}."${seq}"`)}::regclass)` ||
186
- def === `nextval(${singleQuote(`"${schemaName}"."${seq}"`)}::regclass)`)
187
- ) {
188
- return true;
189
- }
190
- }
191
-
192
- return false;
193
- };
194
-
195
- const getColumnType = (item: DbStructure.Column, isSerial: boolean) => {
196
- if (isSerial) {
197
- return item.type === 'int2'
198
- ? 'smallserial'
199
- : item.type === 'int4'
200
- ? 'serial'
201
- : 'bigserial';
202
- }
203
-
204
- return item.type;
205
- };
206
-
207
- const pushTableAst = (
208
- ast: RakeDbAst[],
209
- data: Data,
210
- table: DbStructure.Table,
211
- pendingTables: PendingTables,
212
- innerFKeys = data.foreignKeys,
213
- ) => {
214
- const { schemaName, name } = table;
215
-
216
- const key = `${schemaName}.${table.name}`;
217
- delete pendingTables[key];
218
-
219
- if (name === 'schemaMigrations') return;
220
-
221
- const belongsToTable = makeBelongsToTable(schemaName, name);
222
-
223
- const columns = data.columns.filter(belongsToTable);
224
- const primaryKey = data.primaryKeys.find(belongsToTable);
225
- const tableIndexes = data.indexes.filter(belongsToTable);
226
- const tableForeignKeys = innerFKeys.filter(belongsToTable);
227
-
228
- const shape: ColumnsShape = {};
229
- for (let item of columns) {
230
- const isSerial = getIsSerial(item);
231
- if (isSerial) {
232
- item = { ...item, default: undefined };
233
- }
234
-
235
- const klass = columnsByType[getColumnType(item, isSerial)];
236
- if (!klass) {
237
- throw new Error(`Column type \`${item.type}\` is not supported`);
238
- }
239
-
240
- let column = instantiateColumn(klass, item);
241
-
242
- if (
243
- primaryKey?.columnNames.length === 1 &&
244
- primaryKey?.columnNames[0] === item.name
245
- ) {
246
- column = column.primaryKey();
247
- }
248
-
249
- const indexes = tableIndexes.filter(
250
- (it) =>
251
- it.columns.length === 1 &&
252
- 'column' in it.columns[0] &&
253
- it.columns[0].column === item.name,
254
- );
255
- for (const index of indexes) {
256
- const options = index.columns[0];
257
- column = column.index({
258
- collate: options.collate,
259
- opclass: options.opclass,
260
- order: options.order,
261
- name:
262
- index.name !== getIndexName(name, index.columns)
263
- ? index.name
264
- : undefined,
265
- using: index.using === 'btree' ? undefined : index.using,
266
- unique: index.isUnique,
267
- include: index.include,
268
- with: index.with,
269
- tablespace: index.tablespace,
270
- where: index.where,
271
- });
272
- }
273
-
274
- const foreignKeys = tableForeignKeys.filter(
275
- (it) => it.columnNames.length === 1 && it.columnNames[0] === item.name,
276
- );
277
- for (const foreignKey of foreignKeys) {
278
- column = column.foreignKey(
279
- foreignKey.foreignTableName,
280
- foreignKey.foreignColumnNames[0],
281
- {
282
- name:
283
- foreignKey.name &&
284
- foreignKey.name !== getForeignKeyName(name, foreignKey.columnNames)
285
- ? foreignKey.name
286
- : undefined,
287
- match: matchMap[foreignKey.match],
288
- onUpdate: fkeyActionMap[foreignKey.onUpdate],
289
- onDelete: fkeyActionMap[foreignKey.onDelete],
290
- } as ForeignKeyOptions,
291
- );
292
- }
293
-
294
- shape[item.name] = column;
295
- }
296
-
297
- ast.push({
298
- type: 'table',
299
- action: 'create',
300
- schema: schemaName === 'public' ? undefined : schemaName,
301
- comment: table.comment,
302
- name: name,
303
- shape,
304
- noPrimaryKey: primaryKey ? 'error' : 'ignore',
305
- primaryKey:
306
- primaryKey && primaryKey.columnNames.length > 1
307
- ? {
308
- columns: primaryKey.columnNames,
309
- options:
310
- primaryKey.name === `${name}_pkey`
311
- ? undefined
312
- : { name: primaryKey.name },
313
- }
314
- : undefined,
315
- indexes: tableIndexes
316
- .filter(
317
- (index) =>
318
- index.columns.length > 1 ||
319
- index.columns.some((it) => 'expression' in it),
320
- )
321
- .map((index) => ({
322
- columns: index.columns.map((it) => ({
323
- ...('column' in it
324
- ? { column: it.column }
325
- : { expression: it.expression }),
326
- collate: it.collate,
327
- opclass: it.opclass,
328
- order: it.order,
329
- })),
330
- options: {
331
- name:
332
- index.name !== getIndexName(name, index.columns)
333
- ? index.name
334
- : undefined,
335
- using: index.using === 'btree' ? undefined : index.using,
336
- unique: index.isUnique,
337
- include: index.include,
338
- with: index.with,
339
- tablespace: index.tablespace,
340
- where: index.where,
341
- },
342
- })),
343
- foreignKeys: tableForeignKeys
344
- .filter((it) => it.columnNames.length > 1)
345
- .map(foreignKeyToAst),
346
- });
347
-
348
- for (const otherKey in pendingTables) {
349
- const item = pendingTables[otherKey];
350
- if (item.dependsOn.delete(key) && item.dependsOn.size === 0) {
351
- pushTableAst(ast, data, item.table, pendingTables);
352
- }
353
- }
354
- };
355
-
356
- const foreignKeyToAst = (
357
- fkey: DbStructure.ForeignKey,
358
- ): TableData.ForeignKey => ({
359
- columns: fkey.columnNames,
360
- fnOrTable: fkey.foreignTableName,
361
- foreignColumns: fkey.foreignColumnNames,
362
- options: {
363
- name:
364
- fkey.name &&
365
- fkey.name !== getForeignKeyName(fkey.tableName, fkey.columnNames)
366
- ? fkey.name
367
- : undefined,
368
- match: matchMap[fkey.match],
369
- onUpdate: fkeyActionMap[fkey.onUpdate],
370
- onDelete: fkeyActionMap[fkey.onDelete],
371
- } as ForeignKeyOptions,
372
- });
@@ -1,131 +0,0 @@
1
- import { rakeDb } from './rakeDb';
2
- import { createDb, dropDb, resetDb } from './commands/createOrDrop';
3
- import { migrate, rollback } from './commands/migrateOrRollback';
4
- import { generate } from './commands/generate';
5
- import { pullDbStructure } from './pull/pull';
6
- import { RakeDbError } from './errors';
7
-
8
- jest.mock('./common', () => ({
9
- processRakeDbConfig: (config: unknown) => config,
10
- }));
11
-
12
- jest.mock('./commands/createOrDrop', () => ({
13
- createDb: jest.fn(),
14
- dropDb: jest.fn(),
15
- resetDb: jest.fn(),
16
- }));
17
-
18
- jest.mock('./commands/migrateOrRollback', () => ({
19
- migrate: jest.fn(),
20
- rollback: jest.fn(),
21
- }));
22
-
23
- jest.mock('./commands/generate', () => ({
24
- generate: jest.fn(),
25
- }));
26
-
27
- jest.mock('./pull/pull', () => ({
28
- pullDbStructure: jest.fn(),
29
- }));
30
-
31
- const options = [
32
- {
33
- databaseURL: 'one',
34
- },
35
- {
36
- databaseURL: 'two',
37
- },
38
- ];
39
-
40
- const config = {
41
- migrationsPath: 'migrations',
42
- commands: {},
43
- };
44
-
45
- describe('rakeDb', () => {
46
- test('create', async () => {
47
- await rakeDb(options, config, ['create']);
48
-
49
- expect(createDb).toBeCalledWith(options, config);
50
- });
51
-
52
- test('drop', async () => {
53
- await rakeDb(options, config, ['drop']);
54
-
55
- expect(dropDb).toBeCalledWith(options);
56
- });
57
-
58
- test('reset', async () => {
59
- await rakeDb(options, config, ['reset']);
60
-
61
- expect(resetDb).toBeCalledWith(options, config);
62
- });
63
-
64
- test('migrate', async () => {
65
- await rakeDb(options, config, ['migrate', 'arg']);
66
-
67
- expect(migrate).toBeCalledWith(options, config, ['arg']);
68
- });
69
-
70
- test('rollback', async () => {
71
- await rakeDb(options, config, ['rollback', 'arg']);
72
-
73
- expect(rollback).toBeCalledWith(options, config, ['arg']);
74
- });
75
-
76
- test('generate', async () => {
77
- await rakeDb(options, config, ['g', 'arg']);
78
-
79
- expect(generate).toBeCalledWith(config, ['arg']);
80
-
81
- jest.clearAllMocks();
82
-
83
- await rakeDb(options, config, ['generate', 'arg']);
84
-
85
- expect(generate).toBeCalledWith(config, ['arg']);
86
- });
87
-
88
- test('pull', async () => {
89
- await rakeDb(options, config, ['pull']);
90
-
91
- expect(pullDbStructure).toBeCalledWith(options[0], config);
92
- });
93
-
94
- test('custom command', async () => {
95
- const custom = jest.fn();
96
-
97
- const conf = { ...config, commands: { custom } };
98
-
99
- await rakeDb(options, conf, ['custom', 'arg']);
100
-
101
- expect(custom).toBeCalledWith(options, conf, ['arg']);
102
- });
103
-
104
- test('other', async () => {
105
- const log = jest.fn();
106
- console.log = log;
107
-
108
- await rakeDb(options, config, ['other']);
109
-
110
- expect(log).toBeCalled();
111
- });
112
-
113
- it('should log error and exit process with 1 when RakeDbError thrown', async () => {
114
- const errorLog = jest.fn();
115
- const exit = jest.fn(() => undefined as never);
116
- console.error = errorLog;
117
- process.exit = exit;
118
-
119
- const err = new RakeDbError('message');
120
- const custom = () => {
121
- throw err;
122
- };
123
-
124
- const conf = { ...config, commands: { custom } };
125
-
126
- await expect(() => rakeDb(options, conf, ['custom'])).rejects.toThrow(err);
127
-
128
- expect(errorLog).toBeCalledWith('message');
129
- expect(exit).toBeCalledWith(1);
130
- });
131
- });
package/src/rakeDb.ts DELETED
@@ -1,84 +0,0 @@
1
- import { AdapterOptions, MaybeArray, toArray } from 'pqb';
2
- import { createDb, dropDb, resetDb } from './commands/createOrDrop';
3
- import { migrate, rollback } from './commands/migrateOrRollback';
4
- import { processRakeDbConfig, RakeDbConfig } from './common';
5
- import { generate } from './commands/generate';
6
- import { pullDbStructure } from './pull/pull';
7
- import { RakeDbError } from './errors';
8
-
9
- export const rakeDb = async (
10
- options: MaybeArray<AdapterOptions>,
11
- partialConfig: Partial<RakeDbConfig> = {},
12
- args: string[] = process.argv.slice(2),
13
- ) => {
14
- const config = processRakeDbConfig(partialConfig);
15
-
16
- const command = args[0]?.split(':')[0];
17
-
18
- try {
19
- if (command === 'create') {
20
- await createDb(options, config);
21
- } else if (command === 'drop') {
22
- await dropDb(options);
23
- } else if (command === 'reset') {
24
- await resetDb(options, config);
25
- } else if (command === 'migrate') {
26
- await migrate(options, config, args.slice(1));
27
- } else if (command === 'rollback') {
28
- await rollback(options, config, args.slice(1));
29
- } else if (command === 'g' || command === 'generate') {
30
- await generate(config, args.slice(1));
31
- } else if (command === 'pull') {
32
- await pullDbStructure(toArray(options)[0], config);
33
- } else if (config.commands[command]) {
34
- await config.commands[command](toArray(options), config, args.slice(1));
35
- } else {
36
- printHelp();
37
- }
38
- } catch (err) {
39
- if (err instanceof RakeDbError) {
40
- console.error(err.message);
41
- process.exit(1);
42
- }
43
- throw err;
44
- }
45
- };
46
-
47
- const printHelp = () =>
48
- console.log(
49
- `Usage: rake-db [command] [arguments]
50
-
51
- Commands:
52
- create create databases
53
- drop drop databases
54
- reset drop, create and migrate databases
55
- g, generate generate migration file, see below
56
- migrate migrate pending migrations
57
- rollback rollback the last migrated
58
- no or unknown command prints this message
59
-
60
- Migrate arguments:
61
- no arguments run all pending migrations
62
- number run specific number of pending migrations
63
-
64
- Rollback arguments:
65
- no arguments rollback one last applied migration
66
- number rollback specific number of applied migrations
67
- all rollback all applied migrations
68
-
69
- Migrate and rollback common arguments:
70
- --code run code updater, overrides \`useCodeUpdater\` option
71
- --code false do not run code updater
72
-
73
- Generate arguments:
74
- - (required) first argument is migration name
75
- * create* template for create table
76
- * change* template for change table
77
- * add*To* template for add columns
78
- * remove*From* template for remove columns
79
- * drop* template for drop table
80
-
81
- - other arguments considered as columns with types and optional methods:
82
- rake-db g createTable id:serial.primaryKey name:text.nullable
83
- `,
84
- );
package/src/test-utils.ts DELETED
@@ -1,64 +0,0 @@
1
- import { createMigrationInterface, Migration } from './migration/migration';
2
- import { MaybeArray, toArray, TransactionAdapter } from 'pqb';
3
-
4
- export const asMock = (fn: unknown) => fn as jest.Mock;
5
-
6
- let db: Migration | undefined;
7
-
8
- export const getDb = () => {
9
- if (db) return db;
10
-
11
- db = createMigrationInterface(
12
- {} as unknown as TransactionAdapter,
13
- true,
14
- {
15
- basePath: __dirname,
16
- log: false,
17
- migrationsPath: 'migrations-path',
18
- migrationsTable: 'schemaMigrations',
19
- import: require,
20
- appCodeUpdater: appCodeUpdaterMock,
21
- commands: {},
22
- },
23
- {},
24
- {},
25
- );
26
- db.adapter.query = queryMock;
27
- db.adapter.arrays = queryMock;
28
- return db;
29
- };
30
-
31
- export const queryMock = jest.fn();
32
- const appCodeUpdaterMock = jest.fn();
33
-
34
- export const resetDb = () => {
35
- queryMock.mockClear();
36
- queryMock.mockResolvedValue(undefined);
37
- appCodeUpdaterMock.mockClear();
38
- getDb().up = true;
39
- };
40
-
41
- export const setDbDown = () => {
42
- getDb().up = false;
43
- queryMock.mockClear();
44
- };
45
-
46
- export const trim = (s: string) => {
47
- return s.trim().replace(/\n\s+/g, '\n');
48
- };
49
-
50
- export const toLine = (s: string) => {
51
- return s.trim().replace(/\n\s*/g, ' ');
52
- };
53
-
54
- export const expectSql = (sql: MaybeArray<string>) => {
55
- expect(
56
- queryMock.mock.calls.map((call) =>
57
- trim(
58
- typeof call[0] === 'string'
59
- ? call[0]
60
- : (call[0] as { text: string }).text,
61
- ),
62
- ),
63
- ).toEqual(toArray(sql).map(trim));
64
- };
package/tsconfig.json DELETED
@@ -1,12 +0,0 @@
1
- {
2
- "extends": "../../tsconfig.json",
3
- "include": ["./src", "jest-setup.ts", "app"],
4
- "compilerOptions": {
5
- "outDir": "./dist",
6
- "noEmit": false,
7
- "baseUrl": ".",
8
- "paths": {
9
- "pqb": ["../qb/pqb/src"]
10
- }
11
- }
12
- }