Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions gulpfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,27 @@
import fs from "fs/promises"
import gulp from "gulp"
import rename from "gulp-rename"
import { promisify } from "util"

const execAsync = promisify(exec)
async function execAsync(command: string) {
return new Promise<void>((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (stderr.trim().length) {
console.error(stderr)
}
if (stdout.trim().length) {
console.log(stdout)
}
if (error) {
console.error(
`Command "${command}" failed with exit code ${error.code}.`,
)
reject(error)
} else {
resolve()
}
})
})
}

// -------------------------------------------------------------------------
// Packaging for browser
Expand Down Expand Up @@ -56,7 +74,7 @@

async function nodeCreateEsmIndex() {
const buildDir = "./build/package"
const cjsIndex = require(`${buildDir}/index.js`)

Check warning on line 77 in gulpfile.ts

View workflow job for this annotation

GitHub Actions / lint

A `require()` style import is forbidden
const cjsKeys = Object.keys(cjsIndex).filter(
(key) => key !== "default" && !key.startsWith("__"),
)
Expand Down
9 changes: 5 additions & 4 deletions src/connection/ConnectionOptionsReader.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import path from "node:path"

import type { DataSourceOptions } from "../data-source/DataSourceOptions"
import { TypeORMError } from "../error"
import { PlatformTools } from "../platform/PlatformTools"
Expand Down Expand Up @@ -64,7 +62,7 @@ export class ConnectionOptionsReader {
const fileFormats = ["js", "mjs", "cjs", "ts", "mts", "cts", "json"]

// Detect if baseFilePath contains file extension
const possibleExtension = path.extname(this.baseFilePath)
const possibleExtension = PlatformTools.pathExtname(this.baseFilePath)
const fileExtension = fileFormats.find(
(extension) => `.${extension}` === possibleExtension,
)
Expand Down Expand Up @@ -207,7 +205,10 @@ export class ConnectionOptionsReader {
* Gets directory where configuration file should be located and configuration file name.
*/
protected get baseFilePath(): string {
return path.resolve(this.baseDirectory, this.baseConfigName)
return PlatformTools.pathResolve(
this.baseDirectory,
this.baseConfigName,
)
}

/**
Expand Down
74 changes: 49 additions & 25 deletions src/platform/BrowserPlatformTools.template
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { RandomGenerator } from "../util/RandomGenerator"

/**
* Browser's implementation of the platform-specific tools.
*
* This file gonna replace PlatformTools for browser environment.
* For node.js environment this class is not getting packaged.
* Don't use methods of this class in the code, use PlatformTools methods instead.
*/

export class PlatformTools {

/**
* Type of the currently running platform.
*/
static type: "browser"|"node" = "browser";
static type: "browser" | "node" = "browser"

/**
* Gets global variable where global stuff can be stored.
Expand All @@ -33,96 +33,120 @@ export class PlatformTools {
*/
static load(name: string): any {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: require("${name}").`);
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: require("${name}").`,
)

return "";
return ""
}

/**
* @param input string to encode
* @returns the SHA-1 digest of the input string
*/
static sha1(input: string): string {
return RandomGenerator.sha1(input)
}

/**
* Normalizes given path. Does "path.normalize".
*/
static pathNormalize(pathStr: string): string {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: path.normalize("${pathStr}").`);
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: path.normalize("${pathStr}").`,
)

return "";
return ""
}

/**
* Gets file extension. Does "path.extname".
*/
static pathExtname(pathStr: string): string {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: path.extname("${pathStr}").`);
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: path.extname("${pathStr}").`,
)

return "";
return ""
}

/**
* Resolved given path. Does "path.resolve".
*/
static pathResolve(pathStr: string): string {
static pathResolve(...paths: string[]): string {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: path.resolve("${pathStr}").`);
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: path.resolve("${paths.join('", "')}").`,
)

return "";
return ""
}

/**
* Synchronously checks if file exist. Does "fs.existsSync".
*/
static fileExist(pathStr: string): boolean {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: fs.existsSync("${pathStr}").`);
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: fs.existsSync("${pathStr}").`,
)

return false;
return false
}

