mirror of https://github.com/nocodb/nocodb
Pranav C
2 months ago
25 changed files with 283 additions and 498 deletions
@ -1,14 +1,11 @@
|
||||
import * as path from 'path'; |
||||
import fs from 'fs'; |
||||
import { promisify } from 'util'; |
||||
import { getToolDir, metaUrlToDbConfig } from './helpers'; |
||||
import { DriverClient } from './interfaces'; |
||||
import type { DbConfig } from './interfaces'; |
||||
import { SqlClientFactory } from 'nocodb'; |
||||
const { DriverClient, getToolDir, metaUrlToDbConfig } = require( '../nocodb/cli'); |
||||
|
||||
export class NcConfig { |
||||
meta: { |
||||
db: DbConfig; |
||||
db: any; |
||||
} = { |
||||
db: { |
||||
client: DriverClient.SQLITE, |
@ -0,0 +1,3 @@
|
||||
export class NcError extends Error { |
||||
|
||||
} |
@ -0,0 +1,27 @@
|
||||
// a class to log messages to the console with colors and styles
|
||||
export class NcLogger { |
||||
static log(message: string) { |
||||
console.log(message); |
||||
} |
||||
|
||||
static error(message: string) { |
||||
console.error('\x1b[31m%s\x1b[0m', 'Error: ' + message); |
||||
} |
||||
|
||||
static warn(message: string) { |
||||
console.warn('\x1b[33m%s\x1b[0m', 'Warning: ' + message); |
||||
} |
||||
|
||||
static info(message: string) { |
||||
console.info('\x1b[32m%s\x1b[0m', 'Info: ' + message); |
||||
} |
||||
|
||||
static success(message: string) { |
||||
console.log('\x1b[32m%s\x1b[0m', 'Success: ' + message); |
||||
} |
||||
|
||||
static debug(message: string) { |
||||
console.debug('\x1b[34m%s\x1b[0m', 'Debug: ' + message); |
||||
}
|
||||
|
||||
} |
@ -0,0 +1,4 @@
|
||||
export * from './NcConfig'; |
||||
export * from './NcError'; |
||||
export * from './NcLogger'; |
||||
export * from './SecretManager';
|
@ -0,0 +1,17 @@
|
||||
import { describe, it } from 'mocha'; |
||||
import { expect } from 'chai'; |
||||
import { program } from 'commander'; |
||||
|
||||
describe('Index', () => { |
||||
|
||||
describe('index.ts', () => { |
||||
it('should parse the arguments and options correctly', () => { |
||||
const argv = ['node', 'index.ts', 'oldSecret', 'newSecret', '--nc-db','test_db_url', '--database-url', 'test_db_url', '-o', 'oldSecret', '-n', 'newSecret']; |
||||
program.parse(argv); |
||||
expect(program.opts().oldSecret).to.equal('oldSecret'); |
||||
expect(program.opts().newSecret).to.equal('newSecret'); |
||||
expect(program.opts().ncDb).to.equal('test_db_url'); |
||||
expect(program.opts().databaseUrl).to.equal('test_db_url'); |
||||
}); |
||||
}); |
||||
}); |
@ -1,86 +0,0 @@
|
||||
export const driverClientMapping = { |
||||
mysql: 'mysql2', |
||||
mariadb: 'mysql2', |
||||
postgres: 'pg', |
||||
postgresql: 'pg', |
||||
sqlite: 'sqlite3', |
||||
mssql: 'mssql', |
||||
}; |
||||
|
||||
export const defaultClientPortMapping = { |
||||
mysql: 3306, |
||||
mysql2: 3306, |
||||
postgres: 5432, |
||||
pg: 5432, |
||||
mssql: 1433, |
||||
}; |
||||
|
||||
export const defaultConnectionConfig: any = { |
||||
// https://github.com/knex/knex/issues/97
|
||||
// timezone: process.env.NC_TIMEZONE || 'UTC',
|
||||
dateStrings: true, |
||||
}; |
||||
|
||||
// default knex options
|
||||
export const defaultConnectionOptions = { |
||||
pool: { |
||||
min: 0, |
||||
max: 10, |
||||
}, |
||||
}; |
||||
|
||||
export const avoidSSL = [ |
||||
'localhost', |
||||
'127.0.0.1', |
||||
'host.docker.internal', |
||||
'172.17.0.1', |
||||
]; |
||||
|
||||
export const knownQueryParams = [ |
||||
{ |
||||
parameter: 'database', |
||||
aliases: ['d', 'db'], |
||||
}, |
||||
{ |
||||
parameter: 'password', |
||||
aliases: ['p'], |
||||
}, |
||||
{ |
||||
parameter: 'user', |
||||
aliases: ['u'], |
||||
}, |
||||
{ |
||||
parameter: 'title', |
||||
aliases: ['t'], |
||||
}, |
||||
{ |
||||
parameter: 'keyFilePath', |
||||
aliases: [], |
||||
}, |
||||
{ |
||||
parameter: 'certFilePath', |
||||
aliases: [], |
||||
}, |
||||
{ |
||||
parameter: 'caFilePath', |
||||
aliases: [], |
||||
}, |
||||
{ |
||||
parameter: 'ssl', |
||||
aliases: [], |
||||
}, |
||||
{ |
||||
parameter: 'options', |
||||
aliases: ['opt', 'opts'], |
||||
}, |
||||
]; |
||||
|
||||
export enum DriverClient { |
||||
MYSQL = 'mysql2', |
||||
MYSQL_LEGACY = 'mysql', |
||||
MSSQL = 'mssql', |
||||
PG = 'pg', |
||||
SQLITE = 'sqlite3', |
||||
SNOWFLAKE = 'snowflake', |
||||
DATABRICKS = 'databricks', |
||||
} |
@ -1,326 +0,0 @@
|
||||
import fs from 'fs'; |
||||
import { URL } from 'url'; |
||||
import { promisify } from 'util'; |
||||
import parseDbUrl from 'parse-database-url'; |
||||
import { |
||||
avoidSSL, |
||||
defaultClientPortMapping, |
||||
defaultConnectionConfig, |
||||
defaultConnectionOptions, |
||||
driverClientMapping, |
||||
knownQueryParams, |
||||
} from './constants'; |
||||
import { DriverClient } from './interfaces'; |
||||
import type { Connection, DbConfig } from './interfaces'; |
||||
|
||||
export async function prepareEnv() { |
||||
if (process.env.NC_DATABASE_URL_FILE || process.env.DATABASE_URL_FILE) { |
||||
const database_url = await promisify(fs.readFile)( |
||||
(process.env.NC_DATABASE_URL_FILE || process.env.DATABASE_URL_FILE) as string, |
||||
'utf-8', |
||||
); |
||||
process.env.NC_DB = jdbcToXcUrl(database_url); |
||||
} else if (process.env.NC_DATABASE_URL || process.env.DATABASE_URL) { |
||||
process.env.NC_DB = jdbcToXcUrl( |
||||
(process.env.NC_DATABASE_URL || process.env.DATABASE_URL) as string, |
||||
); |
||||
} |
||||
} |
||||
|
||||
export function getToolDir() { |
||||
return process.env.NC_TOOL_DIR || process.cwd(); |
||||
} |
||||
|
||||
export function jdbcToXcConfig(url: string): DbConfig { |
||||
// drop the jdbc prefix
|
||||
url.replace(/^jdbc:/, ''); |
||||
|
||||
const config = parseDbUrl(url); |
||||
|
||||
const parsedConfig: Connection = {}; |
||||
|
||||
for (const [key, value] of Object.entries(config)) { |
||||
const fnd = knownQueryParams.find( |
||||
(param) => param.parameter === key || param.aliases.includes(key), |
||||
); |
||||
if (fnd) { |
||||
parsedConfig[fnd.parameter] = value; |
||||
} else { |
||||
parsedConfig[key] = value; |
||||
} |
||||
} |
||||
|
||||
if (!parsedConfig?.port) { |
||||
parsedConfig.port = |
||||
defaultClientPortMapping[ |
||||
driverClientMapping[parsedConfig.driver as DriverClient] || parsedConfig.driver |
||||
]; |
||||
} |
||||
|
||||
const { driver, ...connectionConfig } = parsedConfig; |
||||
|
||||
const client = driverClientMapping[driver as DriverClient] || driver; |
||||
|
||||
if ( |
||||
client === 'pg' && |
||||
!connectionConfig?.ssl && |
||||
!avoidSSL.includes(connectionConfig.host as string) |
||||
) { |
||||
connectionConfig.ssl = true; |
||||
} |
||||
|
||||
return { |
||||
client: client, |
||||
connection: { |
||||
...connectionConfig, |
||||
}, |
||||
} as DbConfig; |
||||
} |
||||
|
||||
export function jdbcToXcUrl(url: string): string { |
||||
// drop the jdbc prefix
|
||||
url.replace(/^jdbc:/, ''); |
||||
|
||||
const config = parseDbUrl(url); |
||||
|
||||
const parsedConfig: Connection = {}; |
||||
|
||||
for (const [key, value] of Object.entries(config)) { |
||||
const fnd = knownQueryParams.find( |
||||
(param) => param.parameter === key || param.aliases.includes(key), |
||||
); |
||||
if (fnd) { |
||||
parsedConfig[fnd.parameter] = value; |
||||
} else { |
||||
parsedConfig[key] = value; |
||||
} |
||||
} |
||||
|
||||
if (!parsedConfig?.port) { |
||||
parsedConfig.port = |
||||
defaultClientPortMapping[ |
||||
driverClientMapping[parsedConfig.driver as DriverClient] || parsedConfig.driver |
||||
]; |
||||
} |
||||
|
||||
const { driver, host, port, database, user, password, ...extra } = |
||||
parsedConfig; |
||||
|
||||
const extraParams: string[] = []; |
||||
|
||||
for (const [key, value] of Object.entries(extra)) { |
||||
extraParams.push(`${key}=${encodeURIComponent(String(value))}`); |
||||
} |
||||
|
||||
const res = `${driverClientMapping[driver as DriverClient] || driver}://${host}${ |
||||
port ? `:${port}` : '' |
||||
}?${user ? `u=${encodeURIComponent(user)}&` : ''}${ |
||||
password ? `p=${encodeURIComponent(password)}&` : '' |
||||
}${database ? `d=${encodeURIComponent(database)}&` : ''}${extraParams.join( |
||||
'&', |
||||
)}`;
|
||||
|
||||
return res; |
||||
} |
||||
|
||||
export function xcUrlToDbConfig( |
||||
urlString: string, |
||||
key = '', |
||||
type?: string, |
||||
): DbConfig { |
||||
const url = new URL(urlString); |
||||
|
||||
let dbConfig: DbConfig; |
||||
|
||||
if (url.protocol.startsWith('sqlite3')) { |
||||
dbConfig = { |
||||
client: 'sqlite3', |
||||
connection: { |
||||
client: 'sqlite3', |
||||
connection: { |
||||
filename: |
||||
url.searchParams.get('d') || url.searchParams.get('database'), |
||||
}, |
||||
database: url.searchParams.get('d') || url.searchParams.get('database'), |
||||
}, |
||||
} as any; |
||||
} else { |
||||
const parsedQuery = {}; |
||||
for (const [key, value] of url.searchParams.entries()) { |
||||
const fnd = knownQueryParams.find( |
||||
(param) => param.parameter === key || param.aliases.includes(key), |
||||
); |
||||
if (fnd) { |
||||
parsedQuery[fnd.parameter] = value; |
||||
} else { |
||||
parsedQuery[key] = value; |
||||
} |
||||
} |
||||
|
||||
dbConfig = { |
||||
client: url.protocol.replace(':', '') as DriverClient, |
||||
connection: { |
||||
...parsedQuery, |
||||
host: url.hostname, |
||||
port: +url.port, |
||||
}, |
||||
acquireConnectionTimeout: 600000, |
||||
}; |
||||
|
||||
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { |
||||
dbConfig.connection.ssl = true; |
||||
} |
||||
|
||||
if ( |
||||
url.searchParams.get('keyFilePath') && |
||||
url.searchParams.get('certFilePath') && |
||||
url.searchParams.get('caFilePath') |
||||
) { |
||||
dbConfig.connection.ssl = { |
||||
keyFilePath: url.searchParams.get('keyFilePath') as string, |
||||
certFilePath: url.searchParams.get('certFilePath') as string, |
||||
caFilePath: url.searchParams.get('caFilePath') as string, |
||||
}; |
||||
} |
||||
} |
||||
|
||||
/* TODO check if this is needed |
||||
if (config && !config.title) { |
||||
config.title = |
||||
url.searchParams.get('t') || |
||||
url.searchParams.get('title') || |
||||
this.generateRandomTitle(); |
||||
} |
||||
*/ |
||||
|
||||
Object.assign(dbConfig, { |
||||
meta: { |
||||
tn: 'nc_evolutions', |
||||
allSchemas: |
||||
!!url.searchParams.get('allSchemas') || |
||||
!(url.searchParams.get('d') || url.searchParams.get('database')), |
||||
api: { |
||||
prefix: url.searchParams.get('apiPrefix') || '', |
||||
swagger: true, |
||||
type: |
||||
type || |
||||
((url.searchParams.get('api') || url.searchParams.get('a')) as any) || |
||||
'rest', |
||||
}, |
||||
dbAlias: url.searchParams.get('dbAlias') || `db${key}`, |
||||
metaTables: 'db', |
||||
migrations: { |
||||
disabled: false, |
||||
name: 'nc_evolutions', |
||||
}, |
||||
}, |
||||
}); |
||||
|
||||
return dbConfig; |
||||
} |
||||
|
||||
export async function metaUrlToDbConfig(urlString): Promise<DbConfig> { |
||||
const url = new URL(urlString); |
||||
|
||||
let dbConfig: DbConfig; |
||||
|
||||
if (url.protocol.startsWith('sqlite3')) { |
||||
const db = url.searchParams.get('d') || url.searchParams.get('database'); |
||||
dbConfig = { |
||||
client: DriverClient.SQLITE, |
||||
connection: { |
||||
filename: db as string, |
||||
}, |
||||
...(db === ':memory:' |
||||
? { |
||||
pool: { |
||||
min: 1, |
||||
max: 1, |
||||
// disposeTimeout: 360000*1000,
|
||||
idleTimeoutMillis: 360000 * 1000, |
||||
}, |
||||
} |
||||
: {}), |
||||
}; |
||||
} else { |
||||
const parsedQuery = {}; |
||||
for (const [key, value] of url.searchParams.entries()) { |
||||
const fnd = knownQueryParams.find( |
||||
(param) => param.parameter === key || param.aliases.includes(key), |
||||
); |
||||
if (fnd) { |
||||
parsedQuery[fnd.parameter] = value; |
||||
} else { |
||||
parsedQuery[key] = value; |
||||
} |
||||
} |
||||
|
||||
dbConfig = { |
||||
client: url.protocol.replace(':', '') as DriverClient, |
||||
connection: { |
||||
...defaultConnectionConfig, |
||||
...parsedQuery, |
||||
host: url.hostname, |
||||
port: +url.port, |
||||
}, |
||||
acquireConnectionTimeout: 600000, |
||||
...defaultConnectionOptions, |
||||
...(url.searchParams.has('search_path') |
||||
? { |
||||
searchPath: url.searchParams.get('search_path')?.split(','), |
||||
} |
||||
: {}), |
||||
}; |
||||
|
||||
if (process.env.NODE_TLS_REJECT_UNAUTHORIZED) { |
||||
dbConfig.connection.ssl = true; |
||||
} |
||||
} |
||||
|
||||
url.searchParams.forEach((_value, key) => { |
||||
let value: any = _value; |
||||
if (value === 'true') { |
||||
value = true; |
||||
} else if (value === 'false') { |
||||
value = false; |
||||
} else if (/^\d+$/.test(value)) { |
||||
value = +value; |
||||
} |
||||
// todo: implement config read from JSON file or JSON env val read
|
||||
if ( |
||||
!['password', 'p', 'database', 'd', 'user', 'u', 'search_path'].includes( |
||||
key, |
||||
) |
||||
) { |
||||
key.split('.').reduce((obj, k, i, arr) => { |
||||
return (obj[k] = i === arr.length - 1 ? value : obj[k] || {}); |
||||
}, dbConfig); |
||||
} |
||||
}); |
||||
|
||||
if ( |
||||
dbConfig?.connection?.ssl && |
||||
typeof dbConfig?.connection?.ssl === 'object' |
||||
) { |
||||
if (dbConfig.connection.ssl.caFilePath && !dbConfig.connection.ssl.ca) { |
||||
dbConfig.connection.ssl.ca = ( |
||||
await promisify(fs.readFile)(dbConfig.connection.ssl.caFilePath) |
||||
).toString(); |
||||
delete dbConfig.connection.ssl.caFilePath; |
||||
} |
||||
if (dbConfig.connection.ssl.keyFilePath && !dbConfig.connection.ssl.key) { |
||||
dbConfig.connection.ssl.key = ( |
||||
await promisify(fs.readFile)(dbConfig.connection.ssl.keyFilePath) |
||||
).toString(); |
||||
delete dbConfig.connection.ssl.keyFilePath; |
||||
} |
||||
if (dbConfig.connection.ssl.certFilePath && !dbConfig.connection.ssl.cert) { |
||||
dbConfig.connection.ssl.cert = ( |
||||
await promisify(fs.readFile)(dbConfig.connection.ssl.certFilePath) |
||||
).toString(); |
||||
delete dbConfig.connection.ssl.certFilePath; |
||||
} |
||||
} |
||||
|
||||
return dbConfig; |
||||
} |
@ -1,4 +0,0 @@
|
||||
export * from './helpers'; |
||||
export * from './interfaces'; |
||||
export * from './constants'; |
||||
export * from './NcConfig'; |
@ -1,39 +0,0 @@
|
||||
import { DriverClient } from './constants'; |
||||
|
||||
interface Connection { |
||||
driver?: DriverClient; |
||||
host?: string; |
||||
port?: number; |
||||
database?: string; |
||||
user?: string; |
||||
password?: string; |
||||
ssl?: |
||||
| boolean |
||||
| { |
||||
ca?: string; |
||||
cert?: string; |
||||
key?: string; |
||||
caFilePath?: string; |
||||
certFilePath?: string; |
||||
keyFilePath?: string; |
||||
}; |
||||
filename?: string; |
||||
} |
||||
|
||||
interface DbConfig { |
||||
client: DriverClient; |
||||
connection: Connection; |
||||
acquireConnectionTimeout?: number; |
||||
useNullAsDefault?: boolean; |
||||
pool?: { |
||||
min?: number; |
||||
max?: number; |
||||
idleTimeoutMillis?: number; |
||||
}; |
||||
migrations?: { |
||||
directory?: string; |
||||
tableName?: string; |
||||
}; |
||||
} |
||||
|
||||
export { DriverClient, Connection, DbConfig }; |
File diff suppressed because one or more lines are too long
@ -0,0 +1,46 @@
|
||||
const nodeExternals = require('webpack-node-externals'); |
||||
const TerserPlugin = require('terser-webpack-plugin'); |
||||
const webpack = require('webpack'); |
||||
const path = require('path'); |
||||
module.exports = { |
||||
entry: './src/index.ts', |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.tsx?$/, |
||||
exclude: /node_modules/, |
||||
use: { |
||||
loader: 'ts-loader', |
||||
options: { |
||||
transpileOnly: true |
||||
} |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
|
||||
optimization: { |
||||
minimize: true, //Update this to true or false
|
||||
minimizer: [new TerserPlugin()], |
||||
nodeEnv: false |
||||
}, |
||||
externals: [nodeExternals()], |
||||
resolve: { |
||||
extensions: ['.tsx', '.ts', '.js', '.json'], |
||||
}, |
||||
output: { |
||||
filename: 'cli.js', |
||||
path: path.resolve(__dirname, 'dist'), |
||||
library: 'libs', |
||||
libraryTarget: 'umd', |
||||
globalObject: "typeof self !== 'undefined' ? self : this", |
||||
}, |
||||
// node: {
|
||||
// fs: 'empty'
|
||||
// },
|
||||
plugins: [ |
||||
new webpack.BannerPlugin({banner: "#! /usr/bin/env node", raw: true}), |
||||
], |
||||
|
||||
target: 'node', |
||||
}; |
@ -0,0 +1,5 @@
|
||||
export { SqlClientFactory } from '~/db/sql-client/lib/SqlClientFactory'; |
||||
export { MetaTable } from '~/utils/globals'; |
||||
export * from '~/utils/encryptDecrypt'; |
||||
export { getToolDir, metaUrlToDbConfig } from '~/utils/nc-config/helpers'; |
||||
export { DriverClient } from '~/utils/nc-config/constants'; |
@ -0,0 +1,61 @@
|
||||
const path = require('path'); |
||||
const nodeExternals = require('webpack-node-externals'); |
||||
const webpack = require('webpack'); |
||||
const TerserPlugin = require('terser-webpack-plugin'); |
||||
const { resolveTsAliases } = require('./build-utils/resolveTsAliases'); |
||||
|
||||
module.exports = { |
||||
entry: './src/cli.ts', |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.tsx?$/, |
||||
exclude: /node_modules/, |
||||
use: { |
||||
loader: 'ts-loader', |
||||
options: { |
||||
transpileOnly: true, |
||||
}, |
||||
}, |
||||
}, |
||||
], |
||||
}, |
||||
|
||||
optimization: { |
||||
minimize: true, //Update this to true or false
|
||||
minimizer: [ |
||||
new TerserPlugin({ |
||||
extractComments: false, |
||||
}), |
||||
], |
||||
nodeEnv: false, |
||||
}, |
||||
externals: [ |
||||
nodeExternals({ |
||||
allowlist: ['nocodb-sdk'], |
||||
}), |
||||
], |
||||
resolve: { |
||||
extensions: ['.tsx', '.ts', '.js', '.json'], |
||||
alias: resolveTsAliases(path.resolve('tsconfig.json')), |
||||
}, |
||||
mode: 'production', |
||||
output: { |
||||
filename: 'cli.js', |
||||
path: path.resolve(__dirname, '..', 'nc-secret-cli', 'src/nocodb'), |
||||
library: 'libs', |
||||
libraryTarget: 'umd', |
||||
globalObject: "typeof self !== 'undefined' ? self : this", |
||||
}, |
||||
node: { |
||||
__dirname: false, |
||||
}, |
||||
plugins: [ |
||||
new webpack.EnvironmentPlugin(['EE']), |
||||
new webpack.BannerPlugin({ |
||||
banner: 'This is a generated file. Do not edit', |
||||
entryOnly:true |
||||
}), |
||||
], |
||||
target: 'node', |
||||
}; |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,13 @@
|
||||
process.env.NC_BINARY_BUILD = 'true'; |
||||
(async () => { |
||||
try { |
||||
const app = require('express')(); |
||||
const {Noco} = require("nocodb"); |
||||
const port = process.env.PORT || 8080; |
||||
const httpServer = app.listen(port); |
||||
app.use(await Noco.init({}, httpServer, app)); |
||||
console.log(`Visit : localhost:${port}/dashboard`) |
||||
} catch(e) { |
||||
console.log(e) |
||||
} |
||||
})() |
Loading…
Reference in new issue