@verdaccio/local-storage 14.0.0-next-9.35 → 14.0.0-next-9.37
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/build/dir-utils.js.map +1 -1
- package/build/fs.js.map +1 -1
- package/build/index.js.map +1 -1
- package/build/local-database.js.map +1 -1
- package/build/local-database.mjs.map +1 -1
- package/build/local-fs.js +23 -37
- package/build/local-fs.js.map +1 -1
- package/build/local-fs.mjs +23 -37
- package/build/local-fs.mjs.map +1 -1
- package/build/pkg-utils.js.map +1 -1
- package/build/utils.js.map +1 -1
- package/package.json +8 -8
package/build/dir-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dir-utils.js","names":[],"sources":["../src/dir-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport globby from 'globby';\nimport { join } from 'node:path';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { validationUtils } from '@verdaccio/core';\n\nconst debug = buildDebug('verdaccio:plugin:local-storage:utils');\n\n/**\n * Retrieve a list of absolute paths to all folders in the given storage path\n * @param storagePath the base path of the storage\n * @return a promise that resolves to an array of absolute paths\n */\nexport async function getFolders(storagePath: string, pattern = '*'): Promise<string[]> {\n const files = await globby(pattern, {\n // @ts-ignore\n cwd: storagePath,\n expandDirectories: true,\n onlyDirectories: true,\n onlyFiles: false,\n // should not go deeper than the storage path (10 is reasonable for the storage)\n deep: 10,\n dot: false,\n followSymbolicLinks: true,\n caseSensitiveMatch: true,\n unique: true,\n // FIXME: add here list of forbiden patterns\n // don't include scoped folders.\n // ignore: [`@*`],\n });\n return files;\n}\n\n/**\n * Search packages on the the storage. The storage could be\n * - storage\n * - pkg1\n * - @company\n * - pkg2 -> @scompany/pkg2\n * - storage1\n * - pkg2\n * - pkg3\n * - storage2\n * - @scope\n * - pkg4 > @scope/pkg4\n * The search return a data structure like:\n * [\n * {\n * name: 'pkg1', // package name could be @scope/pkg1\n * path: absolute/path/package/name\n * }\n * ]\n * @param {string} storagePath is the base path of the storage folder,\n * inside could be packages, storages and @scope packages.\n * @param {Set<string>} storages storages are defined peer package access pattern via `storage` property\n * @param query is the search query from the user via npm search command.\n * and are intended to organize packages in a tree structure.\n * @returns {Promise<searchUtils.SearchItemPkg[]>}\n */\nexport async function searchOnStorage(\n storagePath: string,\n storages: Map<string, string>\n): Promise<searchUtils.SearchItemPkg[]> {\n const matchedStorages = Array.from(storages);\n const storageFolders = Array.from(storages.keys());\n debug('search on %o', storagePath);\n debug('storage folders %o', matchedStorages.length);\n const results: searchUtils.SearchItemPkg[] = [];\n // watch base path and ignore storage folders\n const basePathFolders = (await getFolders(storagePath, '*')).filter(\n (storageFolder) => !storageFolders.includes(storageFolder)\n );\n\n for (const store of basePathFolders) {\n if (validationUtils.isPackageNameScoped(store)) {\n const scopedPackages = await getFolders(join(storagePath, store), '*');\n const listScoped = scopedPackages.map((scoped) => ({\n name: `${store}/${scoped}`,\n scoped: store,\n }));\n results.push(...listScoped);\n } else {\n results.push({\n name: store,\n });\n }\n }\n\n // iterate each storage folder\n for (const store of storageFolders) {\n const foldersOnStorage = await getFolders(join(storagePath, store), '*');\n for (const pkgName of foldersOnStorage) {\n if (validationUtils.isPackageNameScoped(pkgName)) {\n const scopedPackages = await getFolders(join(storagePath, store, pkgName), '*');\n const listScoped = scopedPackages.map((scoped) => ({\n name: `${pkgName}/${scoped}`,\n scoped: pkgName,\n }));\n results.push(...listScoped);\n } else {\n results.push({\n name: pkgName,\n });\n }\n }\n }\n\n return results;\n}\n"],"mappings":";;;;;;;;AAOA,
|
|
1
|
+
{"version":3,"file":"dir-utils.js","names":["debug","buildDebug","validationUtils","join"],"sources":["../src/dir-utils.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport globby from 'globby';\nimport { join } from 'node:path';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { validationUtils } from '@verdaccio/core';\n\nconst debug = buildDebug('verdaccio:plugin:local-storage:utils');\n\n/**\n * Retrieve a list of absolute paths to all folders in the given storage path\n * @param storagePath the base path of the storage\n * @return a promise that resolves to an array of absolute paths\n */\nexport async function getFolders(storagePath: string, pattern = '*'): Promise<string[]> {\n const files = await globby(pattern, {\n // @ts-ignore\n cwd: storagePath,\n expandDirectories: true,\n onlyDirectories: true,\n onlyFiles: false,\n // should not go deeper than the storage path (10 is reasonable for the storage)\n deep: 10,\n dot: false,\n followSymbolicLinks: true,\n caseSensitiveMatch: true,\n unique: true,\n // FIXME: add here list of forbiden patterns\n // don't include scoped folders.\n // ignore: [`@*`],\n });\n return files;\n}\n\n/**\n * Search packages on the the storage. The storage could be\n * - storage\n * - pkg1\n * - @company\n * - pkg2 -> @scompany/pkg2\n * - storage1\n * - pkg2\n * - pkg3\n * - storage2\n * - @scope\n * - pkg4 > @scope/pkg4\n * The search return a data structure like:\n * [\n * {\n * name: 'pkg1', // package name could be @scope/pkg1\n * path: absolute/path/package/name\n * }\n * ]\n * @param {string} storagePath is the base path of the storage folder,\n * inside could be packages, storages and @scope packages.\n * @param {Set<string>} storages storages are defined peer package access pattern via `storage` property\n * @param query is the search query from the user via npm search command.\n * and are intended to organize packages in a tree structure.\n * @returns {Promise<searchUtils.SearchItemPkg[]>}\n */\nexport async function searchOnStorage(\n storagePath: string,\n storages: Map<string, string>\n): Promise<searchUtils.SearchItemPkg[]> {\n const matchedStorages = Array.from(storages);\n const storageFolders = Array.from(storages.keys());\n debug('search on %o', storagePath);\n debug('storage folders %o', matchedStorages.length);\n const results: searchUtils.SearchItemPkg[] = [];\n // watch base path and ignore storage folders\n const basePathFolders = (await getFolders(storagePath, '*')).filter(\n (storageFolder) => !storageFolders.includes(storageFolder)\n );\n\n for (const store of basePathFolders) {\n if (validationUtils.isPackageNameScoped(store)) {\n const scopedPackages = await getFolders(join(storagePath, store), '*');\n const listScoped = scopedPackages.map((scoped) => ({\n name: `${store}/${scoped}`,\n scoped: store,\n }));\n results.push(...listScoped);\n } else {\n results.push({\n name: store,\n });\n }\n }\n\n // iterate each storage folder\n for (const store of storageFolders) {\n const foldersOnStorage = await getFolders(join(storagePath, store), '*');\n for (const pkgName of foldersOnStorage) {\n if (validationUtils.isPackageNameScoped(pkgName)) {\n const scopedPackages = await getFolders(join(storagePath, store, pkgName), '*');\n const listScoped = scopedPackages.map((scoped) => ({\n name: `${pkgName}/${scoped}`,\n scoped: pkgName,\n }));\n results.push(...listScoped);\n } else {\n results.push({\n name: pkgName,\n });\n }\n }\n }\n\n return results;\n}\n"],"mappings":";;;;;;;;AAOA,IAAMA,WAAAA,GAAQC,MAAAA,QAAAA,CAAW,sCAAsC;;;;;;AAO/D,eAAsB,WAAW,aAAqB,UAAU,KAAwB;CAiBtF,OAAO,OAAA,GAhBa,OAAA,QAAA,CAAO,SAAS;EAElC,KAAK;EACL,mBAAmB;EACnB,iBAAiB;EACjB,WAAW;EAEX,MAAM;EACN,KAAK;EACL,qBAAqB;EACrB,oBAAoB;EACpB,QAAQ;CAIV,CAAC;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;AA4BA,eAAsB,gBACpB,aACA,UACsC;CACtC,MAAM,kBAAkB,MAAM,KAAK,QAAQ;CAC3C,MAAM,iBAAiB,MAAM,KAAK,SAAS,KAAK,CAAC;CACjD,QAAM,gBAAgB,WAAW;CACjC,QAAM,sBAAsB,gBAAgB,MAAM;CAClD,MAAM,UAAuC,CAAC;CAE9C,MAAM,mBAAmB,MAAM,WAAW,aAAa,GAAG,EAAA,CAAG,QAC1D,kBAAkB,CAAC,eAAe,SAAS,aAAa,CAC3D;CAEA,KAAK,MAAM,SAAS,iBAClB,IAAIC,gBAAAA,gBAAgB,oBAAoB,KAAK,GAAG;EAE9C,MAAM,cAAa,MADU,YAAA,GAAWC,UAAAA,KAAAA,CAAK,aAAa,KAAK,GAAG,GAAG,EAAA,CACnC,KAAK,YAAY;GACjD,MAAM,GAAG,MAAM,GAAG;GAClB,QAAQ;EACV,EAAE;EACF,QAAQ,KAAK,GAAG,UAAU;CAC5B,OACE,QAAQ,KAAK,EACX,MAAM,MACR,CAAC;CAKL,KAAK,MAAM,SAAS,gBAAgB;EAClC,MAAM,mBAAmB,MAAM,YAAA,GAAWA,UAAAA,KAAAA,CAAK,aAAa,KAAK,GAAG,GAAG;EACvE,KAAK,MAAM,WAAW,kBACpB,IAAID,gBAAAA,gBAAgB,oBAAoB,OAAO,GAAG;GAEhD,MAAM,cAAa,MADU,YAAA,GAAWC,UAAAA,KAAAA,CAAK,aAAa,OAAO,OAAO,GAAG,GAAG,EAAA,CAC5C,KAAK,YAAY;IACjD,MAAM,GAAG,QAAQ,GAAG;IACpB,QAAQ;GACV,EAAE;GACF,QAAQ,KAAK,GAAG,UAAU;EAC5B,OACE,QAAQ,KAAK,EACX,MAAM,QACR,CAAC;CAGP;CAEA,OAAO;AACT"}
|
package/build/fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fs.js","names":[],"sources":["../src/fs.ts"],"sourcesContent":["import fsCallback from 'node:fs';\nimport * as fsP from 'node:fs/promises';\n\nconst readFile = fsP.readFile;\nconst mkdirPromise = fsP.mkdir;\nconst accessPromise = fsP.access;\nconst writeFilePromise = fsP.writeFile;\nconst readdirPromise = fsP.readdir;\nconst statPromise = fsP.stat;\nconst unlinkPromise = fsP.unlink;\nconst rmdirPromise = fsP.rmdir;\nconst renamePromise = fsP.rename;\nconst openPromise = fsP.open;\n\nconst readFilePromise = async (path) => {\n return await readFile(path, 'utf8');\n};\n\nfunction fstatPromise(fd: number): Promise<fsCallback.Stats> {\n return new Promise((resolve, reject) => {\n fsCallback.fstat(fd, function (err, stats) {\n if (err) {\n return reject(err);\n }\n return resolve(stats);\n });\n });\n}\n\nexport {\n readFilePromise,\n renamePromise,\n mkdirPromise,\n writeFilePromise,\n readdirPromise,\n statPromise,\n accessPromise,\n unlinkPromise,\n rmdirPromise,\n openPromise,\n fstatPromise,\n};\n"],"mappings":";;;;;;AAGA,IAAM,
|
|
1
|
+
{"version":3,"file":"fs.js","names":["fsP"],"sources":["../src/fs.ts"],"sourcesContent":["import fsCallback from 'node:fs';\nimport * as fsP from 'node:fs/promises';\n\nconst readFile = fsP.readFile;\nconst mkdirPromise = fsP.mkdir;\nconst accessPromise = fsP.access;\nconst writeFilePromise = fsP.writeFile;\nconst readdirPromise = fsP.readdir;\nconst statPromise = fsP.stat;\nconst unlinkPromise = fsP.unlink;\nconst rmdirPromise = fsP.rmdir;\nconst renamePromise = fsP.rename;\nconst openPromise = fsP.open;\n\nconst readFilePromise = async (path) => {\n return await readFile(path, 'utf8');\n};\n\nfunction fstatPromise(fd: number): Promise<fsCallback.Stats> {\n return new Promise((resolve, reject) => {\n fsCallback.fstat(fd, function (err, stats) {\n if (err) {\n return reject(err);\n }\n return resolve(stats);\n });\n });\n}\n\nexport {\n readFilePromise,\n renamePromise,\n mkdirPromise,\n writeFilePromise,\n readdirPromise,\n statPromise,\n accessPromise,\n unlinkPromise,\n rmdirPromise,\n openPromise,\n fstatPromise,\n};\n"],"mappings":";;;;;;AAGA,IAAM,WAAWA,iBAAI;AACrB,IAAM,eAAeA,iBAAI;AACzB,IAAM,gBAAgBA,iBAAI;AAC1B,IAAM,mBAAmBA,iBAAI;AAE7B,IAAM,cAAcA,iBAAI;AACxB,IAAM,gBAAgBA,iBAAI;AAC1B,IAAM,eAAeA,iBAAI;AACzB,IAAM,gBAAgBA,iBAAI;AAC1B,IAAM,cAAcA,iBAAI;AAExB,IAAM,kBAAkB,OAAO,SAAS;CACtC,OAAO,MAAM,SAAS,MAAM,MAAM;AACpC"}
|
package/build/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["import LocalDatabase from './local-database';\n\nexport { LocalDatabase };\n\nexport default LocalDatabase;\n"],"mappings":";;;;;;AAIA,IAAA,
|
|
1
|
+
{"version":3,"file":"index.js","names":["LocalDatabase"],"sources":["../src/index.ts"],"sourcesContent":["import LocalDatabase from './local-database';\n\nexport { LocalDatabase };\n\nexport default LocalDatabase;\n"],"mappings":";;;;;;AAIA,IAAA,cAAeA,uBAAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-database.js","names":[],"sources":["../src/local-database.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { escapeRegExp, isNil } from 'lodash-es';\nimport { Low, Memory } from 'lowdb';\nimport { JSONFile } from 'lowdb/node';\nimport path from 'node:path';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { authUtils, errorUtils, fileUtils, pluginUtils } from '@verdaccio/core';\nimport type { Config, Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport { searchOnStorage } from './dir-utils';\nimport { mkdirPromise, writeFilePromise } from './fs';\nimport LocalDriver, { noSuchFile } from './local-fs';\nimport type { LocalStorage } from './pkg-utils';\nimport { loadPrivatePackages } from './pkg-utils';\nimport { _dbGenPath } from './utils';\n\nconst TOKEN_DB_NAME = '.token-db.json';\nconst DB_NAME = process.env.VERDACCIO_STORAGE_NAME ?? fileUtils.Files.DatabaseName;\n\nconst debug = buildDebug('verdaccio:plugin:local-storage');\n\nexport const ERROR_DB_LOCKED =\n 'Database is locked, please check error message printed during startup to prevent data loss';\n\ntype Storage = pluginUtils.Storage<{}>;\n\nclass LocalDatabase extends pluginUtils.Plugin<{}> implements Storage {\n private readonly path: string;\n private readonly logger: Logger;\n public readonly config: Config;\n public readonly storages: Map<string, string>;\n public data: LocalStorage | undefined;\n public locked: boolean;\n public tokenDb: Low<Record<string, Token[]>> | null;\n\n public constructor(config: Config, logger: Logger) {\n // TODO: fix double config\n super(config, { config, logger });\n this.tokenDb = null;\n this.config = config;\n this.logger = logger;\n this.locked = false;\n this.data = undefined;\n debug('config path %o', config.configPath);\n this.path = _dbGenPath(DB_NAME, config);\n this.storages = this._getCustomPackageLocalStorages();\n this.logger.info({ path: this.path }, 'local storage path: @{path}');\n debug('plugin storage path %o', this.path);\n }\n\n public async init(): Promise<void> {\n debug('plugin init');\n this.data = await this.fetchLocalPackages();\n debug('local packages loaded');\n await this._sync();\n }\n\n public async getSecret(): Promise<string> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n return Promise.resolve(this.data.secret);\n }\n\n public async setSecret(secret: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n } else {\n this.data.secret = secret;\n }\n\n await this._sync();\n }\n\n public async add(name: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n if (this.data.list.indexOf(name) === -1) {\n this.data.list.push(name);\n debug('the private package %s has been added', name);\n await this._sync();\n } else {\n debug('the private package %s already exist on database', name);\n return Promise.resolve();\n }\n }\n\n /**\n * The field storage could be absolute or relative.\n * If relative, it will be resolved against the config path.\n * If absolute, it will be returned as is.\n **/\n private getStoragePath() {\n const { storage } = this.config;\n if (typeof storage !== 'string') {\n throw new TypeError('storage field is mandatory');\n }\n\n const storagePath = path.isAbsolute(storage)\n ? storage\n : path.normalize(path.join(this.getBaseConfigPath(), storage));\n debug('storage path %o', storagePath);\n return storagePath;\n }\n\n private getBaseConfigPath(): string {\n return path.dirname(this.config.configPath);\n }\n\n /**\n * Filter and only match those values that the query define.\n **/\n public async filterByQuery(results: searchUtils.SearchItemPkg[], query: searchUtils.SearchQuery) {\n // FUTURE: apply new filters, keyword, version, ...\n return results.filter((item: searchUtils.SearchItemPkg) => {\n // Sanitize user input\n const safeText = escapeRegExp(query.text);\n return item?.name?.match(safeText) !== null;\n }) as searchUtils.SearchItemPkg[];\n }\n\n public async getScore(_pkg: searchUtils.SearchItemPkg): Promise<searchUtils.Score> {\n // TODO: there is no particular reason to predefined scores\n // could be improved by using\n return Promise.resolve({\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n });\n }\n\n /**\n *\n * @param query\n * @returns\n */\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n debug('search query to %o', query.text);\n const results: searchUtils.SearchItem[] = [];\n const storagePath = this.getStoragePath();\n const localResults = await searchOnStorage(storagePath, this.storages);\n const packagesOnStorage = await this.filterByQuery(localResults, query);\n debug('packages found %o', packagesOnStorage.length);\n for (const storage of packagesOnStorage) {\n // check if package is listed on the cache private database\n const isPrivate = (this.data as LocalStorage).list.includes(storage.name);\n const score = await this.getScore(storage);\n results.push({\n package: storage,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n public async remove(name: string): Promise<void> {\n try {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const data = await this.get();\n\n const pkgName = data.indexOf(name);\n if (pkgName !== -1) {\n this.data.list.splice(pkgName, 1);\n debug('package %o has been removed', name);\n }\n await this._sync();\n } catch (err) {\n this.logger.error({ err }, 'remove the private package has failed @{err}');\n throw errorUtils.getInternalError('error remove private package');\n }\n }\n\n public async get(): Promise<any> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const { list } = this.data;\n const totalItems = list?.length;\n debug('get full list of packages (%o) has been fetched', totalItems);\n return Promise.resolve(list);\n }\n\n public getPackageStorage(packageName: string): pluginUtils.StorageHandler {\n // Storage root\n const storageRoot = path.resolve(this.getStoragePath());\n debug('storage root %o', storageRoot);\n\n if (isNil(storageRoot)) {\n this.logger.error('property storage in config.yaml is required for using this plugin');\n throw new Error('property storage in config.yaml is required for using this plugin');\n }\n\n // Storage path based on package access configuration\n const packageAccess = authUtils.getMatchedPackagesSpec(packageName, this.config.packages);\n\n let storagePath;\n if (packageAccess && typeof packageAccess.storage === 'string') {\n storagePath = fileUtils.resolveSafePath(storageRoot, packageAccess.storage);\n debug('storage path %o', storagePath);\n\n if (isNil(storagePath)) {\n this.logger.error(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n throw errorUtils.getInternalError(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n } else {\n storagePath = storageRoot;\n }\n\n // Storage path for the package\n const packageStoragePath = fileUtils.resolveSafePath(storagePath, packageName);\n debug('package path %o', packageStoragePath);\n\n // the folder must be the name itself, not a normalized form of it\n const isExactFolder =\n !isNil(packageStoragePath) &&\n path.relative(storagePath, packageStoragePath).split(path.sep).join('/') === packageName;\n\n if (!isExactFolder) {\n this.logger.warn(\n { packageName },\n 'package @{packageName} has no local storage because its name does not match its folder'\n );\n throw errorUtils.getInternalError(\n 'package-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n\n return new LocalDriver(packageStoragePath as string, this.logger);\n }\n\n public async clean(): Promise<void> {\n await this._sync();\n }\n\n private _getCustomPackageLocalStorages(): Map<string, string> {\n const storages = new Map<string, string>();\n const { packages } = this.config;\n\n if (packages) {\n Object.keys(packages || {}).map((pkg) => {\n const { storage } = packages[pkg];\n if (typeof storage === 'string') {\n const storagePath = path.join(this.getStoragePath(), storage);\n debug('add custom storage for %s on %s', storage, storagePath);\n storages.set(storage, storagePath);\n }\n });\n }\n\n return storages;\n }\n\n private async _sync(): Promise<null> {\n debug('sync database started');\n\n if (this.locked) {\n this.logger.error(ERROR_DB_LOCKED);\n throw new Error(ERROR_DB_LOCKED);\n }\n // Uses sync to prevent ugly race condition\n try {\n const folderName = path.dirname(this.path);\n debug('creating folder %o', folderName);\n await mkdirPromise(folderName, { recursive: true });\n debug('creating folder %o created succeed', folderName);\n } catch (err) {\n this.logger.error({ err }, 'sync create folder has failed with error: @{err}');\n throw err;\n }\n\n try {\n await writeFilePromise(this.path, JSON.stringify(this.data));\n debug('sync write succeeded');\n\n return null;\n } catch (err: any) {\n this.logger.error({ err }, 'sync database file failed: @{err}');\n throw err;\n }\n }\n\n private async fetchLocalPackages(): Promise<LocalStorage> {\n try {\n return await loadPrivatePackages(this.path, this.logger);\n } catch (err: any) {\n // readFileSync is platform specific, macOS, Linux and Windows thrown an error\n // Only recreate if file not found to prevent data loss\n this.logger.info(\n { path: this.path },\n 'no private database found, recreating new one on @{path}'\n );\n if (err.code !== noSuchFile) {\n this.locked = true;\n this.logger.error(\n 'Failed to read package database file, please check the error printed below:\\n',\n `File Path: ${this.path}\\n\\n ${err.message}`\n );\n }\n // if no database is found we set empty placeholders\n return { list: [], secret: '' };\n }\n }\n\n private async getTokenDb(): Promise<Low<Record<string, Token[]>>> {\n if (!this.tokenDb) {\n debug('token database is not defined');\n let adapter;\n if (process.env.NODE_ENV === 'test') {\n debug('token memory adapter');\n adapter = new Memory<Record<string, Token[]>>();\n } else {\n debug('token async adapter');\n const pathDb = _dbGenPath(TOKEN_DB_NAME, this.config);\n adapter = new JSONFile<Record<string, Token[]>>(pathDb);\n }\n debug('token db generated');\n this.tokenDb = new Low<Record<string, Token[]>>(adapter, {});\n await this.tokenDb.read();\n // keyed by username: names like `constructor` or `__proto__` must not hit the prototype\n this.tokenDb.data = Object.assign(Object.create(null), this.tokenDb.data);\n }\n\n return this.tokenDb;\n }\n\n public async saveToken(token: Token): Promise<void> {\n debug('token key %o', token.key);\n const db = await this.getTokenDb();\n const userData = db.data[token.user];\n debug('user data %o', userData);\n if (isNil(userData)) {\n db.data[token.user] = [token];\n debug('token user %o new database', token.user);\n } else {\n db.data[token.user].push(token);\n }\n await db.write();\n debug('data %o', db.data);\n debug('token saved %o', token.user);\n }\n\n public async deleteToken(user: string, tokenKey: string): Promise<void> {\n const db = await this.getTokenDb();\n const userTokens = db.data[user];\n if (isNil(userTokens)) {\n throw new Error('user not found');\n }\n debug('tokens %o - %o', userTokens, userTokens.length);\n db.data[user] = userTokens.filter(({ key }) => {\n debug('key %o', key);\n return key !== tokenKey;\n });\n await db.write();\n debug('removed tokens key %o', tokenKey);\n }\n\n public async readTokens(filter: TokenFilter): Promise<Token[]> {\n const { user } = filter;\n debug('read tokens with %o', user);\n const db = await this.getTokenDb();\n const tokens = db.data[user];\n return tokens || [];\n }\n}\n\nexport default LocalDatabase;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-database.js","names":["fileUtils","debug","buildDebug","pluginUtils","_dbGenPath","path","escapeRegExp","searchOnStorage","errorUtils","isNil","authUtils","LocalDriver","mkdirPromise","writeFilePromise","loadPrivatePackages","Memory","JSONFile","Low"],"sources":["../src/local-database.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { escapeRegExp, isNil } from 'lodash-es';\nimport { Low, Memory } from 'lowdb';\nimport { JSONFile } from 'lowdb/node';\nimport path from 'node:path';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { authUtils, errorUtils, fileUtils, pluginUtils } from '@verdaccio/core';\nimport type { Config, Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport { searchOnStorage } from './dir-utils';\nimport { mkdirPromise, writeFilePromise } from './fs';\nimport LocalDriver, { noSuchFile } from './local-fs';\nimport type { LocalStorage } from './pkg-utils';\nimport { loadPrivatePackages } from './pkg-utils';\nimport { _dbGenPath } from './utils';\n\nconst TOKEN_DB_NAME = '.token-db.json';\nconst DB_NAME = process.env.VERDACCIO_STORAGE_NAME ?? fileUtils.Files.DatabaseName;\n\nconst debug = buildDebug('verdaccio:plugin:local-storage');\n\nexport const ERROR_DB_LOCKED =\n 'Database is locked, please check error message printed during startup to prevent data loss';\n\ntype Storage = pluginUtils.Storage<{}>;\n\nclass LocalDatabase extends pluginUtils.Plugin<{}> implements Storage {\n private readonly path: string;\n private readonly logger: Logger;\n public readonly config: Config;\n public readonly storages: Map<string, string>;\n public data: LocalStorage | undefined;\n public locked: boolean;\n public tokenDb: Low<Record<string, Token[]>> | null;\n\n public constructor(config: Config, logger: Logger) {\n // TODO: fix double config\n super(config, { config, logger });\n this.tokenDb = null;\n this.config = config;\n this.logger = logger;\n this.locked = false;\n this.data = undefined;\n debug('config path %o', config.configPath);\n this.path = _dbGenPath(DB_NAME, config);\n this.storages = this._getCustomPackageLocalStorages();\n this.logger.info({ path: this.path }, 'local storage path: @{path}');\n debug('plugin storage path %o', this.path);\n }\n\n public async init(): Promise<void> {\n debug('plugin init');\n this.data = await this.fetchLocalPackages();\n debug('local packages loaded');\n await this._sync();\n }\n\n public async getSecret(): Promise<string> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n return Promise.resolve(this.data.secret);\n }\n\n public async setSecret(secret: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n } else {\n this.data.secret = secret;\n }\n\n await this._sync();\n }\n\n public async add(name: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n if (this.data.list.indexOf(name) === -1) {\n this.data.list.push(name);\n debug('the private package %s has been added', name);\n await this._sync();\n } else {\n debug('the private package %s already exist on database', name);\n return Promise.resolve();\n }\n }\n\n /**\n * The field storage could be absolute or relative.\n * If relative, it will be resolved against the config path.\n * If absolute, it will be returned as is.\n **/\n private getStoragePath() {\n const { storage } = this.config;\n if (typeof storage !== 'string') {\n throw new TypeError('storage field is mandatory');\n }\n\n const storagePath = path.isAbsolute(storage)\n ? storage\n : path.normalize(path.join(this.getBaseConfigPath(), storage));\n debug('storage path %o', storagePath);\n return storagePath;\n }\n\n private getBaseConfigPath(): string {\n return path.dirname(this.config.configPath);\n }\n\n /**\n * Filter and only match those values that the query define.\n **/\n public async filterByQuery(results: searchUtils.SearchItemPkg[], query: searchUtils.SearchQuery) {\n // FUTURE: apply new filters, keyword, version, ...\n return results.filter((item: searchUtils.SearchItemPkg) => {\n // Sanitize user input\n const safeText = escapeRegExp(query.text);\n return item?.name?.match(safeText) !== null;\n }) as searchUtils.SearchItemPkg[];\n }\n\n public async getScore(_pkg: searchUtils.SearchItemPkg): Promise<searchUtils.Score> {\n // TODO: there is no particular reason to predefined scores\n // could be improved by using\n return Promise.resolve({\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n });\n }\n\n /**\n *\n * @param query\n * @returns\n */\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n debug('search query to %o', query.text);\n const results: searchUtils.SearchItem[] = [];\n const storagePath = this.getStoragePath();\n const localResults = await searchOnStorage(storagePath, this.storages);\n const packagesOnStorage = await this.filterByQuery(localResults, query);\n debug('packages found %o', packagesOnStorage.length);\n for (const storage of packagesOnStorage) {\n // check if package is listed on the cache private database\n const isPrivate = (this.data as LocalStorage).list.includes(storage.name);\n const score = await this.getScore(storage);\n results.push({\n package: storage,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n public async remove(name: string): Promise<void> {\n try {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const data = await this.get();\n\n const pkgName = data.indexOf(name);\n if (pkgName !== -1) {\n this.data.list.splice(pkgName, 1);\n debug('package %o has been removed', name);\n }\n await this._sync();\n } catch (err) {\n this.logger.error({ err }, 'remove the private package has failed @{err}');\n throw errorUtils.getInternalError('error remove private package');\n }\n }\n\n public async get(): Promise<any> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const { list } = this.data;\n const totalItems = list?.length;\n debug('get full list of packages (%o) has been fetched', totalItems);\n return Promise.resolve(list);\n }\n\n public getPackageStorage(packageName: string): pluginUtils.StorageHandler {\n // Storage root\n const storageRoot = path.resolve(this.getStoragePath());\n debug('storage root %o', storageRoot);\n\n if (isNil(storageRoot)) {\n this.logger.error('property storage in config.yaml is required for using this plugin');\n throw new Error('property storage in config.yaml is required for using this plugin');\n }\n\n // Storage path based on package access configuration\n const packageAccess = authUtils.getMatchedPackagesSpec(packageName, this.config.packages);\n\n let storagePath;\n if (packageAccess && typeof packageAccess.storage === 'string') {\n storagePath = fileUtils.resolveSafePath(storageRoot, packageAccess.storage);\n debug('storage path %o', storagePath);\n\n if (isNil(storagePath)) {\n this.logger.error(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n throw errorUtils.getInternalError(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n } else {\n storagePath = storageRoot;\n }\n\n // Storage path for the package\n const packageStoragePath = fileUtils.resolveSafePath(storagePath, packageName);\n debug('package path %o', packageStoragePath);\n\n // the folder must be the name itself, not a normalized form of it\n const isExactFolder =\n !isNil(packageStoragePath) &&\n path.relative(storagePath, packageStoragePath).split(path.sep).join('/') === packageName;\n\n if (!isExactFolder) {\n this.logger.warn(\n { packageName },\n 'package @{packageName} has no local storage because its name does not match its folder'\n );\n throw errorUtils.getInternalError(\n 'package-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n\n return new LocalDriver(packageStoragePath as string, this.logger);\n }\n\n public async clean(): Promise<void> {\n await this._sync();\n }\n\n private _getCustomPackageLocalStorages(): Map<string, string> {\n const storages = new Map<string, string>();\n const { packages } = this.config;\n\n if (packages) {\n Object.keys(packages || {}).map((pkg) => {\n const { storage } = packages[pkg];\n if (typeof storage === 'string') {\n const storagePath = path.join(this.getStoragePath(), storage);\n debug('add custom storage for %s on %s', storage, storagePath);\n storages.set(storage, storagePath);\n }\n });\n }\n\n return storages;\n }\n\n private async _sync(): Promise<null> {\n debug('sync database started');\n\n if (this.locked) {\n this.logger.error(ERROR_DB_LOCKED);\n throw new Error(ERROR_DB_LOCKED);\n }\n // Uses sync to prevent ugly race condition\n try {\n const folderName = path.dirname(this.path);\n debug('creating folder %o', folderName);\n await mkdirPromise(folderName, { recursive: true });\n debug('creating folder %o created succeed', folderName);\n } catch (err) {\n this.logger.error({ err }, 'sync create folder has failed with error: @{err}');\n throw err;\n }\n\n try {\n await writeFilePromise(this.path, JSON.stringify(this.data));\n debug('sync write succeeded');\n\n return null;\n } catch (err: any) {\n this.logger.error({ err }, 'sync database file failed: @{err}');\n throw err;\n }\n }\n\n private async fetchLocalPackages(): Promise<LocalStorage> {\n try {\n return await loadPrivatePackages(this.path, this.logger);\n } catch (err: any) {\n // readFileSync is platform specific, macOS, Linux and Windows thrown an error\n // Only recreate if file not found to prevent data loss\n this.logger.info(\n { path: this.path },\n 'no private database found, recreating new one on @{path}'\n );\n if (err.code !== noSuchFile) {\n this.locked = true;\n this.logger.error(\n 'Failed to read package database file, please check the error printed below:\\n',\n `File Path: ${this.path}\\n\\n ${err.message}`\n );\n }\n // if no database is found we set empty placeholders\n return { list: [], secret: '' };\n }\n }\n\n private async getTokenDb(): Promise<Low<Record<string, Token[]>>> {\n if (!this.tokenDb) {\n debug('token database is not defined');\n let adapter;\n if (process.env.NODE_ENV === 'test') {\n debug('token memory adapter');\n adapter = new Memory<Record<string, Token[]>>();\n } else {\n debug('token async adapter');\n const pathDb = _dbGenPath(TOKEN_DB_NAME, this.config);\n adapter = new JSONFile<Record<string, Token[]>>(pathDb);\n }\n debug('token db generated');\n this.tokenDb = new Low<Record<string, Token[]>>(adapter, {});\n await this.tokenDb.read();\n // keyed by username: names like `constructor` or `__proto__` must not hit the prototype\n this.tokenDb.data = Object.assign(Object.create(null), this.tokenDb.data);\n }\n\n return this.tokenDb;\n }\n\n public async saveToken(token: Token): Promise<void> {\n debug('token key %o', token.key);\n const db = await this.getTokenDb();\n const userData = db.data[token.user];\n debug('user data %o', userData);\n if (isNil(userData)) {\n db.data[token.user] = [token];\n debug('token user %o new database', token.user);\n } else {\n db.data[token.user].push(token);\n }\n await db.write();\n debug('data %o', db.data);\n debug('token saved %o', token.user);\n }\n\n public async deleteToken(user: string, tokenKey: string): Promise<void> {\n const db = await this.getTokenDb();\n const userTokens = db.data[user];\n if (isNil(userTokens)) {\n throw new Error('user not found');\n }\n debug('tokens %o - %o', userTokens, userTokens.length);\n db.data[user] = userTokens.filter(({ key }) => {\n debug('key %o', key);\n return key !== tokenKey;\n });\n await db.write();\n debug('removed tokens key %o', tokenKey);\n }\n\n public async readTokens(filter: TokenFilter): Promise<Token[]> {\n const { user } = filter;\n debug('read tokens with %o', user);\n const db = await this.getTokenDb();\n const tokens = db.data[user];\n return tokens || [];\n }\n}\n\nexport default LocalDatabase;\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-database.mjs","names":[],"sources":["../src/local-database.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { escapeRegExp, isNil } from 'lodash-es';\nimport { Low, Memory } from 'lowdb';\nimport { JSONFile } from 'lowdb/node';\nimport path from 'node:path';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { authUtils, errorUtils, fileUtils, pluginUtils } from '@verdaccio/core';\nimport type { Config, Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport { searchOnStorage } from './dir-utils';\nimport { mkdirPromise, writeFilePromise } from './fs';\nimport LocalDriver, { noSuchFile } from './local-fs';\nimport type { LocalStorage } from './pkg-utils';\nimport { loadPrivatePackages } from './pkg-utils';\nimport { _dbGenPath } from './utils';\n\nconst TOKEN_DB_NAME = '.token-db.json';\nconst DB_NAME = process.env.VERDACCIO_STORAGE_NAME ?? fileUtils.Files.DatabaseName;\n\nconst debug = buildDebug('verdaccio:plugin:local-storage');\n\nexport const ERROR_DB_LOCKED =\n 'Database is locked, please check error message printed during startup to prevent data loss';\n\ntype Storage = pluginUtils.Storage<{}>;\n\nclass LocalDatabase extends pluginUtils.Plugin<{}> implements Storage {\n private readonly path: string;\n private readonly logger: Logger;\n public readonly config: Config;\n public readonly storages: Map<string, string>;\n public data: LocalStorage | undefined;\n public locked: boolean;\n public tokenDb: Low<Record<string, Token[]>> | null;\n\n public constructor(config: Config, logger: Logger) {\n // TODO: fix double config\n super(config, { config, logger });\n this.tokenDb = null;\n this.config = config;\n this.logger = logger;\n this.locked = false;\n this.data = undefined;\n debug('config path %o', config.configPath);\n this.path = _dbGenPath(DB_NAME, config);\n this.storages = this._getCustomPackageLocalStorages();\n this.logger.info({ path: this.path }, 'local storage path: @{path}');\n debug('plugin storage path %o', this.path);\n }\n\n public async init(): Promise<void> {\n debug('plugin init');\n this.data = await this.fetchLocalPackages();\n debug('local packages loaded');\n await this._sync();\n }\n\n public async getSecret(): Promise<string> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n return Promise.resolve(this.data.secret);\n }\n\n public async setSecret(secret: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n } else {\n this.data.secret = secret;\n }\n\n await this._sync();\n }\n\n public async add(name: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n if (this.data.list.indexOf(name) === -1) {\n this.data.list.push(name);\n debug('the private package %s has been added', name);\n await this._sync();\n } else {\n debug('the private package %s already exist on database', name);\n return Promise.resolve();\n }\n }\n\n /**\n * The field storage could be absolute or relative.\n * If relative, it will be resolved against the config path.\n * If absolute, it will be returned as is.\n **/\n private getStoragePath() {\n const { storage } = this.config;\n if (typeof storage !== 'string') {\n throw new TypeError('storage field is mandatory');\n }\n\n const storagePath = path.isAbsolute(storage)\n ? storage\n : path.normalize(path.join(this.getBaseConfigPath(), storage));\n debug('storage path %o', storagePath);\n return storagePath;\n }\n\n private getBaseConfigPath(): string {\n return path.dirname(this.config.configPath);\n }\n\n /**\n * Filter and only match those values that the query define.\n **/\n public async filterByQuery(results: searchUtils.SearchItemPkg[], query: searchUtils.SearchQuery) {\n // FUTURE: apply new filters, keyword, version, ...\n return results.filter((item: searchUtils.SearchItemPkg) => {\n // Sanitize user input\n const safeText = escapeRegExp(query.text);\n return item?.name?.match(safeText) !== null;\n }) as searchUtils.SearchItemPkg[];\n }\n\n public async getScore(_pkg: searchUtils.SearchItemPkg): Promise<searchUtils.Score> {\n // TODO: there is no particular reason to predefined scores\n // could be improved by using\n return Promise.resolve({\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n });\n }\n\n /**\n *\n * @param query\n * @returns\n */\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n debug('search query to %o', query.text);\n const results: searchUtils.SearchItem[] = [];\n const storagePath = this.getStoragePath();\n const localResults = await searchOnStorage(storagePath, this.storages);\n const packagesOnStorage = await this.filterByQuery(localResults, query);\n debug('packages found %o', packagesOnStorage.length);\n for (const storage of packagesOnStorage) {\n // check if package is listed on the cache private database\n const isPrivate = (this.data as LocalStorage).list.includes(storage.name);\n const score = await this.getScore(storage);\n results.push({\n package: storage,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n public async remove(name: string): Promise<void> {\n try {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const data = await this.get();\n\n const pkgName = data.indexOf(name);\n if (pkgName !== -1) {\n this.data.list.splice(pkgName, 1);\n debug('package %o has been removed', name);\n }\n await this._sync();\n } catch (err) {\n this.logger.error({ err }, 'remove the private package has failed @{err}');\n throw errorUtils.getInternalError('error remove private package');\n }\n }\n\n public async get(): Promise<any> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const { list } = this.data;\n const totalItems = list?.length;\n debug('get full list of packages (%o) has been fetched', totalItems);\n return Promise.resolve(list);\n }\n\n public getPackageStorage(packageName: string): pluginUtils.StorageHandler {\n // Storage root\n const storageRoot = path.resolve(this.getStoragePath());\n debug('storage root %o', storageRoot);\n\n if (isNil(storageRoot)) {\n this.logger.error('property storage in config.yaml is required for using this plugin');\n throw new Error('property storage in config.yaml is required for using this plugin');\n }\n\n // Storage path based on package access configuration\n const packageAccess = authUtils.getMatchedPackagesSpec(packageName, this.config.packages);\n\n let storagePath;\n if (packageAccess && typeof packageAccess.storage === 'string') {\n storagePath = fileUtils.resolveSafePath(storageRoot, packageAccess.storage);\n debug('storage path %o', storagePath);\n\n if (isNil(storagePath)) {\n this.logger.error(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n throw errorUtils.getInternalError(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n } else {\n storagePath = storageRoot;\n }\n\n // Storage path for the package\n const packageStoragePath = fileUtils.resolveSafePath(storagePath, packageName);\n debug('package path %o', packageStoragePath);\n\n // the folder must be the name itself, not a normalized form of it\n const isExactFolder =\n !isNil(packageStoragePath) &&\n path.relative(storagePath, packageStoragePath).split(path.sep).join('/') === packageName;\n\n if (!isExactFolder) {\n this.logger.warn(\n { packageName },\n 'package @{packageName} has no local storage because its name does not match its folder'\n );\n throw errorUtils.getInternalError(\n 'package-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n\n return new LocalDriver(packageStoragePath as string, this.logger);\n }\n\n public async clean(): Promise<void> {\n await this._sync();\n }\n\n private _getCustomPackageLocalStorages(): Map<string, string> {\n const storages = new Map<string, string>();\n const { packages } = this.config;\n\n if (packages) {\n Object.keys(packages || {}).map((pkg) => {\n const { storage } = packages[pkg];\n if (typeof storage === 'string') {\n const storagePath = path.join(this.getStoragePath(), storage);\n debug('add custom storage for %s on %s', storage, storagePath);\n storages.set(storage, storagePath);\n }\n });\n }\n\n return storages;\n }\n\n private async _sync(): Promise<null> {\n debug('sync database started');\n\n if (this.locked) {\n this.logger.error(ERROR_DB_LOCKED);\n throw new Error(ERROR_DB_LOCKED);\n }\n // Uses sync to prevent ugly race condition\n try {\n const folderName = path.dirname(this.path);\n debug('creating folder %o', folderName);\n await mkdirPromise(folderName, { recursive: true });\n debug('creating folder %o created succeed', folderName);\n } catch (err) {\n this.logger.error({ err }, 'sync create folder has failed with error: @{err}');\n throw err;\n }\n\n try {\n await writeFilePromise(this.path, JSON.stringify(this.data));\n debug('sync write succeeded');\n\n return null;\n } catch (err: any) {\n this.logger.error({ err }, 'sync database file failed: @{err}');\n throw err;\n }\n }\n\n private async fetchLocalPackages(): Promise<LocalStorage> {\n try {\n return await loadPrivatePackages(this.path, this.logger);\n } catch (err: any) {\n // readFileSync is platform specific, macOS, Linux and Windows thrown an error\n // Only recreate if file not found to prevent data loss\n this.logger.info(\n { path: this.path },\n 'no private database found, recreating new one on @{path}'\n );\n if (err.code !== noSuchFile) {\n this.locked = true;\n this.logger.error(\n 'Failed to read package database file, please check the error printed below:\\n',\n `File Path: ${this.path}\\n\\n ${err.message}`\n );\n }\n // if no database is found we set empty placeholders\n return { list: [], secret: '' };\n }\n }\n\n private async getTokenDb(): Promise<Low<Record<string, Token[]>>> {\n if (!this.tokenDb) {\n debug('token database is not defined');\n let adapter;\n if (process.env.NODE_ENV === 'test') {\n debug('token memory adapter');\n adapter = new Memory<Record<string, Token[]>>();\n } else {\n debug('token async adapter');\n const pathDb = _dbGenPath(TOKEN_DB_NAME, this.config);\n adapter = new JSONFile<Record<string, Token[]>>(pathDb);\n }\n debug('token db generated');\n this.tokenDb = new Low<Record<string, Token[]>>(adapter, {});\n await this.tokenDb.read();\n // keyed by username: names like `constructor` or `__proto__` must not hit the prototype\n this.tokenDb.data = Object.assign(Object.create(null), this.tokenDb.data);\n }\n\n return this.tokenDb;\n }\n\n public async saveToken(token: Token): Promise<void> {\n debug('token key %o', token.key);\n const db = await this.getTokenDb();\n const userData = db.data[token.user];\n debug('user data %o', userData);\n if (isNil(userData)) {\n db.data[token.user] = [token];\n debug('token user %o new database', token.user);\n } else {\n db.data[token.user].push(token);\n }\n await db.write();\n debug('data %o', db.data);\n debug('token saved %o', token.user);\n }\n\n public async deleteToken(user: string, tokenKey: string): Promise<void> {\n const db = await this.getTokenDb();\n const userTokens = db.data[user];\n if (isNil(userTokens)) {\n throw new Error('user not found');\n }\n debug('tokens %o - %o', userTokens, userTokens.length);\n db.data[user] = userTokens.filter(({ key }) => {\n debug('key %o', key);\n return key !== tokenKey;\n });\n await db.write();\n debug('removed tokens key %o', tokenKey);\n }\n\n public async readTokens(filter: TokenFilter): Promise<Token[]> {\n const { user } = filter;\n debug('read tokens with %o', user);\n const db = await this.getTokenDb();\n const tokens = db.data[user];\n return tokens || [];\n }\n}\n\nexport default LocalDatabase;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"local-database.mjs","names":["LocalDriver"],"sources":["../src/local-database.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport { escapeRegExp, isNil } from 'lodash-es';\nimport { Low, Memory } from 'lowdb';\nimport { JSONFile } from 'lowdb/node';\nimport path from 'node:path';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { authUtils, errorUtils, fileUtils, pluginUtils } from '@verdaccio/core';\nimport type { Config, Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport { searchOnStorage } from './dir-utils';\nimport { mkdirPromise, writeFilePromise } from './fs';\nimport LocalDriver, { noSuchFile } from './local-fs';\nimport type { LocalStorage } from './pkg-utils';\nimport { loadPrivatePackages } from './pkg-utils';\nimport { _dbGenPath } from './utils';\n\nconst TOKEN_DB_NAME = '.token-db.json';\nconst DB_NAME = process.env.VERDACCIO_STORAGE_NAME ?? fileUtils.Files.DatabaseName;\n\nconst debug = buildDebug('verdaccio:plugin:local-storage');\n\nexport const ERROR_DB_LOCKED =\n 'Database is locked, please check error message printed during startup to prevent data loss';\n\ntype Storage = pluginUtils.Storage<{}>;\n\nclass LocalDatabase extends pluginUtils.Plugin<{}> implements Storage {\n private readonly path: string;\n private readonly logger: Logger;\n public readonly config: Config;\n public readonly storages: Map<string, string>;\n public data: LocalStorage | undefined;\n public locked: boolean;\n public tokenDb: Low<Record<string, Token[]>> | null;\n\n public constructor(config: Config, logger: Logger) {\n // TODO: fix double config\n super(config, { config, logger });\n this.tokenDb = null;\n this.config = config;\n this.logger = logger;\n this.locked = false;\n this.data = undefined;\n debug('config path %o', config.configPath);\n this.path = _dbGenPath(DB_NAME, config);\n this.storages = this._getCustomPackageLocalStorages();\n this.logger.info({ path: this.path }, 'local storage path: @{path}');\n debug('plugin storage path %o', this.path);\n }\n\n public async init(): Promise<void> {\n debug('plugin init');\n this.data = await this.fetchLocalPackages();\n debug('local packages loaded');\n await this._sync();\n }\n\n public async getSecret(): Promise<string> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n return Promise.resolve(this.data.secret);\n }\n\n public async setSecret(secret: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n } else {\n this.data.secret = secret;\n }\n\n await this._sync();\n }\n\n public async add(name: string): Promise<void> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n if (this.data.list.indexOf(name) === -1) {\n this.data.list.push(name);\n debug('the private package %s has been added', name);\n await this._sync();\n } else {\n debug('the private package %s already exist on database', name);\n return Promise.resolve();\n }\n }\n\n /**\n * The field storage could be absolute or relative.\n * If relative, it will be resolved against the config path.\n * If absolute, it will be returned as is.\n **/\n private getStoragePath() {\n const { storage } = this.config;\n if (typeof storage !== 'string') {\n throw new TypeError('storage field is mandatory');\n }\n\n const storagePath = path.isAbsolute(storage)\n ? storage\n : path.normalize(path.join(this.getBaseConfigPath(), storage));\n debug('storage path %o', storagePath);\n return storagePath;\n }\n\n private getBaseConfigPath(): string {\n return path.dirname(this.config.configPath);\n }\n\n /**\n * Filter and only match those values that the query define.\n **/\n public async filterByQuery(results: searchUtils.SearchItemPkg[], query: searchUtils.SearchQuery) {\n // FUTURE: apply new filters, keyword, version, ...\n return results.filter((item: searchUtils.SearchItemPkg) => {\n // Sanitize user input\n const safeText = escapeRegExp(query.text);\n return item?.name?.match(safeText) !== null;\n }) as searchUtils.SearchItemPkg[];\n }\n\n public async getScore(_pkg: searchUtils.SearchItemPkg): Promise<searchUtils.Score> {\n // TODO: there is no particular reason to predefined scores\n // could be improved by using\n return Promise.resolve({\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n });\n }\n\n /**\n *\n * @param query\n * @returns\n */\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n debug('search query to %o', query.text);\n const results: searchUtils.SearchItem[] = [];\n const storagePath = this.getStoragePath();\n const localResults = await searchOnStorage(storagePath, this.storages);\n const packagesOnStorage = await this.filterByQuery(localResults, query);\n debug('packages found %o', packagesOnStorage.length);\n for (const storage of packagesOnStorage) {\n // check if package is listed on the cache private database\n const isPrivate = (this.data as LocalStorage).list.includes(storage.name);\n const score = await this.getScore(storage);\n results.push({\n package: storage,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n public async remove(name: string): Promise<void> {\n try {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const data = await this.get();\n\n const pkgName = data.indexOf(name);\n if (pkgName !== -1) {\n this.data.list.splice(pkgName, 1);\n debug('package %o has been removed', name);\n }\n await this._sync();\n } catch (err) {\n this.logger.error({ err }, 'remove the private package has failed @{err}');\n throw errorUtils.getInternalError('error remove private package');\n }\n }\n\n public async get(): Promise<any> {\n if (typeof this.data === 'undefined') {\n throw Error('no data secret available');\n }\n\n const { list } = this.data;\n const totalItems = list?.length;\n debug('get full list of packages (%o) has been fetched', totalItems);\n return Promise.resolve(list);\n }\n\n public getPackageStorage(packageName: string): pluginUtils.StorageHandler {\n // Storage root\n const storageRoot = path.resolve(this.getStoragePath());\n debug('storage root %o', storageRoot);\n\n if (isNil(storageRoot)) {\n this.logger.error('property storage in config.yaml is required for using this plugin');\n throw new Error('property storage in config.yaml is required for using this plugin');\n }\n\n // Storage path based on package access configuration\n const packageAccess = authUtils.getMatchedPackagesSpec(packageName, this.config.packages);\n\n let storagePath;\n if (packageAccess && typeof packageAccess.storage === 'string') {\n storagePath = fileUtils.resolveSafePath(storageRoot, packageAccess.storage);\n debug('storage path %o', storagePath);\n\n if (isNil(storagePath)) {\n this.logger.error(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n throw errorUtils.getInternalError(\n 'access-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n } else {\n storagePath = storageRoot;\n }\n\n // Storage path for the package\n const packageStoragePath = fileUtils.resolveSafePath(storagePath, packageName);\n debug('package path %o', packageStoragePath);\n\n // the folder must be the name itself, not a normalized form of it\n const isExactFolder =\n !isNil(packageStoragePath) &&\n path.relative(storagePath, packageStoragePath).split(path.sep).join('/') === packageName;\n\n if (!isExactFolder) {\n this.logger.warn(\n { packageName },\n 'package @{packageName} has no local storage because its name does not match its folder'\n );\n throw errorUtils.getInternalError(\n 'package-specific storage path is not under the configured storage directory or is invalid'\n );\n }\n\n return new LocalDriver(packageStoragePath as string, this.logger);\n }\n\n public async clean(): Promise<void> {\n await this._sync();\n }\n\n private _getCustomPackageLocalStorages(): Map<string, string> {\n const storages = new Map<string, string>();\n const { packages } = this.config;\n\n if (packages) {\n Object.keys(packages || {}).map((pkg) => {\n const { storage } = packages[pkg];\n if (typeof storage === 'string') {\n const storagePath = path.join(this.getStoragePath(), storage);\n debug('add custom storage for %s on %s', storage, storagePath);\n storages.set(storage, storagePath);\n }\n });\n }\n\n return storages;\n }\n\n private async _sync(): Promise<null> {\n debug('sync database started');\n\n if (this.locked) {\n this.logger.error(ERROR_DB_LOCKED);\n throw new Error(ERROR_DB_LOCKED);\n }\n // Uses sync to prevent ugly race condition\n try {\n const folderName = path.dirname(this.path);\n debug('creating folder %o', folderName);\n await mkdirPromise(folderName, { recursive: true });\n debug('creating folder %o created succeed', folderName);\n } catch (err) {\n this.logger.error({ err }, 'sync create folder has failed with error: @{err}');\n throw err;\n }\n\n try {\n await writeFilePromise(this.path, JSON.stringify(this.data));\n debug('sync write succeeded');\n\n return null;\n } catch (err: any) {\n this.logger.error({ err }, 'sync database file failed: @{err}');\n throw err;\n }\n }\n\n private async fetchLocalPackages(): Promise<LocalStorage> {\n try {\n return await loadPrivatePackages(this.path, this.logger);\n } catch (err: any) {\n // readFileSync is platform specific, macOS, Linux and Windows thrown an error\n // Only recreate if file not found to prevent data loss\n this.logger.info(\n { path: this.path },\n 'no private database found, recreating new one on @{path}'\n );\n if (err.code !== noSuchFile) {\n this.locked = true;\n this.logger.error(\n 'Failed to read package database file, please check the error printed below:\\n',\n `File Path: ${this.path}\\n\\n ${err.message}`\n );\n }\n // if no database is found we set empty placeholders\n return { list: [], secret: '' };\n }\n }\n\n private async getTokenDb(): Promise<Low<Record<string, Token[]>>> {\n if (!this.tokenDb) {\n debug('token database is not defined');\n let adapter;\n if (process.env.NODE_ENV === 'test') {\n debug('token memory adapter');\n adapter = new Memory<Record<string, Token[]>>();\n } else {\n debug('token async adapter');\n const pathDb = _dbGenPath(TOKEN_DB_NAME, this.config);\n adapter = new JSONFile<Record<string, Token[]>>(pathDb);\n }\n debug('token db generated');\n this.tokenDb = new Low<Record<string, Token[]>>(adapter, {});\n await this.tokenDb.read();\n // keyed by username: names like `constructor` or `__proto__` must not hit the prototype\n this.tokenDb.data = Object.assign(Object.create(null), this.tokenDb.data);\n }\n\n return this.tokenDb;\n }\n\n public async saveToken(token: Token): Promise<void> {\n debug('token key %o', token.key);\n const db = await this.getTokenDb();\n const userData = db.data[token.user];\n debug('user data %o', userData);\n if (isNil(userData)) {\n db.data[token.user] = [token];\n debug('token user %o new database', token.user);\n } else {\n db.data[token.user].push(token);\n }\n await db.write();\n debug('data %o', db.data);\n debug('token saved %o', token.user);\n }\n\n public async deleteToken(user: string, tokenKey: string): Promise<void> {\n const db = await this.getTokenDb();\n const userTokens = db.data[user];\n if (isNil(userTokens)) {\n throw new Error('user not found');\n }\n debug('tokens %o - %o', userTokens, userTokens.length);\n db.data[user] = userTokens.filter(({ key }) => {\n debug('key %o', key);\n return key !== tokenKey;\n });\n await db.write();\n debug('removed tokens key %o', tokenKey);\n }\n\n public async readTokens(filter: TokenFilter): Promise<Token[]> {\n const { user } = filter;\n debug('read tokens with %o', user);\n const db = await this.getTokenDb();\n const tokens = db.data[user];\n return tokens || [];\n }\n}\n\nexport default LocalDatabase;\n"],"mappings":"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"}
|
package/build/local-fs.js
CHANGED
|
@@ -198,49 +198,35 @@ var LocalFS = class {
|
|
|
198
198
|
await this.checkCreateFolder(pathName);
|
|
199
199
|
const temporalName = node_path.default.join(this.path, (0, sanitize_filename.default)(`${fileName}.tmp-${String(Math.random()).replace(/^0\./, "")}`));
|
|
200
200
|
debug$1("write a temporal name %o", temporalName);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
const writeStream = node_fs.default.createWriteStream(temporalName);
|
|
204
|
-
writeStream.on("open", () => {
|
|
205
|
-
opened = true;
|
|
206
|
-
});
|
|
207
|
-
writeStream.on("error", async (err) => {
|
|
208
|
-
failed = true;
|
|
209
|
-
if (opened) {
|
|
210
|
-
this.logger.error({
|
|
211
|
-
err,
|
|
212
|
-
fileName
|
|
213
|
-
}, "error on open write tarball for @{fileName}: @{err.message}");
|
|
214
|
-
writeStream.once("close", async () => {
|
|
215
|
-
await this.removeTempFile(temporalName);
|
|
216
|
-
});
|
|
217
|
-
} else {
|
|
218
|
-
this.logger.error({
|
|
219
|
-
err,
|
|
220
|
-
fileName
|
|
221
|
-
}, "error on writing tarball for @{fileName}: @{err.message}");
|
|
201
|
+
const finalize = async (closeError) => {
|
|
202
|
+
if (closeError || writeStream.errored || !writeStream.writableFinished) {
|
|
222
203
|
await this.removeTempFile(temporalName);
|
|
204
|
+
return;
|
|
223
205
|
}
|
|
224
|
-
});
|
|
225
|
-
writeStream.once("close", async () => {
|
|
226
|
-
if (failed) return;
|
|
227
206
|
try {
|
|
228
|
-
await
|
|
207
|
+
await require_fs.renamePromise(temporalName, pathName);
|
|
229
208
|
} catch (err) {
|
|
230
|
-
this.
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
209
|
+
await this.removeTempFile(temporalName);
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
const writeStream = node_fs.default.createWriteStream(temporalName, { fs: {
|
|
214
|
+
open: node_fs.default.open,
|
|
215
|
+
write: node_fs.default.write,
|
|
216
|
+
writev: node_fs.default.writev,
|
|
217
|
+
close(fd, callback) {
|
|
218
|
+
node_fs.default.close(fd, (err) => {
|
|
219
|
+
finalize(err).then(() => callback(err), callback);
|
|
220
|
+
});
|
|
235
221
|
}
|
|
222
|
+
} });
|
|
223
|
+
writeStream.on("error", (err) => {
|
|
224
|
+
this.logger.error({
|
|
225
|
+
err,
|
|
226
|
+
fileName
|
|
227
|
+
}, "error writing tarball for @{fileName}: @{err.message}");
|
|
236
228
|
});
|
|
237
|
-
signal
|
|
238
|
-
failed = true;
|
|
239
|
-
if (opened) writeStream.once("close", async () => {
|
|
240
|
-
await this.removeTempFile(temporalName);
|
|
241
|
-
});
|
|
242
|
-
else await this.removeTempFile(temporalName);
|
|
243
|
-
}, { once: true });
|
|
229
|
+
if (signal) (0, node_stream.addAbortSignal)(signal, writeStream);
|
|
244
230
|
return writeStream;
|
|
245
231
|
}
|
|
246
232
|
/**
|
package/build/local-fs.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-fs.js","names":[],"sources":["../src/local-fs.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\nimport sanitzers from 'sanitize-filename';\n\nimport type { VerdaccioError, pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport { readFileNext, unlockFileNext } from '@verdaccio/file-locking';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport {\n accessPromise,\n mkdirPromise,\n openPromise,\n readFilePromise,\n renamePromise,\n rmdirPromise,\n statPromise,\n unlinkPromise,\n writeFilePromise,\n} from './fs';\n\nexport const fileExist = 'EEXISTS';\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport const packageJSONFileName = 'package.json';\n\nexport type ILocalFSPackageManager = pluginUtils.StorageHandler & { path: string };\n\nconst debug = buildDebug('verdaccio:plugin:local-storage:local-fs');\n\nexport const fSError = function (message: string, code = 409): VerdaccioError {\n const err: VerdaccioError = errorUtils.getCode(code, message);\n // FIXME: we should return http-status codes here instead, future improvement\n // @ts-ignore\n err.code = message;\n\n return err;\n};\n\nconst tempFile = function (str): string {\n return `${str}.tmp${String(Math.random()).slice(2)}`;\n};\n\nexport async function renameTmp(src: string, dst: string): Promise<void> {\n debug('rename %s to %s', src, dst);\n try {\n await renamePromise(src, dst);\n } catch (err: any) {\n debug('error rename %s error %s', src, err?.message);\n await unlinkPromise(src);\n }\n}\n\nexport default class LocalFS implements ILocalFSPackageManager {\n public path: string;\n public logger: Logger;\n\n public constructor(path: string, logger: Logger) {\n this.path = path;\n this.logger = logger;\n }\n\n /**\n * This function allows to update the package\n * This function handle the update package logic, for this plugin\n * we need to lock/unlock handlers for thread-safely and then apply\n * the `handleUpdate` and return the result.\n *\n * The lock could fail on several steps so we need to ensure the\n * file does not get locked if the whole process fails.\n *\n Algorithm:\n 1. lock package.json for writing\n 2. read package.json\n 3. apply external update package handler\n 4. return manifest (write is being hanlded into the core)\n 5. rename package.json.tmp package.json\n * @param {*} packageName\n * The update package handler could be different based\n * on the action and handled into the core.\n * @param {*} handleUpdate\n */\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n // this plugin lock files on write, we handle all possible scenarios\n let locked = false;\n let manifestUpdated: Manifest;\n try {\n const manifest = await this._lockAndReadJSON(packageJSONFileName);\n locked = true;\n manifestUpdated = await handleUpdate(manifest);\n if (locked) {\n debug('unlock %s', packageJSONFileName);\n await this._unlockJSON(packageJSONFileName);\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n } else {\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n }\n } catch (err: any) {\n // we ensure lock the file\n this.logger.error(\n { err, packageName },\n 'error on update package @{packageName}: @{err.message}'\n );\n if (locked) {\n try {\n await this._unlockJSON(packageJSONFileName);\n } catch {\n // unlock could fail, we bubble up error\n throw errorUtils.getInternalError('resource temporarily unavailable');\n }\n // after unlock bubble up the original error (e.g. thrown by the handler)\n throw err;\n } else {\n if (err.code === resourceNotAvailable) {\n throw errorUtils.getInternalError('resource temporarily unavailable');\n } else if (err.code === noSuchFile) {\n throw errorUtils.getNotFound();\n } else {\n throw err;\n }\n }\n }\n }\n\n public async deletePackage(packageName: string): Promise<void> {\n debug('delete a file/package %o', packageName);\n\n return await unlinkPromise(this._getStorage(packageName));\n }\n\n public async removePackage(): Promise<void> {\n debug('remove a package folder %o', this.path);\n\n await rmdirPromise(this._getStorage('.'));\n }\n\n /**\n * Verify if the package exists already.\n * @param name package name\n * @returns\n */\n public async hasPackage(): Promise<boolean> {\n const pathName: string = this._getStorage(packageJSONFileName);\n try {\n const stat = await statPromise(pathName);\n return stat.isFile();\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('dir: %o does not exist %s', pathName, err?.code);\n return false;\n } else {\n this.logger.error({ err }, 'error on verify a package exist: @{err.message}');\n throw errorUtils.getInternalError('error on verify a package exist');\n }\n }\n }\n\n /**\n * Create a package in the local storage, if package already exist fails.\n * @param name package name\n * @param manifest package manifest\n */\n public async createPackage(name: string, manifest: Manifest): Promise<void> {\n debug('create a new package %o', name);\n const pathPackage = this._getStorage(packageJSONFileName);\n try {\n // https://nodejs.org/dist/latest-v17.x/docs/api/fs.html#file-system-flags\n // 'wx': Like 'w' but fails if the path exists\n await openPromise(pathPackage, 'wx');\n } catch (err: any) {\n // cannot override a pacakge that already exist\n if (err.code === 'EEXIST') {\n debug('file %o cannot be created, it already exists: %o', name);\n throw fSError(fileExist);\n }\n }\n // Create a new file and it´s folder if does not exist previously\n await this.writeFile(pathPackage, this._convertToString(manifest));\n }\n\n public async savePackage(name: string, value: Manifest): Promise<void> {\n debug('save a package %o', name);\n\n await this.writeFile(this._getStorage(packageJSONFileName), this._convertToString(value));\n }\n\n public async readPackage(name: string): Promise<Manifest> {\n debug('read a package %o', name);\n try {\n const res = await this._readStorageFile(this._getStorage(packageJSONFileName));\n const data: any = JSON.parse(res.toString('utf8'));\n\n debug('read storage file %o has succeeded', name);\n return data;\n } catch (err: any) {\n if (err.code !== noSuchFile) {\n debug('parse error');\n this.logger.error({ err, name }, 'error on parse @{name}: @{err.message}');\n }\n throw err;\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const pathName: string = this._getStorage(fileName);\n return new Promise((resolve) => {\n accessPromise(pathName)\n .then(() => {\n resolve(true);\n })\n .catch(() => resolve(false));\n });\n }\n\n /**\n * Best-effort removal of a temporary file. Error and abort can both try to\n * clean the same file from event listeners, so a missing file is fine and\n * nothing may throw — a rejection here becomes an uncaught exception.\n */\n private async removeTempFile(temporalName): Promise<void> {\n debug('remove temporal file %o', temporalName);\n try {\n await unlinkPromise(temporalName);\n debug('removed temporal file %o', temporalName);\n } catch (err: any) {\n if (err?.code !== 'ENOENT') {\n this.logger.warn(\n { temporalName, err: err.message },\n 'unable to remove temporal file @{temporalName}: @{err}'\n );\n }\n }\n }\n\n /**\n * Write a tarball into the storage\n * @param fileName package name\n * @param param1\n * @returns\n */\n public async writeTarball(fileName: string, { signal }): Promise<Writable> {\n debug('write a tarball %o', fileName);\n const pathName: string = this._getStorage(fileName);\n await this.checkCreateFolder(pathName);\n // create a temporary file to avoid conflicts or prev corruption files\n const temporalName = path.join(\n this.path,\n sanitzers(`${fileName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`)\n );\n\n debug('write a temporal name %o', temporalName);\n let opened = false;\n // an errored or aborted download must never be renamed into place: that\n // would publish a truncated tarball under the final name\n let failed = false;\n const writeStream = fs.createWriteStream(temporalName);\n\n writeStream.on('open', () => {\n opened = true;\n });\n\n writeStream.on('error', async (err) => {\n failed = true;\n if (opened) {\n this.logger.error(\n { err, fileName },\n 'error on open write tarball for @{fileName}: @{err.message}'\n );\n writeStream.once('close', async () => {\n await this.removeTempFile(temporalName);\n });\n } else {\n this.logger.error(\n { err, fileName },\n 'error on writing tarball for @{fileName}: @{err.message}'\n );\n await this.removeTempFile(temporalName);\n }\n });\n\n // the 'close' event is emitted when the stream and any of its\n // underlying resources (a file descriptor, for example) have been closed.\n writeStream.once('close', async () => {\n if (failed) {\n // the error/abort listeners own the cleanup of the temporal file\n return;\n }\n try {\n await renameTmp(temporalName, pathName);\n } catch (err) {\n this.logger.error(\n { err, temporalName, pathName },\n 'error on rename temporal file @{temporalName} to @{pathName}: @{err.message},' +\n 'please report this bug'\n );\n }\n });\n\n // if upload is aborted, we clean up the temporal file\n signal?.addEventListener(\n 'abort',\n async () => {\n failed = true;\n if (opened) {\n // close always happens, even if error\n writeStream.once('close', async () => {\n await this.removeTempFile(temporalName);\n });\n } else {\n await this.removeTempFile(temporalName);\n }\n },\n { once: true }\n );\n\n return writeStream;\n }\n\n /**\n * Read a tarball from the storage\n * @param tarballName tarball name eg: foo-1.0.0.tgz\n * @param options {signal} abort signal\n * @returns Readable stream\n */\n public async readTarball(tarballName: string, { signal }): Promise<Readable> {\n const pathName: string = this._getStorage(tarballName);\n debug('read a tarball %o', pathName);\n const readStream = addAbortSignal(signal, fs.createReadStream(pathName));\n readStream.on('open', function (fileDescriptorId: number) {\n // if abort, the descriptor is null\n debug('file descriptor id %o', fileDescriptorId);\n if (fileDescriptorId) {\n // sync on purpose: the size must be emitted before the first data\n // chunk flushes the response headers (an async fstat races it)\n let size: number | undefined;\n try {\n size = fs.fstatSync(fileDescriptorId).size;\n } catch {\n // the stream 'error' event surfaces any real problem\n }\n if (size !== undefined) {\n debug('file size %o', size);\n readStream.emit('content-length', size);\n }\n }\n });\n readStream.on('error', (error) => {\n debug('no tarball found %o for %s message %s', pathName, tarballName, error.message);\n });\n return readStream;\n }\n\n private async _readStorageFile(name: string): Promise<any> {\n debug('reading the file: %o', name);\n try {\n return await readFilePromise(name);\n } catch (err: any) {\n debug('error reading the file: %o with error %o', name, err.message);\n throw err;\n }\n }\n\n private _convertToString(value: Manifest): string {\n return JSON.stringify(value);\n }\n\n public _getStorage(fileName = ''): string {\n const storagePath: string = path.join(this.path, sanitzers(fileName));\n debug('get storage %s', storagePath);\n return storagePath;\n }\n\n private async writeTempFileAndRename(dest: string, fileContent: string): Promise<any> {\n const tempFilePath = tempFile(dest);\n try {\n // write file on temp location\n await this.checkCreateFolder(tempFilePath);\n await writeFilePromise(tempFilePath, fileContent);\n debug('creating a new file:: %o', dest);\n // rename tmp file to original\n await renameTmp(tempFilePath, dest);\n } catch (err: any) {\n debug('error on write the file: %o', dest);\n throw err;\n }\n }\n\n private async writeFile(destiny: string, fileContent: string): Promise<void> {\n try {\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } catch (err: any) {\n if (err && err.code === noSuchFile) {\n await this.checkCreateFolder(destiny);\n // we try again create the temp file\n debug('writing a temp file %s', destiny);\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } else {\n this.logger.error({ err }, 'error on write file: @{err.message}');\n throw err;\n }\n }\n }\n\n private async checkCreateFolder(pathName: string): Promise<void> {\n // Check if folder exists and create it if not\n const dir = path.dirname(pathName);\n try {\n await accessPromise(dir);\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('folder does not exist %o', dir);\n await mkdirPromise(dir, { recursive: true });\n debug('folder %o created', dir);\n } else {\n debug('error on check folder %o', dir);\n throw err;\n }\n }\n }\n\n private async _lockAndReadJSON(name: string): Promise<Manifest> {\n const fileName: string = this._getStorage(name);\n debug('lock and read a file %o', fileName);\n try {\n const data = await readFileNext<Manifest>(fileName, {\n lock: true,\n parse: true,\n });\n return data;\n } catch (err) {\n this.logger.error({ err }, 'error on lock file: @{err.message}');\n debug('error on lock and read json for file: %o', name);\n\n throw err;\n }\n }\n\n private async _unlockJSON(name: string): Promise<void> {\n await unlockFileNext(this._getStorage(name));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,IAAa,YAAY;AACzB,IAAa,aAAa;AAC1B,IAAa,uBAAuB;AACpC,IAAa,sBAAsB;AAInC,IAAM,WAAA,GAAQ,MAAA,QAAA,CAAW,yCAAyC;AAElE,IAAa,UAAU,SAAU,SAAiB,OAAO,KAAqB;CAC5E,MAAM,MAAsB,gBAAA,WAAW,QAAQ,MAAM,OAAO;CAG5D,IAAI,OAAO;CAEX,OAAO;AACT;AAEA,IAAM,WAAW,SAAU,KAAa;CACtC,OAAO,GAAG,IAAI,MAAM,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD;AAEA,eAAsB,UAAU,KAAa,KAA4B;CACvE,QAAM,mBAAmB,KAAK,GAAG;CACjC,IAAI;EACF,MAAM,WAAA,cAAc,KAAK,GAAG;CAC9B,SAAS,KAAU;EACjB,QAAM,4BAA4B,KAAK,KAAK,OAAO;EACnD,MAAM,WAAA,cAAc,GAAG;CACzB;AACF;AAEA,IAAqB,UAArB,MAA+D;CAC7D;CACA;CAEA,YAAmB,MAAc,QAAgB;EAC/C,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;;;;;;;;;;;;;;;;;;;;;CAsBA,MAAa,cACX,aACA,cACmB;EAEnB,IAAI,SAAS;EACb,IAAI;EACJ,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,iBAAiB,mBAAmB;GAChE,SAAS;GACT,kBAAkB,MAAM,aAAa,QAAQ;GAC7C,IAAI,QAAQ;IACV,QAAM,aAAa,mBAAmB;IACtC,MAAM,KAAK,YAAY,mBAAmB;IAC1C,KAAK,OAAO,MAAM,EAAE,YAAY,GAAG,6CAA6C;IAChF,OAAO;GACT,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,YAAY,GAAG,6CAA6C;IAChF,OAAO;GACT;EACF,SAAS,KAAU;GAEjB,KAAK,OAAO,MACV;IAAE;IAAK;GAAY,GACnB,wDACF;GACA,IAAI,QAAQ;IACV,IAAI;KACF,MAAM,KAAK,YAAY,mBAAmB;IAC5C,QAAQ;KAEN,MAAM,gBAAA,WAAW,iBAAiB,kCAAkC;IACtE;IAEA,MAAM;GACR,OACE,IAAI,IAAI,SAAA,UACN,MAAM,gBAAA,WAAW,iBAAiB,kCAAkC;QAC/D,IAAI,IAAI,SAAA,UACb,MAAM,gBAAA,WAAW,YAAY;QAE7B,MAAM;EAGZ;CACF;CAEA,MAAa,cAAc,aAAoC;EAC7D,QAAM,4BAA4B,WAAW;EAE7C,OAAO,MAAM,WAAA,cAAc,KAAK,YAAY,WAAW,CAAC;CAC1D;CAEA,MAAa,gBAA+B;EAC1C,QAAM,8BAA8B,KAAK,IAAI;EAE7C,MAAM,WAAA,aAAa,KAAK,YAAY,GAAG,CAAC;CAC1C;;;;;;CAOA,MAAa,aAA+B;EAC1C,MAAM,WAAmB,KAAK,YAAY,mBAAmB;EAC7D,IAAI;GAEF,QAAO,MADY,WAAA,YAAY,QAAQ,EAAA,CAC3B,OAAO;EACrB,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,QAAM,6BAA6B,UAAU,KAAK,IAAI;IACtD,OAAO;GACT,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,iDAAiD;IAC5E,MAAM,gBAAA,WAAW,iBAAiB,iCAAiC;GACrE;EACF;CACF;;;;;;CAOA,MAAa,cAAc,MAAc,UAAmC;EAC1E,QAAM,2BAA2B,IAAI;EACrC,MAAM,cAAc,KAAK,YAAY,mBAAmB;EACxD,IAAI;GAGF,MAAM,WAAA,YAAY,aAAa,IAAI;EACrC,SAAS,KAAU;GAEjB,IAAI,IAAI,SAAS,UAAU;IACzB,QAAM,oDAAoD,IAAI;IAC9D,MAAM,QAAQ,SAAS;GACzB;EACF;EAEA,MAAM,KAAK,UAAU,aAAa,KAAK,iBAAiB,QAAQ,CAAC;CACnE;CAEA,MAAa,YAAY,MAAc,OAAgC;EACrE,QAAM,qBAAqB,IAAI;EAE/B,MAAM,KAAK,UAAU,KAAK,YAAY,mBAAmB,GAAG,KAAK,iBAAiB,KAAK,CAAC;CAC1F;CAEA,MAAa,YAAY,MAAiC;EACxD,QAAM,qBAAqB,IAAI;EAC/B,IAAI;GACF,MAAM,MAAM,MAAM,KAAK,iBAAiB,KAAK,YAAY,mBAAmB,CAAC;GAC7E,MAAM,OAAY,KAAK,MAAM,IAAI,SAAS,MAAM,CAAC;GAEjD,QAAM,sCAAsC,IAAI;GAChD,OAAO;EACT,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,QAAM,aAAa;IACnB,KAAK,OAAO,MAAM;KAAE;KAAK;IAAK,GAAG,wCAAwC;GAC3E;GACA,MAAM;EACR;CACF;CAEA,MAAa,WAAW,UAAoC;EAC1D,MAAM,WAAmB,KAAK,YAAY,QAAQ;EAClD,OAAO,IAAI,SAAS,YAAY;GAC9B,WAAA,cAAc,QAAQ,CAAC,CACpB,WAAW;IACV,QAAQ,IAAI;GACd,CAAC,CAAC,CACD,YAAY,QAAQ,KAAK,CAAC;EAC/B,CAAC;CACH;;;;;;CAOA,MAAc,eAAe,cAA6B;EACxD,QAAM,2BAA2B,YAAY;EAC7C,IAAI;GACF,MAAM,WAAA,cAAc,YAAY;GAChC,QAAM,4BAA4B,YAAY;EAChD,SAAS,KAAU;GACjB,IAAI,KAAK,SAAS,UAChB,KAAK,OAAO,KACV;IAAE;IAAc,KAAK,IAAI;GAAQ,GACjC,wDACF;EAEJ;CACF;;;;;;;CAQA,MAAa,aAAa,UAAkB,EAAE,UAA6B;EACzE,QAAM,sBAAsB,QAAQ;EACpC,MAAM,WAAmB,KAAK,YAAY,QAAQ;EAClD,MAAM,KAAK,kBAAkB,QAAQ;EAErC,MAAM,eAAe,UAAA,QAAK,KACxB,KAAK,OAAA,GACL,kBAAA,QAAA,CAAU,GAAG,SAAS,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,EAAE,GAAG,CAC1E;EAEA,QAAM,4BAA4B,YAAY;EAC9C,IAAI,SAAS;EAGb,IAAI,SAAS;EACb,MAAM,cAAc,QAAA,QAAG,kBAAkB,YAAY;EAErD,YAAY,GAAG,cAAc;GAC3B,SAAS;EACX,CAAC;EAED,YAAY,GAAG,SAAS,OAAO,QAAQ;GACrC,SAAS;GACT,IAAI,QAAQ;IACV,KAAK,OAAO,MACV;KAAE;KAAK;IAAS,GAChB,6DACF;IACA,YAAY,KAAK,SAAS,YAAY;KACpC,MAAM,KAAK,eAAe,YAAY;IACxC,CAAC;GACH,OAAO;IACL,KAAK,OAAO,MACV;KAAE;KAAK;IAAS,GAChB,0DACF;IACA,MAAM,KAAK,eAAe,YAAY;GACxC;EACF,CAAC;EAID,YAAY,KAAK,SAAS,YAAY;GACpC,IAAI,QAEF;GAEF,IAAI;IACF,MAAM,UAAU,cAAc,QAAQ;GACxC,SAAS,KAAK;IACZ,KAAK,OAAO,MACV;KAAE;KAAK;KAAc;IAAS,GAC9B,qGAEF;GACF;EACF,CAAC;EAGD,QAAQ,iBACN,SACA,YAAY;GACV,SAAS;GACT,IAAI,QAEF,YAAY,KAAK,SAAS,YAAY;IACpC,MAAM,KAAK,eAAe,YAAY;GACxC,CAAC;QAED,MAAM,KAAK,eAAe,YAAY;EAE1C,GACA,EAAE,MAAM,KAAK,CACf;EAEA,OAAO;CACT;;;;;;;CAQA,MAAa,YAAY,aAAqB,EAAE,UAA6B;EAC3E,MAAM,WAAmB,KAAK,YAAY,WAAW;EACrD,QAAM,qBAAqB,QAAQ;EACnC,MAAM,cAAA,GAAa,YAAA,eAAA,CAAe,QAAQ,QAAA,QAAG,iBAAiB,QAAQ,CAAC;EACvE,WAAW,GAAG,QAAQ,SAAU,kBAA0B;GAExD,QAAM,yBAAyB,gBAAgB;GAC/C,IAAI,kBAAkB;IAGpB,IAAI;IACJ,IAAI;KACF,OAAO,QAAA,QAAG,UAAU,gBAAgB,CAAC,CAAC;IACxC,QAAQ,CAER;IACA,IAAI,SAAS,KAAA,GAAW;KACtB,QAAM,gBAAgB,IAAI;KAC1B,WAAW,KAAK,kBAAkB,IAAI;IACxC;GACF;EACF,CAAC;EACD,WAAW,GAAG,UAAU,UAAU;GAChC,QAAM,yCAAyC,UAAU,aAAa,MAAM,OAAO;EACrF,CAAC;EACD,OAAO;CACT;CAEA,MAAc,iBAAiB,MAA4B;EACzD,QAAM,wBAAwB,IAAI;EAClC,IAAI;GACF,OAAO,MAAM,WAAA,gBAAgB,IAAI;EACnC,SAAS,KAAU;GACjB,QAAM,4CAA4C,MAAM,IAAI,OAAO;GACnE,MAAM;EACR;CACF;CAEA,iBAAyB,OAAyB;EAChD,OAAO,KAAK,UAAU,KAAK;CAC7B;CAEA,YAAmB,WAAW,IAAY;EACxC,MAAM,cAAsB,UAAA,QAAK,KAAK,KAAK,OAAA,GAAM,kBAAA,QAAA,CAAU,QAAQ,CAAC;EACpE,QAAM,kBAAkB,WAAW;EACnC,OAAO;CACT;CAEA,MAAc,uBAAuB,MAAc,aAAmC;EACpF,MAAM,eAAe,SAAS,IAAI;EAClC,IAAI;GAEF,MAAM,KAAK,kBAAkB,YAAY;GACzC,MAAM,WAAA,iBAAiB,cAAc,WAAW;GAChD,QAAM,4BAA4B,IAAI;GAEtC,MAAM,UAAU,cAAc,IAAI;EACpC,SAAS,KAAU;GACjB,QAAM,+BAA+B,IAAI;GACzC,MAAM;EACR;CACF;CAEA,MAAc,UAAU,SAAiB,aAAoC;EAC3E,IAAI;GACF,MAAM,KAAK,uBAAuB,SAAS,WAAW;GACtD,QAAM,yBAAyB,OAAO;EACxC,SAAS,KAAU;GACjB,IAAI,OAAO,IAAI,SAAA,UAAqB;IAClC,MAAM,KAAK,kBAAkB,OAAO;IAEpC,QAAM,0BAA0B,OAAO;IACvC,MAAM,KAAK,uBAAuB,SAAS,WAAW;IACtD,QAAM,yBAAyB,OAAO;GACxC,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,qCAAqC;IAChE,MAAM;GACR;EACF;CACF;CAEA,MAAc,kBAAkB,UAAiC;EAE/D,MAAM,MAAM,UAAA,QAAK,QAAQ,QAAQ;EACjC,IAAI;GACF,MAAM,WAAA,cAAc,GAAG;EACzB,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,QAAM,4BAA4B,GAAG;IACrC,MAAM,WAAA,aAAa,KAAK,EAAE,WAAW,KAAK,CAAC;IAC3C,QAAM,qBAAqB,GAAG;GAChC,OAAO;IACL,QAAM,4BAA4B,GAAG;IACrC,MAAM;GACR;EACF;CACF;CAEA,MAAc,iBAAiB,MAAiC;EAC9D,MAAM,WAAmB,KAAK,YAAY,IAAI;EAC9C,QAAM,2BAA2B,QAAQ;EACzC,IAAI;GAKF,OAAO,OAAA,GAJY,wBAAA,aAAA,CAAuB,UAAU;IAClD,MAAM;IACN,OAAO;GACT,CAAC;EAEH,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,oCAAoC;GAC/D,QAAM,4CAA4C,IAAI;GAEtD,MAAM;EACR;CACF;CAEA,MAAc,YAAY,MAA6B;EACrD,OAAA,GAAM,wBAAA,eAAA,CAAe,KAAK,YAAY,IAAI,CAAC;CAC7C;AACF"}
|
|
1
|
+
{"version":3,"file":"local-fs.js","names":["debug","buildDebug","errorUtils","renamePromise","unlinkPromise","rmdirPromise","statPromise","openPromise","path","sanitzers","fs","addAbortSignal","readFilePromise","writeFilePromise","accessPromise","mkdirPromise","readFileNext","unlockFileNext"],"sources":["../src/local-fs.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\nimport sanitzers from 'sanitize-filename';\n\nimport type { VerdaccioError, pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport { readFileNext, unlockFileNext } from '@verdaccio/file-locking';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport {\n accessPromise,\n mkdirPromise,\n openPromise,\n readFilePromise,\n renamePromise,\n rmdirPromise,\n statPromise,\n unlinkPromise,\n writeFilePromise,\n} from './fs';\n\nexport const fileExist = 'EEXISTS';\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport const packageJSONFileName = 'package.json';\n\nexport type ILocalFSPackageManager = pluginUtils.StorageHandler & { path: string };\n\nconst debug = buildDebug('verdaccio:plugin:local-storage:local-fs');\n\nexport const fSError = function (message: string, code = 409): VerdaccioError {\n const err: VerdaccioError = errorUtils.getCode(code, message);\n // FIXME: we should return http-status codes here instead, future improvement\n // @ts-ignore\n err.code = message;\n\n return err;\n};\n\nconst tempFile = function (str): string {\n return `${str}.tmp${String(Math.random()).slice(2)}`;\n};\n\nexport async function renameTmp(src: string, dst: string): Promise<void> {\n debug('rename %s to %s', src, dst);\n try {\n await renamePromise(src, dst);\n } catch (err: any) {\n debug('error rename %s error %s', src, err?.message);\n await unlinkPromise(src);\n }\n}\n\nexport default class LocalFS implements ILocalFSPackageManager {\n public path: string;\n public logger: Logger;\n\n public constructor(path: string, logger: Logger) {\n this.path = path;\n this.logger = logger;\n }\n\n /**\n * This function allows to update the package\n * This function handle the update package logic, for this plugin\n * we need to lock/unlock handlers for thread-safely and then apply\n * the `handleUpdate` and return the result.\n *\n * The lock could fail on several steps so we need to ensure the\n * file does not get locked if the whole process fails.\n *\n Algorithm:\n 1. lock package.json for writing\n 2. read package.json\n 3. apply external update package handler\n 4. return manifest (write is being hanlded into the core)\n 5. rename package.json.tmp package.json\n * @param {*} packageName\n * The update package handler could be different based\n * on the action and handled into the core.\n * @param {*} handleUpdate\n */\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n // this plugin lock files on write, we handle all possible scenarios\n let locked = false;\n let manifestUpdated: Manifest;\n try {\n const manifest = await this._lockAndReadJSON(packageJSONFileName);\n locked = true;\n manifestUpdated = await handleUpdate(manifest);\n if (locked) {\n debug('unlock %s', packageJSONFileName);\n await this._unlockJSON(packageJSONFileName);\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n } else {\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n }\n } catch (err: any) {\n // we ensure lock the file\n this.logger.error(\n { err, packageName },\n 'error on update package @{packageName}: @{err.message}'\n );\n if (locked) {\n try {\n await this._unlockJSON(packageJSONFileName);\n } catch {\n // unlock could fail, we bubble up error\n throw errorUtils.getInternalError('resource temporarily unavailable');\n }\n // after unlock bubble up the original error (e.g. thrown by the handler)\n throw err;\n } else {\n if (err.code === resourceNotAvailable) {\n throw errorUtils.getInternalError('resource temporarily unavailable');\n } else if (err.code === noSuchFile) {\n throw errorUtils.getNotFound();\n } else {\n throw err;\n }\n }\n }\n }\n\n public async deletePackage(packageName: string): Promise<void> {\n debug('delete a file/package %o', packageName);\n\n return await unlinkPromise(this._getStorage(packageName));\n }\n\n public async removePackage(): Promise<void> {\n debug('remove a package folder %o', this.path);\n\n await rmdirPromise(this._getStorage('.'));\n }\n\n /**\n * Verify if the package exists already.\n * @param name package name\n * @returns\n */\n public async hasPackage(): Promise<boolean> {\n const pathName: string = this._getStorage(packageJSONFileName);\n try {\n const stat = await statPromise(pathName);\n return stat.isFile();\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('dir: %o does not exist %s', pathName, err?.code);\n return false;\n } else {\n this.logger.error({ err }, 'error on verify a package exist: @{err.message}');\n throw errorUtils.getInternalError('error on verify a package exist');\n }\n }\n }\n\n /**\n * Create a package in the local storage, if package already exist fails.\n * @param name package name\n * @param manifest package manifest\n */\n public async createPackage(name: string, manifest: Manifest): Promise<void> {\n debug('create a new package %o', name);\n const pathPackage = this._getStorage(packageJSONFileName);\n try {\n // https://nodejs.org/dist/latest-v17.x/docs/api/fs.html#file-system-flags\n // 'wx': Like 'w' but fails if the path exists\n await openPromise(pathPackage, 'wx');\n } catch (err: any) {\n // cannot override a pacakge that already exist\n if (err.code === 'EEXIST') {\n debug('file %o cannot be created, it already exists: %o', name);\n throw fSError(fileExist);\n }\n }\n // Create a new file and it´s folder if does not exist previously\n await this.writeFile(pathPackage, this._convertToString(manifest));\n }\n\n public async savePackage(name: string, value: Manifest): Promise<void> {\n debug('save a package %o', name);\n\n await this.writeFile(this._getStorage(packageJSONFileName), this._convertToString(value));\n }\n\n public async readPackage(name: string): Promise<Manifest> {\n debug('read a package %o', name);\n try {\n const res = await this._readStorageFile(this._getStorage(packageJSONFileName));\n const data: any = JSON.parse(res.toString('utf8'));\n\n debug('read storage file %o has succeeded', name);\n return data;\n } catch (err: any) {\n if (err.code !== noSuchFile) {\n debug('parse error');\n this.logger.error({ err, name }, 'error on parse @{name}: @{err.message}');\n }\n throw err;\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const pathName: string = this._getStorage(fileName);\n return new Promise((resolve) => {\n accessPromise(pathName)\n .then(() => {\n resolve(true);\n })\n .catch(() => resolve(false));\n });\n }\n\n /**\n * Best-effort removal of a temporary file. Error and abort can both try to\n * clean the same file from event listeners, so a missing file is fine and\n * nothing may throw — a rejection here becomes an uncaught exception.\n */\n private async removeTempFile(temporalName): Promise<void> {\n debug('remove temporal file %o', temporalName);\n try {\n await unlinkPromise(temporalName);\n debug('removed temporal file %o', temporalName);\n } catch (err: any) {\n if (err?.code !== 'ENOENT') {\n this.logger.warn(\n { temporalName, err: err.message },\n 'unable to remove temporal file @{temporalName}: @{err}'\n );\n }\n }\n }\n\n /**\n * Write a tarball into the storage\n * @param fileName package name\n * @param param1\n * @returns\n */\n public async writeTarball(fileName: string, { signal }): Promise<Writable> {\n debug('write a tarball %o', fileName);\n const pathName: string = this._getStorage(fileName);\n await this.checkCreateFolder(pathName);\n // create a temporary file to avoid conflicts or prev corruption files\n const temporalName = path.join(\n this.path,\n sanitzers(`${fileName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`)\n );\n\n debug('write a temporal name %o', temporalName);\n const finalize = async (closeError: NodeJS.ErrnoException | null): Promise<void> => {\n if (closeError || writeStream.errored || !writeStream.writableFinished) {\n // An opening write may create the file after cancellation. Its\n // descriptor must be closed before removing the temporary file.\n await this.removeTempFile(temporalName);\n return;\n }\n try {\n // A failed rename must reject the upload: the final tarball is not\n // available to readers.\n await renamePromise(temporalName, pathName);\n } catch (err) {\n await this.removeTempFile(temporalName);\n throw err;\n }\n };\n const writeStream: fs.WriteStream = fs.createWriteStream(temporalName, {\n fs: {\n open: fs.open,\n write: fs.write,\n writev: fs.writev,\n close(fd, callback) {\n fs.close(fd, (err) => {\n // Include finalization in stream destruction. An async 'close'\n // listener would let pipeline and Store finish before the rename.\n finalize(err).then(() => callback(err), callback);\n });\n },\n },\n });\n\n writeStream.on('error', (err) => {\n this.logger.error({ err, fileName }, 'error writing tarball for @{fileName}: @{err.message}');\n });\n\n if (signal) {\n addAbortSignal(signal, writeStream);\n }\n\n return writeStream;\n }\n\n /**\n * Read a tarball from the storage\n * @param tarballName tarball name eg: foo-1.0.0.tgz\n * @param options {signal} abort signal\n * @returns Readable stream\n */\n public async readTarball(tarballName: string, { signal }): Promise<Readable> {\n const pathName: string = this._getStorage(tarballName);\n debug('read a tarball %o', pathName);\n const readStream = addAbortSignal(signal, fs.createReadStream(pathName));\n readStream.on('open', function (fileDescriptorId: number) {\n // if abort, the descriptor is null\n debug('file descriptor id %o', fileDescriptorId);\n if (fileDescriptorId) {\n // sync on purpose: the size must be emitted before the first data\n // chunk flushes the response headers (an async fstat races it)\n let size: number | undefined;\n try {\n size = fs.fstatSync(fileDescriptorId).size;\n } catch {\n // the stream 'error' event surfaces any real problem\n }\n if (size !== undefined) {\n debug('file size %o', size);\n readStream.emit('content-length', size);\n }\n }\n });\n readStream.on('error', (error) => {\n debug('no tarball found %o for %s message %s', pathName, tarballName, error.message);\n });\n return readStream;\n }\n\n private async _readStorageFile(name: string): Promise<any> {\n debug('reading the file: %o', name);\n try {\n return await readFilePromise(name);\n } catch (err: any) {\n debug('error reading the file: %o with error %o', name, err.message);\n throw err;\n }\n }\n\n private _convertToString(value: Manifest): string {\n return JSON.stringify(value);\n }\n\n public _getStorage(fileName = ''): string {\n const storagePath: string = path.join(this.path, sanitzers(fileName));\n debug('get storage %s', storagePath);\n return storagePath;\n }\n\n private async writeTempFileAndRename(dest: string, fileContent: string): Promise<any> {\n const tempFilePath = tempFile(dest);\n try {\n // write file on temp location\n await this.checkCreateFolder(tempFilePath);\n await writeFilePromise(tempFilePath, fileContent);\n debug('creating a new file:: %o', dest);\n // rename tmp file to original\n await renameTmp(tempFilePath, dest);\n } catch (err: any) {\n debug('error on write the file: %o', dest);\n throw err;\n }\n }\n\n private async writeFile(destiny: string, fileContent: string): Promise<void> {\n try {\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } catch (err: any) {\n if (err && err.code === noSuchFile) {\n await this.checkCreateFolder(destiny);\n // we try again create the temp file\n debug('writing a temp file %s', destiny);\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } else {\n this.logger.error({ err }, 'error on write file: @{err.message}');\n throw err;\n }\n }\n }\n\n private async checkCreateFolder(pathName: string): Promise<void> {\n // Check if folder exists and create it if not\n const dir = path.dirname(pathName);\n try {\n await accessPromise(dir);\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('folder does not exist %o', dir);\n await mkdirPromise(dir, { recursive: true });\n debug('folder %o created', dir);\n } else {\n debug('error on check folder %o', dir);\n throw err;\n }\n }\n }\n\n private async _lockAndReadJSON(name: string): Promise<Manifest> {\n const fileName: string = this._getStorage(name);\n debug('lock and read a file %o', fileName);\n try {\n const data = await readFileNext<Manifest>(fileName, {\n lock: true,\n parse: true,\n });\n return data;\n } catch (err) {\n this.logger.error({ err }, 'error on lock file: @{err.message}');\n debug('error on lock and read json for file: %o', name);\n\n throw err;\n }\n }\n\n private async _unlockJSON(name: string): Promise<void> {\n await unlockFileNext(this._getStorage(name));\n }\n}\n"],"mappings":";;;;;;;;;;;;;;AAwBA,IAAa,YAAY;AACzB,IAAa,aAAa;AAC1B,IAAa,uBAAuB;AACpC,IAAa,sBAAsB;AAInC,IAAMA,WAAAA,GAAQC,MAAAA,QAAAA,CAAW,yCAAyC;AAElE,IAAa,UAAU,SAAU,SAAiB,OAAO,KAAqB;CAC5E,MAAM,MAAsBC,gBAAAA,WAAW,QAAQ,MAAM,OAAO;CAG5D,IAAI,OAAO;CAEX,OAAO;AACT;AAEA,IAAM,WAAW,SAAU,KAAa;CACtC,OAAO,GAAG,IAAI,MAAM,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD;AAEA,eAAsB,UAAU,KAAa,KAA4B;CACvE,QAAM,mBAAmB,KAAK,GAAG;CACjC,IAAI;EACF,MAAMC,WAAAA,cAAc,KAAK,GAAG;CAC9B,SAAS,KAAU;EACjB,QAAM,4BAA4B,KAAK,KAAK,OAAO;EACnD,MAAMC,WAAAA,cAAc,GAAG;CACzB;AACF;AAEA,IAAqB,UAArB,MAA+D;CAC7D;CACA;CAEA,YAAmB,MAAc,QAAgB;EAC/C,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;;;;;;;;;;;;;;;;;;;;;CAsBA,MAAa,cACX,aACA,cACmB;EAEnB,IAAI,SAAS;EACb,IAAI;EACJ,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,iBAAiB,mBAAmB;GAChE,SAAS;GACT,kBAAkB,MAAM,aAAa,QAAQ;GAC7C,IAAI,QAAQ;IACV,QAAM,aAAa,mBAAmB;IACtC,MAAM,KAAK,YAAY,mBAAmB;IAC1C,KAAK,OAAO,MAAM,EAAE,YAAY,GAAG,6CAA6C;IAChF,OAAO;GACT,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,YAAY,GAAG,6CAA6C;IAChF,OAAO;GACT;EACF,SAAS,KAAU;GAEjB,KAAK,OAAO,MACV;IAAE;IAAK;GAAY,GACnB,wDACF;GACA,IAAI,QAAQ;IACV,IAAI;KACF,MAAM,KAAK,YAAY,mBAAmB;IAC5C,QAAQ;KAEN,MAAMF,gBAAAA,WAAW,iBAAiB,kCAAkC;IACtE;IAEA,MAAM;GACR,OACE,IAAI,IAAI,SAAA,UACN,MAAMA,gBAAAA,WAAW,iBAAiB,kCAAkC;QAC/D,IAAI,IAAI,SAAA,UACb,MAAMA,gBAAAA,WAAW,YAAY;QAE7B,MAAM;EAGZ;CACF;CAEA,MAAa,cAAc,aAAoC;EAC7D,QAAM,4BAA4B,WAAW;EAE7C,OAAO,MAAME,WAAAA,cAAc,KAAK,YAAY,WAAW,CAAC;CAC1D;CAEA,MAAa,gBAA+B;EAC1C,QAAM,8BAA8B,KAAK,IAAI;EAE7C,MAAMC,WAAAA,aAAa,KAAK,YAAY,GAAG,CAAC;CAC1C;;;;;;CAOA,MAAa,aAA+B;EAC1C,MAAM,WAAmB,KAAK,YAAY,mBAAmB;EAC7D,IAAI;GAEF,QAAO,MADYC,WAAAA,YAAY,QAAQ,EAAA,CAC3B,OAAO;EACrB,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,QAAM,6BAA6B,UAAU,KAAK,IAAI;IACtD,OAAO;GACT,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,iDAAiD;IAC5E,MAAMJ,gBAAAA,WAAW,iBAAiB,iCAAiC;GACrE;EACF;CACF;;;;;;CAOA,MAAa,cAAc,MAAc,UAAmC;EAC1E,QAAM,2BAA2B,IAAI;EACrC,MAAM,cAAc,KAAK,YAAY,mBAAmB;EACxD,IAAI;GAGF,MAAMK,WAAAA,YAAY,aAAa,IAAI;EACrC,SAAS,KAAU;GAEjB,IAAI,IAAI,SAAS,UAAU;IACzB,QAAM,oDAAoD,IAAI;IAC9D,MAAM,QAAQ,SAAS;GACzB;EACF;EAEA,MAAM,KAAK,UAAU,aAAa,KAAK,iBAAiB,QAAQ,CAAC;CACnE;CAEA,MAAa,YAAY,MAAc,OAAgC;EACrE,QAAM,qBAAqB,IAAI;EAE/B,MAAM,KAAK,UAAU,KAAK,YAAY,mBAAmB,GAAG,KAAK,iBAAiB,KAAK,CAAC;CAC1F;CAEA,MAAa,YAAY,MAAiC;EACxD,QAAM,qBAAqB,IAAI;EAC/B,IAAI;GACF,MAAM,MAAM,MAAM,KAAK,iBAAiB,KAAK,YAAY,mBAAmB,CAAC;GAC7E,MAAM,OAAY,KAAK,MAAM,IAAI,SAAS,MAAM,CAAC;GAEjD,QAAM,sCAAsC,IAAI;GAChD,OAAO;EACT,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,QAAM,aAAa;IACnB,KAAK,OAAO,MAAM;KAAE;KAAK;IAAK,GAAG,wCAAwC;GAC3E;GACA,MAAM;EACR;CACF;CAEA,MAAa,WAAW,UAAoC;EAC1D,MAAM,WAAmB,KAAK,YAAY,QAAQ;EAClD,OAAO,IAAI,SAAS,YAAY;GAC9B,WAAA,cAAc,QAAQ,CAAC,CACpB,WAAW;IACV,QAAQ,IAAI;GACd,CAAC,CAAC,CACD,YAAY,QAAQ,KAAK,CAAC;EAC/B,CAAC;CACH;;;;;;CAOA,MAAc,eAAe,cAA6B;EACxD,QAAM,2BAA2B,YAAY;EAC7C,IAAI;GACF,MAAMH,WAAAA,cAAc,YAAY;GAChC,QAAM,4BAA4B,YAAY;EAChD,SAAS,KAAU;GACjB,IAAI,KAAK,SAAS,UAChB,KAAK,OAAO,KACV;IAAE;IAAc,KAAK,IAAI;GAAQ,GACjC,wDACF;EAEJ;CACF;;;;;;;CAQA,MAAa,aAAa,UAAkB,EAAE,UAA6B;EACzE,QAAM,sBAAsB,QAAQ;EACpC,MAAM,WAAmB,KAAK,YAAY,QAAQ;EAClD,MAAM,KAAK,kBAAkB,QAAQ;EAErC,MAAM,eAAeI,UAAAA,QAAK,KACxB,KAAK,OAAA,GACLC,kBAAAA,QAAAA,CAAU,GAAG,SAAS,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,EAAE,GAAG,CAC1E;EAEA,QAAM,4BAA4B,YAAY;EAC9C,MAAM,WAAW,OAAO,eAA4D;GAClF,IAAI,cAAc,YAAY,WAAW,CAAC,YAAY,kBAAkB;IAGtE,MAAM,KAAK,eAAe,YAAY;IACtC;GACF;GACA,IAAI;IAGF,MAAMN,WAAAA,cAAc,cAAc,QAAQ;GAC5C,SAAS,KAAK;IACZ,MAAM,KAAK,eAAe,YAAY;IACtC,MAAM;GACR;EACF;EACA,MAAM,cAA8BO,QAAAA,QAAG,kBAAkB,cAAc,EACrE,IAAI;GACF,MAAMA,QAAAA,QAAG;GACT,OAAOA,QAAAA,QAAG;GACV,QAAQA,QAAAA,QAAG;GACX,MAAM,IAAI,UAAU;IAClB,QAAA,QAAG,MAAM,KAAK,QAAQ;KAGpB,SAAS,GAAG,CAAC,CAAC,WAAW,SAAS,GAAG,GAAG,QAAQ;IAClD,CAAC;GACH;EACF,EACF,CAAC;EAED,YAAY,GAAG,UAAU,QAAQ;GAC/B,KAAK,OAAO,MAAM;IAAE;IAAK;GAAS,GAAG,uDAAuD;EAC9F,CAAC;EAED,IAAI,QACF,CAAA,GAAA,YAAA,eAAA,CAAe,QAAQ,WAAW;EAGpC,OAAO;CACT;;;;;;;CAQA,MAAa,YAAY,aAAqB,EAAE,UAA6B;EAC3E,MAAM,WAAmB,KAAK,YAAY,WAAW;EACrD,QAAM,qBAAqB,QAAQ;EACnC,MAAM,cAAA,GAAaC,YAAAA,eAAAA,CAAe,QAAQD,QAAAA,QAAG,iBAAiB,QAAQ,CAAC;EACvE,WAAW,GAAG,QAAQ,SAAU,kBAA0B;GAExD,QAAM,yBAAyB,gBAAgB;GAC/C,IAAI,kBAAkB;IAGpB,IAAI;IACJ,IAAI;KACF,OAAOA,QAAAA,QAAG,UAAU,gBAAgB,CAAC,CAAC;IACxC,QAAQ,CAER;IACA,IAAI,SAAS,KAAA,GAAW;KACtB,QAAM,gBAAgB,IAAI;KAC1B,WAAW,KAAK,kBAAkB,IAAI;IACxC;GACF;EACF,CAAC;EACD,WAAW,GAAG,UAAU,UAAU;GAChC,QAAM,yCAAyC,UAAU,aAAa,MAAM,OAAO;EACrF,CAAC;EACD,OAAO;CACT;CAEA,MAAc,iBAAiB,MAA4B;EACzD,QAAM,wBAAwB,IAAI;EAClC,IAAI;GACF,OAAO,MAAME,WAAAA,gBAAgB,IAAI;EACnC,SAAS,KAAU;GACjB,QAAM,4CAA4C,MAAM,IAAI,OAAO;GACnE,MAAM;EACR;CACF;CAEA,iBAAyB,OAAyB;EAChD,OAAO,KAAK,UAAU,KAAK;CAC7B;CAEA,YAAmB,WAAW,IAAY;EACxC,MAAM,cAAsBJ,UAAAA,QAAK,KAAK,KAAK,OAAA,GAAMC,kBAAAA,QAAAA,CAAU,QAAQ,CAAC;EACpE,QAAM,kBAAkB,WAAW;EACnC,OAAO;CACT;CAEA,MAAc,uBAAuB,MAAc,aAAmC;EACpF,MAAM,eAAe,SAAS,IAAI;EAClC,IAAI;GAEF,MAAM,KAAK,kBAAkB,YAAY;GACzC,MAAMI,WAAAA,iBAAiB,cAAc,WAAW;GAChD,QAAM,4BAA4B,IAAI;GAEtC,MAAM,UAAU,cAAc,IAAI;EACpC,SAAS,KAAU;GACjB,QAAM,+BAA+B,IAAI;GACzC,MAAM;EACR;CACF;CAEA,MAAc,UAAU,SAAiB,aAAoC;EAC3E,IAAI;GACF,MAAM,KAAK,uBAAuB,SAAS,WAAW;GACtD,QAAM,yBAAyB,OAAO;EACxC,SAAS,KAAU;GACjB,IAAI,OAAO,IAAI,SAAA,UAAqB;IAClC,MAAM,KAAK,kBAAkB,OAAO;IAEpC,QAAM,0BAA0B,OAAO;IACvC,MAAM,KAAK,uBAAuB,SAAS,WAAW;IACtD,QAAM,yBAAyB,OAAO;GACxC,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,qCAAqC;IAChE,MAAM;GACR;EACF;CACF;CAEA,MAAc,kBAAkB,UAAiC;EAE/D,MAAM,MAAML,UAAAA,QAAK,QAAQ,QAAQ;EACjC,IAAI;GACF,MAAMM,WAAAA,cAAc,GAAG;EACzB,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,QAAM,4BAA4B,GAAG;IACrC,MAAMC,WAAAA,aAAa,KAAK,EAAE,WAAW,KAAK,CAAC;IAC3C,QAAM,qBAAqB,GAAG;GAChC,OAAO;IACL,QAAM,4BAA4B,GAAG;IACrC,MAAM;GACR;EACF;CACF;CAEA,MAAc,iBAAiB,MAAiC;EAC9D,MAAM,WAAmB,KAAK,YAAY,IAAI;EAC9C,QAAM,2BAA2B,QAAQ;EACzC,IAAI;GAKF,OAAO,OAAA,GAJYC,wBAAAA,aAAAA,CAAuB,UAAU;IAClD,MAAM;IACN,OAAO;GACT,CAAC;EAEH,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,oCAAoC;GAC/D,QAAM,4CAA4C,IAAI;GAEtD,MAAM;EACR;CACF;CAEA,MAAc,YAAY,MAA6B;EACrD,OAAA,GAAMC,wBAAAA,eAAAA,CAAe,KAAK,YAAY,IAAI,CAAC;CAC7C;AACF"}
|
package/build/local-fs.mjs
CHANGED
|
@@ -193,49 +193,35 @@ var LocalFS = class {
|
|
|
193
193
|
await this.checkCreateFolder(pathName);
|
|
194
194
|
const temporalName = path.join(this.path, sanitzers(`${fileName}.tmp-${String(Math.random()).replace(/^0\./, "")}`));
|
|
195
195
|
debug("write a temporal name %o", temporalName);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const writeStream = fsCallback.createWriteStream(temporalName);
|
|
199
|
-
writeStream.on("open", () => {
|
|
200
|
-
opened = true;
|
|
201
|
-
});
|
|
202
|
-
writeStream.on("error", async (err) => {
|
|
203
|
-
failed = true;
|
|
204
|
-
if (opened) {
|
|
205
|
-
this.logger.error({
|
|
206
|
-
err,
|
|
207
|
-
fileName
|
|
208
|
-
}, "error on open write tarball for @{fileName}: @{err.message}");
|
|
209
|
-
writeStream.once("close", async () => {
|
|
210
|
-
await this.removeTempFile(temporalName);
|
|
211
|
-
});
|
|
212
|
-
} else {
|
|
213
|
-
this.logger.error({
|
|
214
|
-
err,
|
|
215
|
-
fileName
|
|
216
|
-
}, "error on writing tarball for @{fileName}: @{err.message}");
|
|
196
|
+
const finalize = async (closeError) => {
|
|
197
|
+
if (closeError || writeStream.errored || !writeStream.writableFinished) {
|
|
217
198
|
await this.removeTempFile(temporalName);
|
|
199
|
+
return;
|
|
218
200
|
}
|
|
219
|
-
});
|
|
220
|
-
writeStream.once("close", async () => {
|
|
221
|
-
if (failed) return;
|
|
222
201
|
try {
|
|
223
|
-
await
|
|
202
|
+
await renamePromise(temporalName, pathName);
|
|
224
203
|
} catch (err) {
|
|
225
|
-
this.
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
204
|
+
await this.removeTempFile(temporalName);
|
|
205
|
+
throw err;
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
const writeStream = fsCallback.createWriteStream(temporalName, { fs: {
|
|
209
|
+
open: fsCallback.open,
|
|
210
|
+
write: fsCallback.write,
|
|
211
|
+
writev: fsCallback.writev,
|
|
212
|
+
close(fd, callback) {
|
|
213
|
+
fsCallback.close(fd, (err) => {
|
|
214
|
+
finalize(err).then(() => callback(err), callback);
|
|
215
|
+
});
|
|
230
216
|
}
|
|
217
|
+
} });
|
|
218
|
+
writeStream.on("error", (err) => {
|
|
219
|
+
this.logger.error({
|
|
220
|
+
err,
|
|
221
|
+
fileName
|
|
222
|
+
}, "error writing tarball for @{fileName}: @{err.message}");
|
|
231
223
|
});
|
|
232
|
-
signal
|
|
233
|
-
failed = true;
|
|
234
|
-
if (opened) writeStream.once("close", async () => {
|
|
235
|
-
await this.removeTempFile(temporalName);
|
|
236
|
-
});
|
|
237
|
-
else await this.removeTempFile(temporalName);
|
|
238
|
-
}, { once: true });
|
|
224
|
+
if (signal) addAbortSignal(signal, writeStream);
|
|
239
225
|
return writeStream;
|
|
240
226
|
}
|
|
241
227
|
/**
|
package/build/local-fs.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"local-fs.mjs","names":[],"sources":["../src/local-fs.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\nimport sanitzers from 'sanitize-filename';\n\nimport type { VerdaccioError, pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport { readFileNext, unlockFileNext } from '@verdaccio/file-locking';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport {\n accessPromise,\n mkdirPromise,\n openPromise,\n readFilePromise,\n renamePromise,\n rmdirPromise,\n statPromise,\n unlinkPromise,\n writeFilePromise,\n} from './fs';\n\nexport const fileExist = 'EEXISTS';\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport const packageJSONFileName = 'package.json';\n\nexport type ILocalFSPackageManager = pluginUtils.StorageHandler & { path: string };\n\nconst debug = buildDebug('verdaccio:plugin:local-storage:local-fs');\n\nexport const fSError = function (message: string, code = 409): VerdaccioError {\n const err: VerdaccioError = errorUtils.getCode(code, message);\n // FIXME: we should return http-status codes here instead, future improvement\n // @ts-ignore\n err.code = message;\n\n return err;\n};\n\nconst tempFile = function (str): string {\n return `${str}.tmp${String(Math.random()).slice(2)}`;\n};\n\nexport async function renameTmp(src: string, dst: string): Promise<void> {\n debug('rename %s to %s', src, dst);\n try {\n await renamePromise(src, dst);\n } catch (err: any) {\n debug('error rename %s error %s', src, err?.message);\n await unlinkPromise(src);\n }\n}\n\nexport default class LocalFS implements ILocalFSPackageManager {\n public path: string;\n public logger: Logger;\n\n public constructor(path: string, logger: Logger) {\n this.path = path;\n this.logger = logger;\n }\n\n /**\n * This function allows to update the package\n * This function handle the update package logic, for this plugin\n * we need to lock/unlock handlers for thread-safely and then apply\n * the `handleUpdate` and return the result.\n *\n * The lock could fail on several steps so we need to ensure the\n * file does not get locked if the whole process fails.\n *\n Algorithm:\n 1. lock package.json for writing\n 2. read package.json\n 3. apply external update package handler\n 4. return manifest (write is being hanlded into the core)\n 5. rename package.json.tmp package.json\n * @param {*} packageName\n * The update package handler could be different based\n * on the action and handled into the core.\n * @param {*} handleUpdate\n */\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n // this plugin lock files on write, we handle all possible scenarios\n let locked = false;\n let manifestUpdated: Manifest;\n try {\n const manifest = await this._lockAndReadJSON(packageJSONFileName);\n locked = true;\n manifestUpdated = await handleUpdate(manifest);\n if (locked) {\n debug('unlock %s', packageJSONFileName);\n await this._unlockJSON(packageJSONFileName);\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n } else {\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n }\n } catch (err: any) {\n // we ensure lock the file\n this.logger.error(\n { err, packageName },\n 'error on update package @{packageName}: @{err.message}'\n );\n if (locked) {\n try {\n await this._unlockJSON(packageJSONFileName);\n } catch {\n // unlock could fail, we bubble up error\n throw errorUtils.getInternalError('resource temporarily unavailable');\n }\n // after unlock bubble up the original error (e.g. thrown by the handler)\n throw err;\n } else {\n if (err.code === resourceNotAvailable) {\n throw errorUtils.getInternalError('resource temporarily unavailable');\n } else if (err.code === noSuchFile) {\n throw errorUtils.getNotFound();\n } else {\n throw err;\n }\n }\n }\n }\n\n public async deletePackage(packageName: string): Promise<void> {\n debug('delete a file/package %o', packageName);\n\n return await unlinkPromise(this._getStorage(packageName));\n }\n\n public async removePackage(): Promise<void> {\n debug('remove a package folder %o', this.path);\n\n await rmdirPromise(this._getStorage('.'));\n }\n\n /**\n * Verify if the package exists already.\n * @param name package name\n * @returns\n */\n public async hasPackage(): Promise<boolean> {\n const pathName: string = this._getStorage(packageJSONFileName);\n try {\n const stat = await statPromise(pathName);\n return stat.isFile();\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('dir: %o does not exist %s', pathName, err?.code);\n return false;\n } else {\n this.logger.error({ err }, 'error on verify a package exist: @{err.message}');\n throw errorUtils.getInternalError('error on verify a package exist');\n }\n }\n }\n\n /**\n * Create a package in the local storage, if package already exist fails.\n * @param name package name\n * @param manifest package manifest\n */\n public async createPackage(name: string, manifest: Manifest): Promise<void> {\n debug('create a new package %o', name);\n const pathPackage = this._getStorage(packageJSONFileName);\n try {\n // https://nodejs.org/dist/latest-v17.x/docs/api/fs.html#file-system-flags\n // 'wx': Like 'w' but fails if the path exists\n await openPromise(pathPackage, 'wx');\n } catch (err: any) {\n // cannot override a pacakge that already exist\n if (err.code === 'EEXIST') {\n debug('file %o cannot be created, it already exists: %o', name);\n throw fSError(fileExist);\n }\n }\n // Create a new file and it´s folder if does not exist previously\n await this.writeFile(pathPackage, this._convertToString(manifest));\n }\n\n public async savePackage(name: string, value: Manifest): Promise<void> {\n debug('save a package %o', name);\n\n await this.writeFile(this._getStorage(packageJSONFileName), this._convertToString(value));\n }\n\n public async readPackage(name: string): Promise<Manifest> {\n debug('read a package %o', name);\n try {\n const res = await this._readStorageFile(this._getStorage(packageJSONFileName));\n const data: any = JSON.parse(res.toString('utf8'));\n\n debug('read storage file %o has succeeded', name);\n return data;\n } catch (err: any) {\n if (err.code !== noSuchFile) {\n debug('parse error');\n this.logger.error({ err, name }, 'error on parse @{name}: @{err.message}');\n }\n throw err;\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const pathName: string = this._getStorage(fileName);\n return new Promise((resolve) => {\n accessPromise(pathName)\n .then(() => {\n resolve(true);\n })\n .catch(() => resolve(false));\n });\n }\n\n /**\n * Best-effort removal of a temporary file. Error and abort can both try to\n * clean the same file from event listeners, so a missing file is fine and\n * nothing may throw — a rejection here becomes an uncaught exception.\n */\n private async removeTempFile(temporalName): Promise<void> {\n debug('remove temporal file %o', temporalName);\n try {\n await unlinkPromise(temporalName);\n debug('removed temporal file %o', temporalName);\n } catch (err: any) {\n if (err?.code !== 'ENOENT') {\n this.logger.warn(\n { temporalName, err: err.message },\n 'unable to remove temporal file @{temporalName}: @{err}'\n );\n }\n }\n }\n\n /**\n * Write a tarball into the storage\n * @param fileName package name\n * @param param1\n * @returns\n */\n public async writeTarball(fileName: string, { signal }): Promise<Writable> {\n debug('write a tarball %o', fileName);\n const pathName: string = this._getStorage(fileName);\n await this.checkCreateFolder(pathName);\n // create a temporary file to avoid conflicts or prev corruption files\n const temporalName = path.join(\n this.path,\n sanitzers(`${fileName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`)\n );\n\n debug('write a temporal name %o', temporalName);\n let opened = false;\n // an errored or aborted download must never be renamed into place: that\n // would publish a truncated tarball under the final name\n let failed = false;\n const writeStream = fs.createWriteStream(temporalName);\n\n writeStream.on('open', () => {\n opened = true;\n });\n\n writeStream.on('error', async (err) => {\n failed = true;\n if (opened) {\n this.logger.error(\n { err, fileName },\n 'error on open write tarball for @{fileName}: @{err.message}'\n );\n writeStream.once('close', async () => {\n await this.removeTempFile(temporalName);\n });\n } else {\n this.logger.error(\n { err, fileName },\n 'error on writing tarball for @{fileName}: @{err.message}'\n );\n await this.removeTempFile(temporalName);\n }\n });\n\n // the 'close' event is emitted when the stream and any of its\n // underlying resources (a file descriptor, for example) have been closed.\n writeStream.once('close', async () => {\n if (failed) {\n // the error/abort listeners own the cleanup of the temporal file\n return;\n }\n try {\n await renameTmp(temporalName, pathName);\n } catch (err) {\n this.logger.error(\n { err, temporalName, pathName },\n 'error on rename temporal file @{temporalName} to @{pathName}: @{err.message},' +\n 'please report this bug'\n );\n }\n });\n\n // if upload is aborted, we clean up the temporal file\n signal?.addEventListener(\n 'abort',\n async () => {\n failed = true;\n if (opened) {\n // close always happens, even if error\n writeStream.once('close', async () => {\n await this.removeTempFile(temporalName);\n });\n } else {\n await this.removeTempFile(temporalName);\n }\n },\n { once: true }\n );\n\n return writeStream;\n }\n\n /**\n * Read a tarball from the storage\n * @param tarballName tarball name eg: foo-1.0.0.tgz\n * @param options {signal} abort signal\n * @returns Readable stream\n */\n public async readTarball(tarballName: string, { signal }): Promise<Readable> {\n const pathName: string = this._getStorage(tarballName);\n debug('read a tarball %o', pathName);\n const readStream = addAbortSignal(signal, fs.createReadStream(pathName));\n readStream.on('open', function (fileDescriptorId: number) {\n // if abort, the descriptor is null\n debug('file descriptor id %o', fileDescriptorId);\n if (fileDescriptorId) {\n // sync on purpose: the size must be emitted before the first data\n // chunk flushes the response headers (an async fstat races it)\n let size: number | undefined;\n try {\n size = fs.fstatSync(fileDescriptorId).size;\n } catch {\n // the stream 'error' event surfaces any real problem\n }\n if (size !== undefined) {\n debug('file size %o', size);\n readStream.emit('content-length', size);\n }\n }\n });\n readStream.on('error', (error) => {\n debug('no tarball found %o for %s message %s', pathName, tarballName, error.message);\n });\n return readStream;\n }\n\n private async _readStorageFile(name: string): Promise<any> {\n debug('reading the file: %o', name);\n try {\n return await readFilePromise(name);\n } catch (err: any) {\n debug('error reading the file: %o with error %o', name, err.message);\n throw err;\n }\n }\n\n private _convertToString(value: Manifest): string {\n return JSON.stringify(value);\n }\n\n public _getStorage(fileName = ''): string {\n const storagePath: string = path.join(this.path, sanitzers(fileName));\n debug('get storage %s', storagePath);\n return storagePath;\n }\n\n private async writeTempFileAndRename(dest: string, fileContent: string): Promise<any> {\n const tempFilePath = tempFile(dest);\n try {\n // write file on temp location\n await this.checkCreateFolder(tempFilePath);\n await writeFilePromise(tempFilePath, fileContent);\n debug('creating a new file:: %o', dest);\n // rename tmp file to original\n await renameTmp(tempFilePath, dest);\n } catch (err: any) {\n debug('error on write the file: %o', dest);\n throw err;\n }\n }\n\n private async writeFile(destiny: string, fileContent: string): Promise<void> {\n try {\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } catch (err: any) {\n if (err && err.code === noSuchFile) {\n await this.checkCreateFolder(destiny);\n // we try again create the temp file\n debug('writing a temp file %s', destiny);\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } else {\n this.logger.error({ err }, 'error on write file: @{err.message}');\n throw err;\n }\n }\n }\n\n private async checkCreateFolder(pathName: string): Promise<void> {\n // Check if folder exists and create it if not\n const dir = path.dirname(pathName);\n try {\n await accessPromise(dir);\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('folder does not exist %o', dir);\n await mkdirPromise(dir, { recursive: true });\n debug('folder %o created', dir);\n } else {\n debug('error on check folder %o', dir);\n throw err;\n }\n }\n }\n\n private async _lockAndReadJSON(name: string): Promise<Manifest> {\n const fileName: string = this._getStorage(name);\n debug('lock and read a file %o', fileName);\n try {\n const data = await readFileNext<Manifest>(fileName, {\n lock: true,\n parse: true,\n });\n return data;\n } catch (err) {\n this.logger.error({ err }, 'error on lock file: @{err.message}');\n debug('error on lock and read json for file: %o', name);\n\n throw err;\n }\n }\n\n private async _unlockJSON(name: string): Promise<void> {\n await unlockFileNext(this._getStorage(name));\n }\n}\n"],"mappings":";;;;;;;;;AAwBA,IAAa,YAAY;AACzB,IAAa,aAAa;AAC1B,IAAa,uBAAuB;AACpC,IAAa,sBAAsB;AAInC,IAAM,QAAQ,WAAW,yCAAyC;AAElE,IAAa,UAAU,SAAU,SAAiB,OAAO,KAAqB;CAC5E,MAAM,MAAsB,WAAW,QAAQ,MAAM,OAAO;CAG5D,IAAI,OAAO;CAEX,OAAO;AACT;AAEA,IAAM,WAAW,SAAU,KAAa;CACtC,OAAO,GAAG,IAAI,MAAM,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AACnD;AAEA,eAAsB,UAAU,KAAa,KAA4B;CACvE,MAAM,mBAAmB,KAAK,GAAG;CACjC,IAAI;EACF,MAAM,cAAc,KAAK,GAAG;CAC9B,SAAS,KAAU;EACjB,MAAM,4BAA4B,KAAK,KAAK,OAAO;EACnD,MAAM,cAAc,GAAG;CACzB;AACF;AAEA,IAAqB,UAArB,MAA+D;CAC7D;CACA;CAEA,YAAmB,MAAc,QAAgB;EAC/C,KAAK,OAAO;EACZ,KAAK,SAAS;CAChB;;;;;;;;;;;;;;;;;;;;;CAsBA,MAAa,cACX,aACA,cACmB;EAEnB,IAAI,SAAS;EACb,IAAI;EACJ,IAAI;GACF,MAAM,WAAW,MAAM,KAAK,iBAAiB,mBAAmB;GAChE,SAAS;GACT,kBAAkB,MAAM,aAAa,QAAQ;GAC7C,IAAI,QAAQ;IACV,MAAM,aAAa,mBAAmB;IACtC,MAAM,KAAK,YAAY,mBAAmB;IAC1C,KAAK,OAAO,MAAM,EAAE,YAAY,GAAG,6CAA6C;IAChF,OAAO;GACT,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,YAAY,GAAG,6CAA6C;IAChF,OAAO;GACT;EACF,SAAS,KAAU;GAEjB,KAAK,OAAO,MACV;IAAE;IAAK;GAAY,GACnB,wDACF;GACA,IAAI,QAAQ;IACV,IAAI;KACF,MAAM,KAAK,YAAY,mBAAmB;IAC5C,QAAQ;KAEN,MAAM,WAAW,iBAAiB,kCAAkC;IACtE;IAEA,MAAM;GACR,OACE,IAAI,IAAI,SAAA,UACN,MAAM,WAAW,iBAAiB,kCAAkC;QAC/D,IAAI,IAAI,SAAA,UACb,MAAM,WAAW,YAAY;QAE7B,MAAM;EAGZ;CACF;CAEA,MAAa,cAAc,aAAoC;EAC7D,MAAM,4BAA4B,WAAW;EAE7C,OAAO,MAAM,cAAc,KAAK,YAAY,WAAW,CAAC;CAC1D;CAEA,MAAa,gBAA+B;EAC1C,MAAM,8BAA8B,KAAK,IAAI;EAE7C,MAAM,aAAa,KAAK,YAAY,GAAG,CAAC;CAC1C;;;;;;CAOA,MAAa,aAA+B;EAC1C,MAAM,WAAmB,KAAK,YAAY,mBAAmB;EAC7D,IAAI;GAEF,QAAO,MADY,YAAY,QAAQ,EAAA,CAC3B,OAAO;EACrB,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,MAAM,6BAA6B,UAAU,KAAK,IAAI;IACtD,OAAO;GACT,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,iDAAiD;IAC5E,MAAM,WAAW,iBAAiB,iCAAiC;GACrE;EACF;CACF;;;;;;CAOA,MAAa,cAAc,MAAc,UAAmC;EAC1E,MAAM,2BAA2B,IAAI;EACrC,MAAM,cAAc,KAAK,YAAY,mBAAmB;EACxD,IAAI;GAGF,MAAM,YAAY,aAAa,IAAI;EACrC,SAAS,KAAU;GAEjB,IAAI,IAAI,SAAS,UAAU;IACzB,MAAM,oDAAoD,IAAI;IAC9D,MAAM,QAAQ,SAAS;GACzB;EACF;EAEA,MAAM,KAAK,UAAU,aAAa,KAAK,iBAAiB,QAAQ,CAAC;CACnE;CAEA,MAAa,YAAY,MAAc,OAAgC;EACrE,MAAM,qBAAqB,IAAI;EAE/B,MAAM,KAAK,UAAU,KAAK,YAAY,mBAAmB,GAAG,KAAK,iBAAiB,KAAK,CAAC;CAC1F;CAEA,MAAa,YAAY,MAAiC;EACxD,MAAM,qBAAqB,IAAI;EAC/B,IAAI;GACF,MAAM,MAAM,MAAM,KAAK,iBAAiB,KAAK,YAAY,mBAAmB,CAAC;GAC7E,MAAM,OAAY,KAAK,MAAM,IAAI,SAAS,MAAM,CAAC;GAEjD,MAAM,sCAAsC,IAAI;GAChD,OAAO;EACT,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,MAAM,aAAa;IACnB,KAAK,OAAO,MAAM;KAAE;KAAK;IAAK,GAAG,wCAAwC;GAC3E;GACA,MAAM;EACR;CACF;CAEA,MAAa,WAAW,UAAoC;EAC1D,MAAM,WAAmB,KAAK,YAAY,QAAQ;EAClD,OAAO,IAAI,SAAS,YAAY;GAC9B,cAAc,QAAQ,CAAC,CACpB,WAAW;IACV,QAAQ,IAAI;GACd,CAAC,CAAC,CACD,YAAY,QAAQ,KAAK,CAAC;EAC/B,CAAC;CACH;;;;;;CAOA,MAAc,eAAe,cAA6B;EACxD,MAAM,2BAA2B,YAAY;EAC7C,IAAI;GACF,MAAM,cAAc,YAAY;GAChC,MAAM,4BAA4B,YAAY;EAChD,SAAS,KAAU;GACjB,IAAI,KAAK,SAAS,UAChB,KAAK,OAAO,KACV;IAAE;IAAc,KAAK,IAAI;GAAQ,GACjC,wDACF;EAEJ;CACF;;;;;;;CAQA,MAAa,aAAa,UAAkB,EAAE,UAA6B;EACzE,MAAM,sBAAsB,QAAQ;EACpC,MAAM,WAAmB,KAAK,YAAY,QAAQ;EAClD,MAAM,KAAK,kBAAkB,QAAQ;EAErC,MAAM,eAAe,KAAK,KACxB,KAAK,MACL,UAAU,GAAG,SAAS,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,QAAQ,QAAQ,EAAE,GAAG,CAC1E;EAEA,MAAM,4BAA4B,YAAY;EAC9C,IAAI,SAAS;EAGb,IAAI,SAAS;EACb,MAAM,cAAc,WAAG,kBAAkB,YAAY;EAErD,YAAY,GAAG,cAAc;GAC3B,SAAS;EACX,CAAC;EAED,YAAY,GAAG,SAAS,OAAO,QAAQ;GACrC,SAAS;GACT,IAAI,QAAQ;IACV,KAAK,OAAO,MACV;KAAE;KAAK;IAAS,GAChB,6DACF;IACA,YAAY,KAAK,SAAS,YAAY;KACpC,MAAM,KAAK,eAAe,YAAY;IACxC,CAAC;GACH,OAAO;IACL,KAAK,OAAO,MACV;KAAE;KAAK;IAAS,GAChB,0DACF;IACA,MAAM,KAAK,eAAe,YAAY;GACxC;EACF,CAAC;EAID,YAAY,KAAK,SAAS,YAAY;GACpC,IAAI,QAEF;GAEF,IAAI;IACF,MAAM,UAAU,cAAc,QAAQ;GACxC,SAAS,KAAK;IACZ,KAAK,OAAO,MACV;KAAE;KAAK;KAAc;IAAS,GAC9B,qGAEF;GACF;EACF,CAAC;EAGD,QAAQ,iBACN,SACA,YAAY;GACV,SAAS;GACT,IAAI,QAEF,YAAY,KAAK,SAAS,YAAY;IACpC,MAAM,KAAK,eAAe,YAAY;GACxC,CAAC;QAED,MAAM,KAAK,eAAe,YAAY;EAE1C,GACA,EAAE,MAAM,KAAK,CACf;EAEA,OAAO;CACT;;;;;;;CAQA,MAAa,YAAY,aAAqB,EAAE,UAA6B;EAC3E,MAAM,WAAmB,KAAK,YAAY,WAAW;EACrD,MAAM,qBAAqB,QAAQ;EACnC,MAAM,aAAa,eAAe,QAAQ,WAAG,iBAAiB,QAAQ,CAAC;EACvE,WAAW,GAAG,QAAQ,SAAU,kBAA0B;GAExD,MAAM,yBAAyB,gBAAgB;GAC/C,IAAI,kBAAkB;IAGpB,IAAI;IACJ,IAAI;KACF,OAAO,WAAG,UAAU,gBAAgB,CAAC,CAAC;IACxC,QAAQ,CAER;IACA,IAAI,SAAS,KAAA,GAAW;KACtB,MAAM,gBAAgB,IAAI;KAC1B,WAAW,KAAK,kBAAkB,IAAI;IACxC;GACF;EACF,CAAC;EACD,WAAW,GAAG,UAAU,UAAU;GAChC,MAAM,yCAAyC,UAAU,aAAa,MAAM,OAAO;EACrF,CAAC;EACD,OAAO;CACT;CAEA,MAAc,iBAAiB,MAA4B;EACzD,MAAM,wBAAwB,IAAI;EAClC,IAAI;GACF,OAAO,MAAM,gBAAgB,IAAI;EACnC,SAAS,KAAU;GACjB,MAAM,4CAA4C,MAAM,IAAI,OAAO;GACnE,MAAM;EACR;CACF;CAEA,iBAAyB,OAAyB;EAChD,OAAO,KAAK,UAAU,KAAK;CAC7B;CAEA,YAAmB,WAAW,IAAY;EACxC,MAAM,cAAsB,KAAK,KAAK,KAAK,MAAM,UAAU,QAAQ,CAAC;EACpE,MAAM,kBAAkB,WAAW;EACnC,OAAO;CACT;CAEA,MAAc,uBAAuB,MAAc,aAAmC;EACpF,MAAM,eAAe,SAAS,IAAI;EAClC,IAAI;GAEF,MAAM,KAAK,kBAAkB,YAAY;GACzC,MAAM,iBAAiB,cAAc,WAAW;GAChD,MAAM,4BAA4B,IAAI;GAEtC,MAAM,UAAU,cAAc,IAAI;EACpC,SAAS,KAAU;GACjB,MAAM,+BAA+B,IAAI;GACzC,MAAM;EACR;CACF;CAEA,MAAc,UAAU,SAAiB,aAAoC;EAC3E,IAAI;GACF,MAAM,KAAK,uBAAuB,SAAS,WAAW;GACtD,MAAM,yBAAyB,OAAO;EACxC,SAAS,KAAU;GACjB,IAAI,OAAO,IAAI,SAAA,UAAqB;IAClC,MAAM,KAAK,kBAAkB,OAAO;IAEpC,MAAM,0BAA0B,OAAO;IACvC,MAAM,KAAK,uBAAuB,SAAS,WAAW;IACtD,MAAM,yBAAyB,OAAO;GACxC,OAAO;IACL,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,qCAAqC;IAChE,MAAM;GACR;EACF;CACF;CAEA,MAAc,kBAAkB,UAAiC;EAE/D,MAAM,MAAM,KAAK,QAAQ,QAAQ;EACjC,IAAI;GACF,MAAM,cAAc,GAAG;EACzB,SAAS,KAAU;GACjB,IAAI,IAAI,SAAA,UAAqB;IAC3B,MAAM,4BAA4B,GAAG;IACrC,MAAM,aAAa,KAAK,EAAE,WAAW,KAAK,CAAC;IAC3C,MAAM,qBAAqB,GAAG;GAChC,OAAO;IACL,MAAM,4BAA4B,GAAG;IACrC,MAAM;GACR;EACF;CACF;CAEA,MAAc,iBAAiB,MAAiC;EAC9D,MAAM,WAAmB,KAAK,YAAY,IAAI;EAC9C,MAAM,2BAA2B,QAAQ;EACzC,IAAI;GAKF,OAAO,MAJY,aAAuB,UAAU;IAClD,MAAM;IACN,OAAO;GACT,CAAC;EAEH,SAAS,KAAK;GACZ,KAAK,OAAO,MAAM,EAAE,IAAI,GAAG,oCAAoC;GAC/D,MAAM,4CAA4C,IAAI;GAEtD,MAAM;EACR;CACF;CAEA,MAAc,YAAY,MAA6B;EACrD,MAAM,eAAe,KAAK,YAAY,IAAI,CAAC;CAC7C;AACF"}
|
|
1
|
+
{"version":3,"file":"local-fs.mjs","names":["fs"],"sources":["../src/local-fs.ts"],"sourcesContent":["import buildDebug from 'debug';\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\nimport sanitzers from 'sanitize-filename';\n\nimport type { VerdaccioError, pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport { readFileNext, unlockFileNext } from '@verdaccio/file-locking';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport {\n accessPromise,\n mkdirPromise,\n openPromise,\n readFilePromise,\n renamePromise,\n rmdirPromise,\n statPromise,\n unlinkPromise,\n writeFilePromise,\n} from './fs';\n\nexport const fileExist = 'EEXISTS';\nexport const noSuchFile = 'ENOENT';\nexport const resourceNotAvailable = 'EAGAIN';\nexport const packageJSONFileName = 'package.json';\n\nexport type ILocalFSPackageManager = pluginUtils.StorageHandler & { path: string };\n\nconst debug = buildDebug('verdaccio:plugin:local-storage:local-fs');\n\nexport const fSError = function (message: string, code = 409): VerdaccioError {\n const err: VerdaccioError = errorUtils.getCode(code, message);\n // FIXME: we should return http-status codes here instead, future improvement\n // @ts-ignore\n err.code = message;\n\n return err;\n};\n\nconst tempFile = function (str): string {\n return `${str}.tmp${String(Math.random()).slice(2)}`;\n};\n\nexport async function renameTmp(src: string, dst: string): Promise<void> {\n debug('rename %s to %s', src, dst);\n try {\n await renamePromise(src, dst);\n } catch (err: any) {\n debug('error rename %s error %s', src, err?.message);\n await unlinkPromise(src);\n }\n}\n\nexport default class LocalFS implements ILocalFSPackageManager {\n public path: string;\n public logger: Logger;\n\n public constructor(path: string, logger: Logger) {\n this.path = path;\n this.logger = logger;\n }\n\n /**\n * This function allows to update the package\n * This function handle the update package logic, for this plugin\n * we need to lock/unlock handlers for thread-safely and then apply\n * the `handleUpdate` and return the result.\n *\n * The lock could fail on several steps so we need to ensure the\n * file does not get locked if the whole process fails.\n *\n Algorithm:\n 1. lock package.json for writing\n 2. read package.json\n 3. apply external update package handler\n 4. return manifest (write is being hanlded into the core)\n 5. rename package.json.tmp package.json\n * @param {*} packageName\n * The update package handler could be different based\n * on the action and handled into the core.\n * @param {*} handleUpdate\n */\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n // this plugin lock files on write, we handle all possible scenarios\n let locked = false;\n let manifestUpdated: Manifest;\n try {\n const manifest = await this._lockAndReadJSON(packageJSONFileName);\n locked = true;\n manifestUpdated = await handleUpdate(manifest);\n if (locked) {\n debug('unlock %s', packageJSONFileName);\n await this._unlockJSON(packageJSONFileName);\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n } else {\n this.logger.debug({ packageName }, 'the package @{packageName} has been updated');\n return manifestUpdated;\n }\n } catch (err: any) {\n // we ensure lock the file\n this.logger.error(\n { err, packageName },\n 'error on update package @{packageName}: @{err.message}'\n );\n if (locked) {\n try {\n await this._unlockJSON(packageJSONFileName);\n } catch {\n // unlock could fail, we bubble up error\n throw errorUtils.getInternalError('resource temporarily unavailable');\n }\n // after unlock bubble up the original error (e.g. thrown by the handler)\n throw err;\n } else {\n if (err.code === resourceNotAvailable) {\n throw errorUtils.getInternalError('resource temporarily unavailable');\n } else if (err.code === noSuchFile) {\n throw errorUtils.getNotFound();\n } else {\n throw err;\n }\n }\n }\n }\n\n public async deletePackage(packageName: string): Promise<void> {\n debug('delete a file/package %o', packageName);\n\n return await unlinkPromise(this._getStorage(packageName));\n }\n\n public async removePackage(): Promise<void> {\n debug('remove a package folder %o', this.path);\n\n await rmdirPromise(this._getStorage('.'));\n }\n\n /**\n * Verify if the package exists already.\n * @param name package name\n * @returns\n */\n public async hasPackage(): Promise<boolean> {\n const pathName: string = this._getStorage(packageJSONFileName);\n try {\n const stat = await statPromise(pathName);\n return stat.isFile();\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('dir: %o does not exist %s', pathName, err?.code);\n return false;\n } else {\n this.logger.error({ err }, 'error on verify a package exist: @{err.message}');\n throw errorUtils.getInternalError('error on verify a package exist');\n }\n }\n }\n\n /**\n * Create a package in the local storage, if package already exist fails.\n * @param name package name\n * @param manifest package manifest\n */\n public async createPackage(name: string, manifest: Manifest): Promise<void> {\n debug('create a new package %o', name);\n const pathPackage = this._getStorage(packageJSONFileName);\n try {\n // https://nodejs.org/dist/latest-v17.x/docs/api/fs.html#file-system-flags\n // 'wx': Like 'w' but fails if the path exists\n await openPromise(pathPackage, 'wx');\n } catch (err: any) {\n // cannot override a pacakge that already exist\n if (err.code === 'EEXIST') {\n debug('file %o cannot be created, it already exists: %o', name);\n throw fSError(fileExist);\n }\n }\n // Create a new file and it´s folder if does not exist previously\n await this.writeFile(pathPackage, this._convertToString(manifest));\n }\n\n public async savePackage(name: string, value: Manifest): Promise<void> {\n debug('save a package %o', name);\n\n await this.writeFile(this._getStorage(packageJSONFileName), this._convertToString(value));\n }\n\n public async readPackage(name: string): Promise<Manifest> {\n debug('read a package %o', name);\n try {\n const res = await this._readStorageFile(this._getStorage(packageJSONFileName));\n const data: any = JSON.parse(res.toString('utf8'));\n\n debug('read storage file %o has succeeded', name);\n return data;\n } catch (err: any) {\n if (err.code !== noSuchFile) {\n debug('parse error');\n this.logger.error({ err, name }, 'error on parse @{name}: @{err.message}');\n }\n throw err;\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const pathName: string = this._getStorage(fileName);\n return new Promise((resolve) => {\n accessPromise(pathName)\n .then(() => {\n resolve(true);\n })\n .catch(() => resolve(false));\n });\n }\n\n /**\n * Best-effort removal of a temporary file. Error and abort can both try to\n * clean the same file from event listeners, so a missing file is fine and\n * nothing may throw — a rejection here becomes an uncaught exception.\n */\n private async removeTempFile(temporalName): Promise<void> {\n debug('remove temporal file %o', temporalName);\n try {\n await unlinkPromise(temporalName);\n debug('removed temporal file %o', temporalName);\n } catch (err: any) {\n if (err?.code !== 'ENOENT') {\n this.logger.warn(\n { temporalName, err: err.message },\n 'unable to remove temporal file @{temporalName}: @{err}'\n );\n }\n }\n }\n\n /**\n * Write a tarball into the storage\n * @param fileName package name\n * @param param1\n * @returns\n */\n public async writeTarball(fileName: string, { signal }): Promise<Writable> {\n debug('write a tarball %o', fileName);\n const pathName: string = this._getStorage(fileName);\n await this.checkCreateFolder(pathName);\n // create a temporary file to avoid conflicts or prev corruption files\n const temporalName = path.join(\n this.path,\n sanitzers(`${fileName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`)\n );\n\n debug('write a temporal name %o', temporalName);\n const finalize = async (closeError: NodeJS.ErrnoException | null): Promise<void> => {\n if (closeError || writeStream.errored || !writeStream.writableFinished) {\n // An opening write may create the file after cancellation. Its\n // descriptor must be closed before removing the temporary file.\n await this.removeTempFile(temporalName);\n return;\n }\n try {\n // A failed rename must reject the upload: the final tarball is not\n // available to readers.\n await renamePromise(temporalName, pathName);\n } catch (err) {\n await this.removeTempFile(temporalName);\n throw err;\n }\n };\n const writeStream: fs.WriteStream = fs.createWriteStream(temporalName, {\n fs: {\n open: fs.open,\n write: fs.write,\n writev: fs.writev,\n close(fd, callback) {\n fs.close(fd, (err) => {\n // Include finalization in stream destruction. An async 'close'\n // listener would let pipeline and Store finish before the rename.\n finalize(err).then(() => callback(err), callback);\n });\n },\n },\n });\n\n writeStream.on('error', (err) => {\n this.logger.error({ err, fileName }, 'error writing tarball for @{fileName}: @{err.message}');\n });\n\n if (signal) {\n addAbortSignal(signal, writeStream);\n }\n\n return writeStream;\n }\n\n /**\n * Read a tarball from the storage\n * @param tarballName tarball name eg: foo-1.0.0.tgz\n * @param options {signal} abort signal\n * @returns Readable stream\n */\n public async readTarball(tarballName: string, { signal }): Promise<Readable> {\n const pathName: string = this._getStorage(tarballName);\n debug('read a tarball %o', pathName);\n const readStream = addAbortSignal(signal, fs.createReadStream(pathName));\n readStream.on('open', function (fileDescriptorId: number) {\n // if abort, the descriptor is null\n debug('file descriptor id %o', fileDescriptorId);\n if (fileDescriptorId) {\n // sync on purpose: the size must be emitted before the first data\n // chunk flushes the response headers (an async fstat races it)\n let size: number | undefined;\n try {\n size = fs.fstatSync(fileDescriptorId).size;\n } catch {\n // the stream 'error' event surfaces any real problem\n }\n if (size !== undefined) {\n debug('file size %o', size);\n readStream.emit('content-length', size);\n }\n }\n });\n readStream.on('error', (error) => {\n debug('no tarball found %o for %s message %s', pathName, tarballName, error.message);\n });\n return readStream;\n }\n\n private async _readStorageFile(name: string): Promise<any> {\n debug('reading the file: %o', name);\n try {\n return await readFilePromise(name);\n } catch (err: any) {\n debug('error reading the file: %o with error %o', name, err.message);\n throw err;\n }\n }\n\n private _convertToString(value: Manifest): string {\n return JSON.stringify(value);\n }\n\n public _getStorage(fileName = ''): string {\n const storagePath: string = path.join(this.path, sanitzers(fileName));\n debug('get storage %s', storagePath);\n return storagePath;\n }\n\n private async writeTempFileAndRename(dest: string, fileContent: string): Promise<any> {\n const tempFilePath = tempFile(dest);\n try {\n // write file on temp location\n await this.checkCreateFolder(tempFilePath);\n await writeFilePromise(tempFilePath, fileContent);\n debug('creating a new file:: %o', dest);\n // rename tmp file to original\n await renameTmp(tempFilePath, dest);\n } catch (err: any) {\n debug('error on write the file: %o', dest);\n throw err;\n }\n }\n\n private async writeFile(destiny: string, fileContent: string): Promise<void> {\n try {\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } catch (err: any) {\n if (err && err.code === noSuchFile) {\n await this.checkCreateFolder(destiny);\n // we try again create the temp file\n debug('writing a temp file %s', destiny);\n await this.writeTempFileAndRename(destiny, fileContent);\n debug('write file success %s', destiny);\n } else {\n this.logger.error({ err }, 'error on write file: @{err.message}');\n throw err;\n }\n }\n }\n\n private async checkCreateFolder(pathName: string): Promise<void> {\n // Check if folder exists and create it if not\n const dir = path.dirname(pathName);\n try {\n await accessPromise(dir);\n } catch (err: any) {\n if (err.code === noSuchFile) {\n debug('folder does not exist %o', dir);\n await mkdirPromise(dir, { recursive: true });\n debug('folder %o created', dir);\n } else {\n debug('error on check folder %o', dir);\n throw err;\n }\n }\n }\n\n private async _lockAndReadJSON(name: string): Promise<Manifest> {\n const fileName: string = this._getStorage(name);\n debug('lock and read a file %o', fileName);\n try {\n const data = await readFileNext<Manifest>(fileName, {\n lock: true,\n parse: true,\n });\n return data;\n } catch (err) {\n this.logger.error({ err }, 'error on lock file: @{err.message}');\n debug('error on lock and read json for file: %o', name);\n\n throw err;\n }\n }\n\n private async _unlockJSON(name: string): Promise<void> {\n await unlockFileNext(this._getStorage(name));\n }\n}\n"],"mappings":"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"}
|
package/build/pkg-utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pkg-utils.js","names":[],"sources":["../src/pkg-utils.ts"],"sourcesContent":["import { isEmpty, isNil } from 'lodash-es';\n\nimport type { Logger, StorageList } from '@verdaccio/types';\n\nimport { readFilePromise } from './fs';\n\nexport type LocalStorage = {\n list: any;\n secret: string;\n};\n\nexport async function loadPrivatePackages(path: string, logger: Logger): Promise<LocalStorage> {\n const list: StorageList = [];\n const emptyDatabase = { list, secret: '' };\n const data = await readFilePromise(path);\n if (isNil(data)) {\n // readFilePromise is platform specific, FreeBSD might return null\n return emptyDatabase;\n }\n\n let db;\n try {\n db = JSON.parse(data);\n } catch (err: any) {\n logger.error(\n {\n err,\n path,\n },\n `Package database file corrupted (invalid JSON) @{err.message}, please check the error printed below.File Path: @{path}`\n );\n throw Error('Package database file corrupted (invalid JSON)');\n }\n\n if (isEmpty(db)) {\n return emptyDatabase;\n }\n\n return db;\n}\n"],"mappings":";;;AAWA,eAAsB,oBAAoB,MAAc,QAAuC;CAE7F,MAAM,gBAAgB;EAAE,MAAA,CAAA;EAAM,QAAQ;CAAG;CACzC,MAAM,OAAO,
|
|
1
|
+
{"version":3,"file":"pkg-utils.js","names":["readFilePromise","isNil","isEmpty"],"sources":["../src/pkg-utils.ts"],"sourcesContent":["import { isEmpty, isNil } from 'lodash-es';\n\nimport type { Logger, StorageList } from '@verdaccio/types';\n\nimport { readFilePromise } from './fs';\n\nexport type LocalStorage = {\n list: any;\n secret: string;\n};\n\nexport async function loadPrivatePackages(path: string, logger: Logger): Promise<LocalStorage> {\n const list: StorageList = [];\n const emptyDatabase = { list, secret: '' };\n const data = await readFilePromise(path);\n if (isNil(data)) {\n // readFilePromise is platform specific, FreeBSD might return null\n return emptyDatabase;\n }\n\n let db;\n try {\n db = JSON.parse(data);\n } catch (err: any) {\n logger.error(\n {\n err,\n path,\n },\n `Package database file corrupted (invalid JSON) @{err.message}, please check the error printed below.File Path: @{path}`\n );\n throw Error('Package database file corrupted (invalid JSON)');\n }\n\n if (isEmpty(db)) {\n return emptyDatabase;\n }\n\n return db;\n}\n"],"mappings":";;;AAWA,eAAsB,oBAAoB,MAAc,QAAuC;CAE7F,MAAM,gBAAgB;EAAE,MAAA,CAAA;EAAM,QAAQ;CAAG;CACzC,MAAM,OAAO,MAAMA,WAAAA,gBAAgB,IAAI;CACvC,KAAA,GAAIC,UAAAA,MAAAA,CAAM,IAAI,GAEZ,OAAO;CAGT,IAAI;CACJ,IAAI;EACF,KAAK,KAAK,MAAM,IAAI;CACtB,SAAS,KAAU;EACjB,OAAO,MACL;GACE;GACA;EACF,GACA,wHACF;EACA,MAAM,MAAM,gDAAgD;CAC9D;CAEA,KAAA,GAAIC,UAAAA,QAAAA,CAAQ,EAAE,GACZ,OAAO;CAGT,OAAO;AACT"}
|
package/build/utils.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.js","names":[],"sources":["../src/utils.ts"],"sourcesContent":["import { isNil } from 'lodash-es';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Config } from '@verdaccio/types';\n\nexport function getFileStats(packagePath: string): Promise<fs.Stats> {\n return new Promise((resolve, reject): void => {\n fs.stat(packagePath, (err, stats) => {\n if (isNil(err) === false) {\n return reject(err);\n }\n resolve(stats);\n });\n });\n}\n\nexport function readDirectory(packagePath: string): Promise<string[]> {\n return new Promise((resolve, reject): void => {\n fs.readdir(packagePath, (err, scopedPackages) => {\n if (isNil(err) === false) {\n return reject(err);\n }\n\n resolve(scopedPackages);\n });\n });\n}\n\nfunction hasScope(file: string): boolean {\n return file.match(/^@/) !== null;\n}\n\n/* eslint-disable no-async-promise-executor */\nexport async function findPackages(\n storagePath: string,\n validationHandler: Function\n): Promise<{ name: string; path: string }[]> {\n const listPackages: { name: string; path: string }[] = [];\n return new Promise(async (resolve, reject) => {\n try {\n const scopePath = path.resolve(storagePath);\n const storageDirs = await readDirectory(scopePath);\n for (const directory of storageDirs) {\n // we check whether has 2nd level\n if (hasScope(directory)) {\n // we read directory multiple\n const scopeDirectory = path.resolve(storagePath, directory);\n const scopedPackages = await readDirectory(scopeDirectory);\n for (const scopedDirName of scopedPackages) {\n if (validationHandler(scopedDirName)) {\n // we build the complete scope path\n const scopePath = path.resolve(storagePath, directory, scopedDirName);\n // list content of such directory\n listPackages.push({\n name: `${directory}/${scopedDirName}`,\n path: scopePath,\n });\n }\n }\n } else {\n // otherwise we read as single level\n if (validationHandler(directory)) {\n const scopePath = path.resolve(storagePath, directory);\n listPackages.push({\n name: directory,\n path: scopePath,\n });\n }\n }\n }\n } catch (error: any) {\n reject(error);\n }\n\n resolve(listPackages);\n });\n}\n\nexport function _dbGenPath(dbName: string, config: Pick<Config, 'configPath' | 'storage'>): string {\n return path.join(\n path.resolve(path.dirname(config.configPath || ''), config.storage as string, dbName)\n );\n}\n"],"mappings":";;;;;;;AA+EA,SAAgB,WAAW,QAAgB,QAAwD;CACjG,
|
|
1
|
+
{"version":3,"file":"utils.js","names":["path"],"sources":["../src/utils.ts"],"sourcesContent":["import { isNil } from 'lodash-es';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nimport type { Config } from '@verdaccio/types';\n\nexport function getFileStats(packagePath: string): Promise<fs.Stats> {\n return new Promise((resolve, reject): void => {\n fs.stat(packagePath, (err, stats) => {\n if (isNil(err) === false) {\n return reject(err);\n }\n resolve(stats);\n });\n });\n}\n\nexport function readDirectory(packagePath: string): Promise<string[]> {\n return new Promise((resolve, reject): void => {\n fs.readdir(packagePath, (err, scopedPackages) => {\n if (isNil(err) === false) {\n return reject(err);\n }\n\n resolve(scopedPackages);\n });\n });\n}\n\nfunction hasScope(file: string): boolean {\n return file.match(/^@/) !== null;\n}\n\n/* eslint-disable no-async-promise-executor */\nexport async function findPackages(\n storagePath: string,\n validationHandler: Function\n): Promise<{ name: string; path: string }[]> {\n const listPackages: { name: string; path: string }[] = [];\n return new Promise(async (resolve, reject) => {\n try {\n const scopePath = path.resolve(storagePath);\n const storageDirs = await readDirectory(scopePath);\n for (const directory of storageDirs) {\n // we check whether has 2nd level\n if (hasScope(directory)) {\n // we read directory multiple\n const scopeDirectory = path.resolve(storagePath, directory);\n const scopedPackages = await readDirectory(scopeDirectory);\n for (const scopedDirName of scopedPackages) {\n if (validationHandler(scopedDirName)) {\n // we build the complete scope path\n const scopePath = path.resolve(storagePath, directory, scopedDirName);\n // list content of such directory\n listPackages.push({\n name: `${directory}/${scopedDirName}`,\n path: scopePath,\n });\n }\n }\n } else {\n // otherwise we read as single level\n if (validationHandler(directory)) {\n const scopePath = path.resolve(storagePath, directory);\n listPackages.push({\n name: directory,\n path: scopePath,\n });\n }\n }\n }\n } catch (error: any) {\n reject(error);\n }\n\n resolve(listPackages);\n });\n}\n\nexport function _dbGenPath(dbName: string, config: Pick<Config, 'configPath' | 'storage'>): string {\n return path.join(\n path.resolve(path.dirname(config.configPath || ''), config.storage as string, dbName)\n );\n}\n"],"mappings":";;;;;;;AA+EA,SAAgB,WAAW,QAAgB,QAAwD;CACjG,OAAOA,UAAAA,QAAK,KACVA,UAAAA,QAAK,QAAQA,UAAAA,QAAK,QAAQ,OAAO,cAAc,EAAE,GAAG,OAAO,SAAmB,MAAM,CACtF;AACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@verdaccio/local-storage",
|
|
3
|
-
"version": "14.0.0-next-9.
|
|
3
|
+
"version": "14.0.0-next-9.37",
|
|
4
4
|
"description": "Verdaccio Local Storage Plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"private",
|
|
@@ -36,21 +36,21 @@
|
|
|
36
36
|
"node": ">=24"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@verdaccio/core": "9.0.0-next-9.
|
|
39
|
+
"@verdaccio/core": "9.0.0-next-9.37",
|
|
40
40
|
"@verdaccio/file-locking": "14.0.0-next-9.4",
|
|
41
41
|
"debug": "4.4.3",
|
|
42
42
|
"globby": "11.1.0",
|
|
43
43
|
"lodash-es": "4.18.1",
|
|
44
44
|
"lowdb": "7.0.1",
|
|
45
|
-
"sanitize-filename": "1.6.
|
|
45
|
+
"sanitize-filename": "1.6.4"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/lodash-es": "4.17.12",
|
|
49
|
-
"@verdaccio/logger": "9.0.0-next-9.
|
|
50
|
-
"@verdaccio/types": "14.0.0-next-9.
|
|
49
|
+
"@verdaccio/logger": "9.0.0-next-9.37",
|
|
50
|
+
"@verdaccio/types": "14.0.0-next-9.17",
|
|
51
51
|
"rimraf": "6.1.3",
|
|
52
|
-
"vite": "8.
|
|
53
|
-
"vitest": "
|
|
52
|
+
"vite": "8.3.4",
|
|
53
|
+
"vitest": "5.0.5"
|
|
54
54
|
},
|
|
55
55
|
"funding": {
|
|
56
56
|
"type": "opencollective",
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"scripts": {
|
|
74
74
|
"clean": "rimraf ./build",
|
|
75
|
-
"test": "vitest run",
|
|
75
|
+
"test": "vitest run --fsModuleCachePath node_modules/.vitest-cache",
|
|
76
76
|
"watch": "vite build --watch",
|
|
77
77
|
"build": "vite build"
|
|
78
78
|
}
|