static readFileSync(filename: string): any {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: fs.readFileSync("${filename}").`);
return null;
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: fs.readFileSync("${filename}").`,
)
return null
}

static appendFileSync(filename: string, data: any) {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: fs.appendFileSync("${filename}").`);
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: fs.appendFileSync("${filename}").`,
)
}

static writeFile(path: string, data: any): Promise<void> {
if (this.type === "browser")
throw new Error(`This option/function is not supported in the browser environment. Failed operation: fs.writeFile("${path}").`);
return Promise.reject(null);
throw new Error(
`This option/function is not supported in the browser environment. Failed operation: fs.writeFile("${path}").`,
)
return Promise.reject(null)
}

/**
* Highlights sql string to be printed in the console.
*/
static highlightSql(sql: string) {
return sql;
return sql
}

/**
* Logging functions needed by AdvancedConsoleLogger (but here without ANSI colors)
*/
static logInfo(prefix: string, info: any) {
console.info(prefix + " ", info);
console.info(prefix + " ", info)
}

static logError(prefix: string, error: any) {
console.error(prefix + " ", error);
console.error(prefix + " ", error)
}

static logWarn(prefix: string, warning: any) {
console.warn(prefix + " ", warning);
console.warn(prefix + " ", warning)
}

static log(message: string) {
console.log(message);
console.log(message)
}

static warn(message: string) {
return message;
return message
}
}

Expand Down
51 changes: 26 additions & 25 deletions src/platform/PlatformTools.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { format as sqlFormat } from "@sqltools/formatter"
import { type Config as SqlFormatterConfig } from "@sqltools/formatter/lib/core/types"
import ansi from "ansis"
import crypto from "crypto"

Check warning on line 4 in src/platform/PlatformTools.ts

View check run for this annotation

SonarQubeCloud / [typeorm] SonarCloud Code Analysis

Prefer `node:crypto` over `crypto`.

See more on https://sonarcloud.io/project/issues?id=typeorm_typeorm&issues=AZ8jYT7FG4MWQlr5-nDJ&open=AZ8jYT7FG4MWQlr5-nDJ&pullRequest=12648
import fs from "fs"
import path from "path"
import { highlight } from "sql-highlight"
import { format as sqlFormat } from "@sqltools/formatter"
import { type Config as SqlFormatterConfig } from "@sqltools/formatter/lib/core/types"
import { type DatabaseType } from "../driver/types/DatabaseType"

export { EventEmitter } from "events"
Expand All @@ -20,34 +21,21 @@
static type: "browser" | "node" = "node"

/**
* Gets global variable where global stuff can be stored.
* @returns the platform-specific global variable
*/
static getGlobalVariable(): any {
return global
}

/**
* Reads the version string from package.json of the given package.
* This operation is only supported in node.
*
* @param name
*/
static readPackageVersion(name: string): string {
try {
return require(`${name}/package.json`).version
} catch (err) {
throw new TypeError(
`Failed to read package.json for "${name}": ${err.message}`,
{ cause: err },
)
if (typeof globalThis !== "undefined") {
return globalThis
}
return global
}

/**
* Loads ("require"-s) given file or package.
* This operation only supports on node platform
* This operation is only supported on the NodeJS platform
*
* @param name
* @param name name of the module to be imported
* @returns the module
*/
static load(name: string): any {
// if name is not absolute or relative, then try to load package from the node_modules of the directory we are currently in
Expand Down Expand Up @@ -151,6 +139,19 @@
throw new TypeError(`Invalid Package for PlatformTools.load: ${name}`)
}

/**
* Returns a SHA-1 hex digest for internal IDs/aliases (not for cryptographic security)
*
* @param input string to encode
* @returns the SHA-1 digest of the input string
*/
static sha1(input: string): string {
const hashFunction = crypto.createHash("sha1")

Check failure on line 149 in src/platform/PlatformTools.ts

View check run for this annotation

SonarQubeCloud / [typeorm] SonarCloud Code Analysis

Make sure this weak hash algorithm is not used in a sensitive context here.

See more on https://sonarcloud.io/project/issues?id=typeorm_typeorm&issues=AZ8jYT7FG4MWQlr5-nDK&open=AZ8jYT7FG4MWQlr5-nDK&pullRequest=12648
Comment thread
alumni marked this conversation as resolved.
Dismissed
hashFunction.update(input, "utf8")
Comment thread
alumni marked this conversation as resolved.
Dismissed

return hashFunction.digest("hex")
}

/**
* Normalizes given path. Does "path.normalize" and replaces backslashes with forward slashes on Windows.
*
Expand All @@ -175,10 +176,10 @@
/**
* Resolved given path. Does "path.resolve".
*
* @param pathStr
* @param paths
*/
static pathResolve(pathStr: string): string {
return path.resolve(pathStr)
static pathResolve(...paths: string[]): string {
return path.resolve(...paths)
}

/**
Expand Down
8 changes: 5 additions & 3 deletions src/util/RandomGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ export class RandomGenerator {
* @returns A version 4 UUID string
*/
static uuidv4(): string {
const crypto = globalThis.crypto as typeof globalThis.crypto | undefined

// Try native crypto.randomUUID() (available in Node.js 19+ and modern browsers)
const uuid = globalThis.crypto?.randomUUID?.()
const uuid = crypto?.randomUUID?.()
if (uuid) {
return uuid
}
Expand All @@ -17,8 +19,8 @@ export class RandomGenerator {
// Based on RFC 4122 version 4 UUID specification
const randomBytes = new Uint8Array(16)

if (globalThis.crypto?.getRandomValues) {
globalThis.crypto.getRandomValues(randomBytes)
if (crypto?.getRandomValues) {
crypto.getRandomValues(randomBytes)
} else {
// Fallback for React Native/Hermes and environments without crypto support
// Hermes (React Native's JavaScript engine) does not provide crypto APIs
Expand Down
39 changes: 2 additions & 37 deletions src/util/StringUtils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { RandomGenerator } from "./RandomGenerator"
import { PlatformTools } from "../platform/PlatformTools"

/**
* Converts string into camelCase.
Expand Down Expand Up @@ -47,23 +47,6 @@ export function titleCase(str: string): string {
)
}

/**
* Builds abbreviated string from given string;
*
* @param str String to be abbreviated.
* @param abbrLettersCount Number of letters to be used for abbreviation.
* @returns abbreviated string
*/
export function abbreviate(str: string, abbrLettersCount: number = 1): string {
const words = str
.replaceAll(/([a-z\xE0-\xFF])([A-Z\xC0\xDF])/g, "$1 $2")
.split(" ")
return words.reduce((res, word) => {
res += word.slice(0, abbrLettersCount)
return res
}, "")
}

export interface IShortenOptions {
/** String used to split "segments" of the alias/column name */
separator?: string
Expand Down Expand Up @@ -117,15 +100,6 @@ export function shorten(input: string, options: IShortenOptions = {}): string {
return shortSegments.join(separator)
}

/**
* Checks if the current environment is Node.js.
*
* @returns `true` if the current environment is Node.js, `false` otherwise.
*/
function isNode(): boolean {
return typeof process !== "undefined" && !!process.versions?.node
}

interface IHashOptions {
length?: number
}
Expand All @@ -139,16 +113,7 @@ interface IHashOptions {
* @returns SHA-1 hex digest
*/
export function hash(input: string, options: IHashOptions = {}): string {
let sha1: string
if (isNode()) {
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
const crypto = require("node:crypto") as typeof import("node:crypto")
const hashFunction = crypto.createHash("sha1")
hashFunction.update(input, "utf8")
sha1 = hashFunction.digest("hex")
} else {
sha1 = RandomGenerator.sha1(input)
}
const sha1 = PlatformTools.sha1(input)

if (options.length && options.length > 0) {
return sha1.slice(0, options.length)
Expand Down
Loading
Loading