verdaccio-memory 14.0.0-next-9.21 → 14.0.0-next-9.22

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/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { default as LocalMemory } from './local-memory';
1
+ import LocalMemory from './local-memory';
2
2
  export { LocalMemory };
3
3
  export default LocalMemory;
@@ -1,6 +1,8 @@
1
- import { searchUtils, pluginUtils } from '@verdaccio/core';
2
- import { Logger, Token, TokenFilter } from '@verdaccio/types';
3
- import { DataHandler, default as MemoryHandler } from './memory-handler';
1
+ import type { searchUtils } from '@verdaccio/core';
2
+ import { pluginUtils } from '@verdaccio/core';
3
+ import type { Logger, Token, TokenFilter } from '@verdaccio/types';
4
+ import type { DataHandler } from './memory-handler';
5
+ import MemoryHandler from './memory-handler';
4
6
  export type ConfigMemory = {
5
7
  limit?: number;
6
8
  };
@@ -1 +1 @@
1
- {"version":3,"file":"local-memory.js","names":[],"sources":["../src/local-memory.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { pluginUtils } from '@verdaccio/core';\nimport type { Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport type { DataHandler } from './memory-handler';\nimport MemoryHandler from './memory-handler';\n\nexport type ConfigMemory = { limit?: number };\nexport interface MemoryLocalStorage {\n secret: string;\n list: string[];\n files: DataHandler;\n tokens: { [user: string]: Token[] };\n}\n\nconst debug = buildDebug('verdaccio:plugin:storage:local-memory');\n\nconst DEFAULT_LIMIT = 1000;\n\nclass LocalMemory\n extends pluginUtils.Plugin<ConfigMemory>\n implements pluginUtils.Storage<ConfigMemory>\n{\n private path: string;\n private limit: number;\n public logger: Logger;\n private data: MemoryLocalStorage;\n public config: ConfigMemory;\n\n public constructor(config: ConfigMemory, options: pluginUtils.PluginOptions) {\n super(config, options);\n this.config = config;\n this.limit = config.limit || DEFAULT_LIMIT;\n this.logger = options.logger;\n this.data = this._createEmtpyDatabase();\n this.path = '/';\n debug('start plugin');\n }\n\n public init() {\n return Promise.resolve();\n }\n\n public getSecret(): Promise<string> {\n return Promise.resolve(this.data.secret);\n }\n\n public setSecret(secret: string): Promise<string | null> {\n return new Promise((resolve): void => {\n this.data.secret = secret;\n resolve(null);\n });\n }\n\n async add(name: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { list } = this.data;\n\n if (list.length < this.limit) {\n if (list.indexOf(name) === -1) {\n list.push(name);\n }\n resolve();\n } else {\n this.logger.info(\n { limit: this.limit },\n 'Storage memory has reached limit of @{limit} packages'\n );\n reject(new Error(`Storage memory has reached limit of ${this.limit} packages`));\n }\n });\n }\n\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n const results: searchUtils.SearchItem[] = [];\n const packagesOnStorage = this.filterByQuery(this.data.list, query);\n debug('memory search: packages found %o', packagesOnStorage.length);\n for (const name of packagesOnStorage) {\n const pkg: searchUtils.SearchItemPkg = { name };\n const isPrivate = this.data.list.includes(name);\n const score = this.getScore(pkg);\n results.push({\n package: pkg,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n private filterByQuery(list: string[], query: searchUtils.SearchQuery): string[] {\n const safeText = query.text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(safeText, 'i');\n return list.filter((name) => regex.test(name));\n }\n\n private getScore(_pkg: searchUtils.SearchItemPkg): searchUtils.Score {\n return {\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n };\n }\n\n async remove(name: string): Promise<void> {\n return new Promise((resolve): void => {\n const { list } = this.data;\n const item = list.indexOf(name);\n\n if (item !== -1) {\n list.splice(item, 1);\n }\n\n return resolve();\n });\n }\n\n async get(): Promise<any> {\n debug('data list length %o', this.data?.list?.length);\n return Promise.resolve(this.data?.list);\n }\n\n public getPackageStorage(packageInfo: string): MemoryHandler {\n // Ensure the package is in the list\n if (!this.data.list.includes(packageInfo)) {\n this.data.list.push(packageInfo);\n }\n return new MemoryHandler(packageInfo, this.data.files, this.logger);\n }\n\n private _createEmtpyDatabase(): MemoryLocalStorage {\n const list: string[] = [];\n const files = {};\n const tokens = {};\n const emptyDatabase = {\n list,\n files,\n tokens,\n secret: '',\n };\n\n return emptyDatabase;\n }\n\n public saveToken(token: Token): Promise<void> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n\n if (!tokens[token.user]) {\n tokens[token.user] = [token];\n } else {\n tokens[token.user].push(token);\n }\n\n debug('token saved for user %o', token.user);\n resolve();\n });\n }\n\n public deleteToken(user: string, tokenKey: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { tokens } = this.data;\n const userTokens = tokens[user];\n\n if (!userTokens) {\n reject(new Error('user not found'));\n return;\n }\n\n const remainingTokens = userTokens.filter((token) => token.key !== tokenKey);\n tokens[user] = remainingTokens;\n\n debug('removed token key %o for user %o', tokenKey, user);\n resolve();\n });\n }\n\n public readTokens(filter: TokenFilter): Promise<Token[]> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n const userTokens = tokens[filter.user] || [];\n\n debug('read tokens for user %o: %o tokens', filter.user, userTokens.length);\n resolve(userTokens);\n });\n }\n}\n\nexport default LocalMemory;\n"],"mappings":"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"}
1
+ {"version":3,"file":"local-memory.js","names":[],"sources":["../src/local-memory.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { pluginUtils } from '@verdaccio/core';\nimport type { Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport type { DataHandler } from './memory-handler';\nimport MemoryHandler from './memory-handler';\n\nexport type ConfigMemory = { limit?: number };\nexport interface MemoryLocalStorage {\n secret: string;\n list: string[];\n files: DataHandler;\n tokens: { [user: string]: Token[] };\n}\n\nconst debug = buildDebug('verdaccio:plugin:storage:local-memory');\n\nconst DEFAULT_LIMIT = 1000;\n\nclass LocalMemory\n extends pluginUtils.Plugin<ConfigMemory>\n implements pluginUtils.Storage<ConfigMemory>\n{\n private path: string;\n private limit: number;\n public logger: Logger;\n private data: MemoryLocalStorage;\n public config: ConfigMemory;\n\n public constructor(config: ConfigMemory, options: pluginUtils.PluginOptions) {\n super(config, options);\n this.config = config;\n this.limit = config.limit || DEFAULT_LIMIT;\n this.logger = options.logger;\n this.data = this._createEmtpyDatabase();\n this.path = '/';\n debug('start plugin');\n }\n\n public init() {\n return Promise.resolve();\n }\n\n public getSecret(): Promise<string> {\n return Promise.resolve(this.data.secret);\n }\n\n public setSecret(secret: string): Promise<string | null> {\n return new Promise((resolve): void => {\n this.data.secret = secret;\n resolve(null);\n });\n }\n\n async add(name: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { list } = this.data;\n\n if (list.length < this.limit) {\n if (list.indexOf(name) === -1) {\n list.push(name);\n }\n resolve();\n } else {\n this.logger.info(\n { limit: this.limit },\n 'Storage memory has reached limit of @{limit} packages'\n );\n reject(new Error(`Storage memory has reached limit of ${this.limit} packages`));\n }\n });\n }\n\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n const results: searchUtils.SearchItem[] = [];\n const packagesOnStorage = this.filterByQuery(this.data.list, query);\n debug('memory search: packages found %o', packagesOnStorage.length);\n for (const name of packagesOnStorage) {\n const pkg: searchUtils.SearchItemPkg = { name };\n const isPrivate = this.data.list.includes(name);\n const score = this.getScore(pkg);\n results.push({\n package: pkg,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n private filterByQuery(list: string[], query: searchUtils.SearchQuery): string[] {\n const safeText = query.text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(safeText, 'i');\n return list.filter((name) => regex.test(name));\n }\n\n private getScore(_pkg: searchUtils.SearchItemPkg): searchUtils.Score {\n return {\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n };\n }\n\n async remove(name: string): Promise<void> {\n return new Promise((resolve): void => {\n const { list } = this.data;\n const item = list.indexOf(name);\n\n if (item !== -1) {\n list.splice(item, 1);\n }\n\n return resolve();\n });\n }\n\n async get(): Promise<any> {\n debug('data list length %o', this.data?.list?.length);\n return Promise.resolve(this.data?.list);\n }\n\n public getPackageStorage(packageInfo: string): MemoryHandler {\n // Ensure the package is in the list\n if (!this.data.list.includes(packageInfo)) {\n this.data.list.push(packageInfo);\n }\n return new MemoryHandler(packageInfo, this.data.files, this.logger);\n }\n\n private _createEmtpyDatabase(): MemoryLocalStorage {\n const list: string[] = [];\n const files = {};\n const tokens = {};\n const emptyDatabase = {\n list,\n files,\n tokens,\n secret: '',\n };\n\n return emptyDatabase;\n }\n\n public saveToken(token: Token): Promise<void> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n\n if (!tokens[token.user]) {\n tokens[token.user] = [token];\n } else {\n tokens[token.user].push(token);\n }\n\n debug('token saved for user %o', token.user);\n resolve();\n });\n }\n\n public deleteToken(user: string, tokenKey: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { tokens } = this.data;\n const userTokens = tokens[user];\n\n if (!userTokens) {\n reject(new Error('user not found'));\n return;\n }\n\n const remainingTokens = userTokens.filter((token) => token.key !== tokenKey);\n tokens[user] = remainingTokens;\n\n debug('removed token key %o for user %o', tokenKey, user);\n resolve();\n });\n }\n\n public readTokens(filter: TokenFilter): Promise<Token[]> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n const userTokens = tokens[filter.user] || [];\n\n debug('read tokens for user %o: %o tokens', filter.user, userTokens.length);\n resolve(userTokens);\n });\n }\n}\n\nexport default LocalMemory;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"local-memory.mjs","names":[],"sources":["../src/local-memory.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { pluginUtils } from '@verdaccio/core';\nimport type { Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport type { DataHandler } from './memory-handler';\nimport MemoryHandler from './memory-handler';\n\nexport type ConfigMemory = { limit?: number };\nexport interface MemoryLocalStorage {\n secret: string;\n list: string[];\n files: DataHandler;\n tokens: { [user: string]: Token[] };\n}\n\nconst debug = buildDebug('verdaccio:plugin:storage:local-memory');\n\nconst DEFAULT_LIMIT = 1000;\n\nclass LocalMemory\n extends pluginUtils.Plugin<ConfigMemory>\n implements pluginUtils.Storage<ConfigMemory>\n{\n private path: string;\n private limit: number;\n public logger: Logger;\n private data: MemoryLocalStorage;\n public config: ConfigMemory;\n\n public constructor(config: ConfigMemory, options: pluginUtils.PluginOptions) {\n super(config, options);\n this.config = config;\n this.limit = config.limit || DEFAULT_LIMIT;\n this.logger = options.logger;\n this.data = this._createEmtpyDatabase();\n this.path = '/';\n debug('start plugin');\n }\n\n public init() {\n return Promise.resolve();\n }\n\n public getSecret(): Promise<string> {\n return Promise.resolve(this.data.secret);\n }\n\n public setSecret(secret: string): Promise<string | null> {\n return new Promise((resolve): void => {\n this.data.secret = secret;\n resolve(null);\n });\n }\n\n async add(name: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { list } = this.data;\n\n if (list.length < this.limit) {\n if (list.indexOf(name) === -1) {\n list.push(name);\n }\n resolve();\n } else {\n this.logger.info(\n { limit: this.limit },\n 'Storage memory has reached limit of @{limit} packages'\n );\n reject(new Error(`Storage memory has reached limit of ${this.limit} packages`));\n }\n });\n }\n\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n const results: searchUtils.SearchItem[] = [];\n const packagesOnStorage = this.filterByQuery(this.data.list, query);\n debug('memory search: packages found %o', packagesOnStorage.length);\n for (const name of packagesOnStorage) {\n const pkg: searchUtils.SearchItemPkg = { name };\n const isPrivate = this.data.list.includes(name);\n const score = this.getScore(pkg);\n results.push({\n package: pkg,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n private filterByQuery(list: string[], query: searchUtils.SearchQuery): string[] {\n const safeText = query.text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(safeText, 'i');\n return list.filter((name) => regex.test(name));\n }\n\n private getScore(_pkg: searchUtils.SearchItemPkg): searchUtils.Score {\n return {\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n };\n }\n\n async remove(name: string): Promise<void> {\n return new Promise((resolve): void => {\n const { list } = this.data;\n const item = list.indexOf(name);\n\n if (item !== -1) {\n list.splice(item, 1);\n }\n\n return resolve();\n });\n }\n\n async get(): Promise<any> {\n debug('data list length %o', this.data?.list?.length);\n return Promise.resolve(this.data?.list);\n }\n\n public getPackageStorage(packageInfo: string): MemoryHandler {\n // Ensure the package is in the list\n if (!this.data.list.includes(packageInfo)) {\n this.data.list.push(packageInfo);\n }\n return new MemoryHandler(packageInfo, this.data.files, this.logger);\n }\n\n private _createEmtpyDatabase(): MemoryLocalStorage {\n const list: string[] = [];\n const files = {};\n const tokens = {};\n const emptyDatabase = {\n list,\n files,\n tokens,\n secret: '',\n };\n\n return emptyDatabase;\n }\n\n public saveToken(token: Token): Promise<void> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n\n if (!tokens[token.user]) {\n tokens[token.user] = [token];\n } else {\n tokens[token.user].push(token);\n }\n\n debug('token saved for user %o', token.user);\n resolve();\n });\n }\n\n public deleteToken(user: string, tokenKey: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { tokens } = this.data;\n const userTokens = tokens[user];\n\n if (!userTokens) {\n reject(new Error('user not found'));\n return;\n }\n\n const remainingTokens = userTokens.filter((token) => token.key !== tokenKey);\n tokens[user] = remainingTokens;\n\n debug('removed token key %o for user %o', tokenKey, user);\n resolve();\n });\n }\n\n public readTokens(filter: TokenFilter): Promise<Token[]> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n const userTokens = tokens[filter.user] || [];\n\n debug('read tokens for user %o: %o tokens', filter.user, userTokens.length);\n resolve(userTokens);\n });\n }\n}\n\nexport default LocalMemory;\n"],"mappings":"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"}
1
+ {"version":3,"file":"local-memory.mjs","names":[],"sources":["../src/local-memory.ts"],"sourcesContent":["import buildDebug from 'debug';\n\nimport type { searchUtils } from '@verdaccio/core';\nimport { pluginUtils } from '@verdaccio/core';\nimport type { Logger, Token, TokenFilter } from '@verdaccio/types';\n\nimport type { DataHandler } from './memory-handler';\nimport MemoryHandler from './memory-handler';\n\nexport type ConfigMemory = { limit?: number };\nexport interface MemoryLocalStorage {\n secret: string;\n list: string[];\n files: DataHandler;\n tokens: { [user: string]: Token[] };\n}\n\nconst debug = buildDebug('verdaccio:plugin:storage:local-memory');\n\nconst DEFAULT_LIMIT = 1000;\n\nclass LocalMemory\n extends pluginUtils.Plugin<ConfigMemory>\n implements pluginUtils.Storage<ConfigMemory>\n{\n private path: string;\n private limit: number;\n public logger: Logger;\n private data: MemoryLocalStorage;\n public config: ConfigMemory;\n\n public constructor(config: ConfigMemory, options: pluginUtils.PluginOptions) {\n super(config, options);\n this.config = config;\n this.limit = config.limit || DEFAULT_LIMIT;\n this.logger = options.logger;\n this.data = this._createEmtpyDatabase();\n this.path = '/';\n debug('start plugin');\n }\n\n public init() {\n return Promise.resolve();\n }\n\n public getSecret(): Promise<string> {\n return Promise.resolve(this.data.secret);\n }\n\n public setSecret(secret: string): Promise<string | null> {\n return new Promise((resolve): void => {\n this.data.secret = secret;\n resolve(null);\n });\n }\n\n async add(name: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { list } = this.data;\n\n if (list.length < this.limit) {\n if (list.indexOf(name) === -1) {\n list.push(name);\n }\n resolve();\n } else {\n this.logger.info(\n { limit: this.limit },\n 'Storage memory has reached limit of @{limit} packages'\n );\n reject(new Error(`Storage memory has reached limit of ${this.limit} packages`));\n }\n });\n }\n\n public async search(query: searchUtils.SearchQuery): Promise<searchUtils.SearchItem[]> {\n const results: searchUtils.SearchItem[] = [];\n const packagesOnStorage = this.filterByQuery(this.data.list, query);\n debug('memory search: packages found %o', packagesOnStorage.length);\n for (const name of packagesOnStorage) {\n const pkg: searchUtils.SearchItemPkg = { name };\n const isPrivate = this.data.list.includes(name);\n const score = this.getScore(pkg);\n results.push({\n package: pkg,\n verdaccioPrivate: isPrivate,\n verdaccioPkgCached: !isPrivate,\n score,\n });\n }\n return results;\n }\n\n private filterByQuery(list: string[], query: searchUtils.SearchQuery): string[] {\n const safeText = query.text.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const regex = new RegExp(safeText, 'i');\n return list.filter((name) => regex.test(name));\n }\n\n private getScore(_pkg: searchUtils.SearchItemPkg): searchUtils.Score {\n return {\n final: 1,\n detail: {\n maintenance: 0,\n popularity: 1,\n quality: 1,\n },\n };\n }\n\n async remove(name: string): Promise<void> {\n return new Promise((resolve): void => {\n const { list } = this.data;\n const item = list.indexOf(name);\n\n if (item !== -1) {\n list.splice(item, 1);\n }\n\n return resolve();\n });\n }\n\n async get(): Promise<any> {\n debug('data list length %o', this.data?.list?.length);\n return Promise.resolve(this.data?.list);\n }\n\n public getPackageStorage(packageInfo: string): MemoryHandler {\n // Ensure the package is in the list\n if (!this.data.list.includes(packageInfo)) {\n this.data.list.push(packageInfo);\n }\n return new MemoryHandler(packageInfo, this.data.files, this.logger);\n }\n\n private _createEmtpyDatabase(): MemoryLocalStorage {\n const list: string[] = [];\n const files = {};\n const tokens = {};\n const emptyDatabase = {\n list,\n files,\n tokens,\n secret: '',\n };\n\n return emptyDatabase;\n }\n\n public saveToken(token: Token): Promise<void> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n\n if (!tokens[token.user]) {\n tokens[token.user] = [token];\n } else {\n tokens[token.user].push(token);\n }\n\n debug('token saved for user %o', token.user);\n resolve();\n });\n }\n\n public deleteToken(user: string, tokenKey: string): Promise<void> {\n return new Promise((resolve, reject): void => {\n const { tokens } = this.data;\n const userTokens = tokens[user];\n\n if (!userTokens) {\n reject(new Error('user not found'));\n return;\n }\n\n const remainingTokens = userTokens.filter((token) => token.key !== tokenKey);\n tokens[user] = remainingTokens;\n\n debug('removed token key %o for user %o', tokenKey, user);\n resolve();\n });\n }\n\n public readTokens(filter: TokenFilter): Promise<Token[]> {\n return new Promise((resolve): void => {\n const { tokens } = this.data;\n const userTokens = tokens[filter.user] || [];\n\n debug('read tokens for user %o: %o tokens', filter.user, userTokens.length);\n resolve(userTokens);\n });\n }\n}\n\nexport default LocalMemory;\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import { Readable, Writable } from 'node:stream';
2
- import { pluginUtils } from '@verdaccio/core';
3
- import { Logger, Manifest } from '@verdaccio/types';
1
+ import type { Readable, Writable } from 'node:stream';
2
+ import type { pluginUtils } from '@verdaccio/core';
3
+ import type { Logger, Manifest } from '@verdaccio/types';
4
4
  export type DataHandler = {
5
5
  [key: string]: string;
6
6
  };
@@ -1 +1 @@
1
- {"version":3,"file":"memory-handler.js","names":[],"sources":["../src/memory-handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport buildDebug from 'debug';\nimport { fs } from 'memfs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { parsePackage, stringifyPackage } from './utils';\n\nconst debug = buildDebug('verdaccio:plugin:storage:memory-storage');\n\nexport type DataHandler = {\n [key: string]: string;\n};\n\nclass MemoryHandler implements pluginUtils.StorageHandler {\n private data: DataHandler;\n private packageName: string;\n public logger: Logger;\n\n public constructor(packageName: string, data: DataHandler, logger: Logger) {\n this.data = data;\n this.logger = logger;\n this.packageName = packageName;\n // Ensure the root directory exists in memfs\n try {\n fs.mkdirSync('/');\n } catch (e) {\n // ignore if already exists\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const exists = this.data[this.getStorageKey(fileName)] === 'true';\n debug('hasTarball for %o: %s', fileName, exists);\n return exists;\n }\n\n public async hasPackage(packageName: string): Promise<boolean> {\n return this.data[packageName] !== undefined;\n }\n\n public async deletePackage(fileName: string): Promise<void> {\n // manifest is deleted by removePackage\n if (fileName === 'package.json') {\n return;\n }\n debug('delete tarball %o', fileName);\n try {\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n debug('deleting tarball with key: %s', tarballKey);\n this.removeFile(tarballName);\n delete this.data[tarballKey];\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async removePackage(packageName: string): Promise<void> {\n debug('remove package %o', packageName);\n delete this.data[packageName];\n }\n\n public async createPackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('create package %o', packageName);\n await this.savePackage(packageName, manifest);\n }\n\n public async savePackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('save package %o', packageName);\n try {\n this.data[packageName] = stringifyPackage(manifest);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async readPackage(packageName: string): Promise<Manifest> {\n debug('read package %o', packageName);\n const json = this.data[packageName];\n if (json === undefined) {\n throw errorUtils.getNotFound();\n }\n try {\n return parsePackage(json);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n private getStorageKey(fileName: string): string {\n return `${this.packageName}/${fileName}`;\n }\n\n private getStoragePath(fileName: string): string {\n // Use the complete package name for proper directory structure\n // memfs works with Unix-style paths on all platforms, so use forward slashes consistently\n const storagePath = path.posix.normalize(path.posix.join('/', this.packageName, fileName));\n debug('storage path for %s: %s', fileName, storagePath);\n return storagePath;\n }\n\n private async removeFile(fileName: string): Promise<void> {\n debug('remove file %o', fileName);\n try {\n fs.unlinkSync(fileName);\n } catch (err: any) {\n // Ignore if file does not exist\n if (err.code !== 'ENOENT') {\n throw err;\n }\n }\n }\n\n public async writeTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Writable> {\n debug('write tarball %o', fileName);\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Create a temporary file path to avoid conflicts (similar to local-fs)\n const temporalName = `${tarballName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`;\n debug('using temporal file %o', temporalName);\n\n // Ensure the directory exists for the tarball BEFORE creating the stream\n const dirName = path.posix.dirname(temporalName);\n try {\n // Make sure all directory levels are created\n await fs.promises.mkdir(dirName, { recursive: true });\n debug('ensured directory exists %o', dirName);\n } catch (err: any) {\n debug('error creating directory %o: %s', dirName, err.message);\n throw errorUtils.getInternalError(`Failed to create directory: ${err.message}`);\n }\n\n let opened = false;\n let closed = false;\n // Create the write stream but don't rely on its built-in events entirely\n const writeStream = fs.createWriteStream(temporalName);\n\n // Emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n opened = true;\n debug('write stream opened for %o', temporalName);\n writeStream.emit('open');\n });\n\n writeStream.on('error', (err) => {\n debug('write stream error %o: %s', temporalName, err.message);\n delete this.data[tarballKey];\n if (opened) {\n this.logger.error(\n { err, fileName },\n 'error on open write tarball for @{fileName}: @{err.message}'\n );\n writeStream.on('close', () => {\n this.removeFile(temporalName);\n });\n } else {\n this.logger.error(\n { err, fileName },\n 'error on writing tarball for @{fileName}: @{err.message}'\n );\n this.removeFile(temporalName);\n }\n });\n\n writeStream.on('finish', () => {\n debug('write stream finished for %o', fileName);\n });\n\n // This is called when the underlying file descriptor is closed\n // which happens after the stream is finished/ended\n writeStream.on('close', async () => {\n // prevent double close event\n if (closed) {\n debug('close event already handled for %o', temporalName);\n return;\n }\n closed = true;\n try {\n // Check if temporary file exists before trying to rename\n if (!fs.existsSync(temporalName)) {\n debug('temporary file %o does not exist, skipping rename', temporalName);\n delete this.data[tarballKey];\n return;\n }\n\n // Rename the temporary file to the final location (atomic operation)\n await fs.promises.rename(temporalName, tarballName);\n // Store the mapping of key to true (indicating it exists)\n this.data[tarballKey] = 'true';\n debug(\n 'write stream closed successfully, renamed %o to %o, stored with key %o',\n temporalName,\n tarballName,\n tarballKey\n );\n } catch (err: any) {\n debug('error renaming %o to %o: %s', temporalName, tarballName, err.message);\n this.logger.error(\n { err, temporalName, finalTarballPath: tarballName },\n 'error renaming temporal file @{temporalName} to @{finalTarballPath}: @{err.message}'\n );\n // Clean up the mapping since rename failed\n delete this.data[tarballKey];\n this.removeFile(temporalName);\n }\n });\n\n signal?.addEventListener(\n 'abort',\n () => {\n debug('upload aborted for %o', fileName);\n delete this.data[tarballKey];\n if (opened) {\n writeStream.once('close', () => {\n this.removeFile(temporalName);\n });\n writeStream.destroy();\n } else {\n this.removeFile(temporalName);\n }\n },\n { once: true }\n );\n\n return writeStream;\n }\n\n public async readTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Readable> {\n debug('read tarball %o', fileName);\n\n // Check if the tarball exists in our mapping\n if (!this.hasTarball(fileName)) {\n debug('tarball not found: %s', fileName);\n throw errorUtils.getNotFound('no such package');\n }\n\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Additional filesystem check\n try {\n const exists = fs.existsSync(tarballName);\n if (!exists) {\n debug('tarball file does not exist on filesystem: %s', tarballName);\n delete this.data[tarballKey]; // Clean up incorrect state\n throw errorUtils.getNotFound('no such package');\n }\n } catch (err: any) {\n debug('error checking tarball existence: %s', err.message);\n throw errorUtils.getNotFound('no such package');\n }\n\n const readStream = addAbortSignal(signal, fs.createReadStream(tarballName));\n\n // Check stats before emitting open event to ensure content-length is available\n const stats = fs.statSync(tarballName);\n\n // Manually emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n debug('read stream opened for %o', tarballName);\n readStream.emit('open');\n debug('file size %o', stats.size);\n readStream.emit('content-length', stats.size);\n });\n\n readStream.on('error', (error) => {\n debug('error reading tarball %o: %s', tarballName, error.message);\n });\n\n return readStream;\n }\n\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n debug('update package %o', packageName);\n try {\n const json = this.data[packageName];\n const manifest = parsePackage(json);\n const newManifest = await handleUpdate(manifest);\n return newManifest;\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n}\n\nexport default MemoryHandler;\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory-handler.js","names":[],"sources":["../src/memory-handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport buildDebug from 'debug';\nimport { fs } from 'memfs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { parsePackage, stringifyPackage } from './utils';\n\nconst debug = buildDebug('verdaccio:plugin:storage:memory-storage');\n\nexport type DataHandler = {\n [key: string]: string;\n};\n\nclass MemoryHandler implements pluginUtils.StorageHandler {\n private data: DataHandler;\n private packageName: string;\n public logger: Logger;\n\n public constructor(packageName: string, data: DataHandler, logger: Logger) {\n this.data = data;\n this.logger = logger;\n this.packageName = packageName;\n // Ensure the root directory exists in memfs\n try {\n fs.mkdirSync('/');\n } catch (e) {\n // ignore if already exists\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const exists = this.data[this.getStorageKey(fileName)] === 'true';\n debug('hasTarball for %o: %s', fileName, exists);\n return exists;\n }\n\n public async hasPackage(packageName: string): Promise<boolean> {\n return this.data[packageName] !== undefined;\n }\n\n public async deletePackage(fileName: string): Promise<void> {\n // manifest is deleted by removePackage\n if (fileName === 'package.json') {\n return;\n }\n debug('delete tarball %o', fileName);\n try {\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n debug('deleting tarball with key: %s', tarballKey);\n this.removeFile(tarballName);\n delete this.data[tarballKey];\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async removePackage(packageName: string): Promise<void> {\n debug('remove package %o', packageName);\n delete this.data[packageName];\n }\n\n public async createPackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('create package %o', packageName);\n await this.savePackage(packageName, manifest);\n }\n\n public async savePackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('save package %o', packageName);\n try {\n this.data[packageName] = stringifyPackage(manifest);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async readPackage(packageName: string): Promise<Manifest> {\n debug('read package %o', packageName);\n const json = this.data[packageName];\n if (json === undefined) {\n throw errorUtils.getNotFound();\n }\n try {\n return parsePackage(json);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n private getStorageKey(fileName: string): string {\n return `${this.packageName}/${fileName}`;\n }\n\n private getStoragePath(fileName: string): string {\n // Use the complete package name for proper directory structure\n // memfs works with Unix-style paths on all platforms, so use forward slashes consistently\n const storagePath = path.posix.normalize(path.posix.join('/', this.packageName, fileName));\n debug('storage path for %s: %s', fileName, storagePath);\n return storagePath;\n }\n\n private async removeFile(fileName: string): Promise<void> {\n debug('remove file %o', fileName);\n try {\n fs.unlinkSync(fileName);\n } catch (err: any) {\n // Ignore if file does not exist\n if (err.code !== 'ENOENT') {\n throw err;\n }\n }\n }\n\n public async writeTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Writable> {\n debug('write tarball %o', fileName);\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Create a temporary file path to avoid conflicts (similar to local-fs)\n const temporalName = `${tarballName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`;\n debug('using temporal file %o', temporalName);\n\n // Ensure the directory exists for the tarball BEFORE creating the stream\n const dirName = path.posix.dirname(temporalName);\n try {\n // Make sure all directory levels are created\n await fs.promises.mkdir(dirName, { recursive: true });\n debug('ensured directory exists %o', dirName);\n } catch (err: any) {\n debug('error creating directory %o: %s', dirName, err.message);\n throw errorUtils.getInternalError(`Failed to create directory: ${err.message}`);\n }\n\n let opened = false;\n let closed = false;\n // Create the write stream but don't rely on its built-in events entirely\n const writeStream = fs.createWriteStream(temporalName);\n\n // Emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n opened = true;\n debug('write stream opened for %o', temporalName);\n writeStream.emit('open');\n });\n\n writeStream.on('error', (err) => {\n debug('write stream error %o: %s', temporalName, err.message);\n delete this.data[tarballKey];\n if (opened) {\n this.logger.error(\n { err, fileName },\n 'error on open write tarball for @{fileName}: @{err.message}'\n );\n writeStream.on('close', () => {\n this.removeFile(temporalName);\n });\n } else {\n this.logger.error(\n { err, fileName },\n 'error on writing tarball for @{fileName}: @{err.message}'\n );\n this.removeFile(temporalName);\n }\n });\n\n writeStream.on('finish', () => {\n debug('write stream finished for %o', fileName);\n });\n\n // This is called when the underlying file descriptor is closed\n // which happens after the stream is finished/ended\n writeStream.on('close', async () => {\n // prevent double close event\n if (closed) {\n debug('close event already handled for %o', temporalName);\n return;\n }\n closed = true;\n try {\n // Check if temporary file exists before trying to rename\n if (!fs.existsSync(temporalName)) {\n debug('temporary file %o does not exist, skipping rename', temporalName);\n delete this.data[tarballKey];\n return;\n }\n\n // Rename the temporary file to the final location (atomic operation)\n await fs.promises.rename(temporalName, tarballName);\n // Store the mapping of key to true (indicating it exists)\n this.data[tarballKey] = 'true';\n debug(\n 'write stream closed successfully, renamed %o to %o, stored with key %o',\n temporalName,\n tarballName,\n tarballKey\n );\n } catch (err: any) {\n debug('error renaming %o to %o: %s', temporalName, tarballName, err.message);\n this.logger.error(\n { err, temporalName, finalTarballPath: tarballName },\n 'error renaming temporal file @{temporalName} to @{finalTarballPath}: @{err.message}'\n );\n // Clean up the mapping since rename failed\n delete this.data[tarballKey];\n this.removeFile(temporalName);\n }\n });\n\n signal?.addEventListener(\n 'abort',\n () => {\n debug('upload aborted for %o', fileName);\n delete this.data[tarballKey];\n if (opened) {\n writeStream.once('close', () => {\n this.removeFile(temporalName);\n });\n writeStream.destroy();\n } else {\n this.removeFile(temporalName);\n }\n },\n { once: true }\n );\n\n return writeStream;\n }\n\n public async readTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Readable> {\n debug('read tarball %o', fileName);\n\n // Check if the tarball exists in our mapping\n if (!this.hasTarball(fileName)) {\n debug('tarball not found: %s', fileName);\n throw errorUtils.getNotFound('no such package');\n }\n\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Additional filesystem check\n try {\n const exists = fs.existsSync(tarballName);\n if (!exists) {\n debug('tarball file does not exist on filesystem: %s', tarballName);\n delete this.data[tarballKey]; // Clean up incorrect state\n throw errorUtils.getNotFound('no such package');\n }\n } catch (err: any) {\n debug('error checking tarball existence: %s', err.message);\n throw errorUtils.getNotFound('no such package');\n }\n\n const readStream = addAbortSignal(signal, fs.createReadStream(tarballName));\n\n // Check stats before emitting open event to ensure content-length is available\n const stats = fs.statSync(tarballName);\n\n // Manually emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n debug('read stream opened for %o', tarballName);\n readStream.emit('open');\n debug('file size %o', stats.size);\n readStream.emit('content-length', stats.size);\n });\n\n readStream.on('error', (error) => {\n debug('error reading tarball %o: %s', tarballName, error.message);\n });\n\n return readStream;\n }\n\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n debug('update package %o', packageName);\n try {\n const json = this.data[packageName];\n const manifest = parsePackage(json);\n const newManifest = await handleUpdate(manifest);\n return newManifest;\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n}\n\nexport default MemoryHandler;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"memory-handler.mjs","names":[],"sources":["../src/memory-handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport buildDebug from 'debug';\nimport { fs } from 'memfs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { parsePackage, stringifyPackage } from './utils';\n\nconst debug = buildDebug('verdaccio:plugin:storage:memory-storage');\n\nexport type DataHandler = {\n [key: string]: string;\n};\n\nclass MemoryHandler implements pluginUtils.StorageHandler {\n private data: DataHandler;\n private packageName: string;\n public logger: Logger;\n\n public constructor(packageName: string, data: DataHandler, logger: Logger) {\n this.data = data;\n this.logger = logger;\n this.packageName = packageName;\n // Ensure the root directory exists in memfs\n try {\n fs.mkdirSync('/');\n } catch (e) {\n // ignore if already exists\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const exists = this.data[this.getStorageKey(fileName)] === 'true';\n debug('hasTarball for %o: %s', fileName, exists);\n return exists;\n }\n\n public async hasPackage(packageName: string): Promise<boolean> {\n return this.data[packageName] !== undefined;\n }\n\n public async deletePackage(fileName: string): Promise<void> {\n // manifest is deleted by removePackage\n if (fileName === 'package.json') {\n return;\n }\n debug('delete tarball %o', fileName);\n try {\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n debug('deleting tarball with key: %s', tarballKey);\n this.removeFile(tarballName);\n delete this.data[tarballKey];\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async removePackage(packageName: string): Promise<void> {\n debug('remove package %o', packageName);\n delete this.data[packageName];\n }\n\n public async createPackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('create package %o', packageName);\n await this.savePackage(packageName, manifest);\n }\n\n public async savePackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('save package %o', packageName);\n try {\n this.data[packageName] = stringifyPackage(manifest);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async readPackage(packageName: string): Promise<Manifest> {\n debug('read package %o', packageName);\n const json = this.data[packageName];\n if (json === undefined) {\n throw errorUtils.getNotFound();\n }\n try {\n return parsePackage(json);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n private getStorageKey(fileName: string): string {\n return `${this.packageName}/${fileName}`;\n }\n\n private getStoragePath(fileName: string): string {\n // Use the complete package name for proper directory structure\n // memfs works with Unix-style paths on all platforms, so use forward slashes consistently\n const storagePath = path.posix.normalize(path.posix.join('/', this.packageName, fileName));\n debug('storage path for %s: %s', fileName, storagePath);\n return storagePath;\n }\n\n private async removeFile(fileName: string): Promise<void> {\n debug('remove file %o', fileName);\n try {\n fs.unlinkSync(fileName);\n } catch (err: any) {\n // Ignore if file does not exist\n if (err.code !== 'ENOENT') {\n throw err;\n }\n }\n }\n\n public async writeTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Writable> {\n debug('write tarball %o', fileName);\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Create a temporary file path to avoid conflicts (similar to local-fs)\n const temporalName = `${tarballName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`;\n debug('using temporal file %o', temporalName);\n\n // Ensure the directory exists for the tarball BEFORE creating the stream\n const dirName = path.posix.dirname(temporalName);\n try {\n // Make sure all directory levels are created\n await fs.promises.mkdir(dirName, { recursive: true });\n debug('ensured directory exists %o', dirName);\n } catch (err: any) {\n debug('error creating directory %o: %s', dirName, err.message);\n throw errorUtils.getInternalError(`Failed to create directory: ${err.message}`);\n }\n\n let opened = false;\n let closed = false;\n // Create the write stream but don't rely on its built-in events entirely\n const writeStream = fs.createWriteStream(temporalName);\n\n // Emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n opened = true;\n debug('write stream opened for %o', temporalName);\n writeStream.emit('open');\n });\n\n writeStream.on('error', (err) => {\n debug('write stream error %o: %s', temporalName, err.message);\n delete this.data[tarballKey];\n if (opened) {\n this.logger.error(\n { err, fileName },\n 'error on open write tarball for @{fileName}: @{err.message}'\n );\n writeStream.on('close', () => {\n this.removeFile(temporalName);\n });\n } else {\n this.logger.error(\n { err, fileName },\n 'error on writing tarball for @{fileName}: @{err.message}'\n );\n this.removeFile(temporalName);\n }\n });\n\n writeStream.on('finish', () => {\n debug('write stream finished for %o', fileName);\n });\n\n // This is called when the underlying file descriptor is closed\n // which happens after the stream is finished/ended\n writeStream.on('close', async () => {\n // prevent double close event\n if (closed) {\n debug('close event already handled for %o', temporalName);\n return;\n }\n closed = true;\n try {\n // Check if temporary file exists before trying to rename\n if (!fs.existsSync(temporalName)) {\n debug('temporary file %o does not exist, skipping rename', temporalName);\n delete this.data[tarballKey];\n return;\n }\n\n // Rename the temporary file to the final location (atomic operation)\n await fs.promises.rename(temporalName, tarballName);\n // Store the mapping of key to true (indicating it exists)\n this.data[tarballKey] = 'true';\n debug(\n 'write stream closed successfully, renamed %o to %o, stored with key %o',\n temporalName,\n tarballName,\n tarballKey\n );\n } catch (err: any) {\n debug('error renaming %o to %o: %s', temporalName, tarballName, err.message);\n this.logger.error(\n { err, temporalName, finalTarballPath: tarballName },\n 'error renaming temporal file @{temporalName} to @{finalTarballPath}: @{err.message}'\n );\n // Clean up the mapping since rename failed\n delete this.data[tarballKey];\n this.removeFile(temporalName);\n }\n });\n\n signal?.addEventListener(\n 'abort',\n () => {\n debug('upload aborted for %o', fileName);\n delete this.data[tarballKey];\n if (opened) {\n writeStream.once('close', () => {\n this.removeFile(temporalName);\n });\n writeStream.destroy();\n } else {\n this.removeFile(temporalName);\n }\n },\n { once: true }\n );\n\n return writeStream;\n }\n\n public async readTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Readable> {\n debug('read tarball %o', fileName);\n\n // Check if the tarball exists in our mapping\n if (!this.hasTarball(fileName)) {\n debug('tarball not found: %s', fileName);\n throw errorUtils.getNotFound('no such package');\n }\n\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Additional filesystem check\n try {\n const exists = fs.existsSync(tarballName);\n if (!exists) {\n debug('tarball file does not exist on filesystem: %s', tarballName);\n delete this.data[tarballKey]; // Clean up incorrect state\n throw errorUtils.getNotFound('no such package');\n }\n } catch (err: any) {\n debug('error checking tarball existence: %s', err.message);\n throw errorUtils.getNotFound('no such package');\n }\n\n const readStream = addAbortSignal(signal, fs.createReadStream(tarballName));\n\n // Check stats before emitting open event to ensure content-length is available\n const stats = fs.statSync(tarballName);\n\n // Manually emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n debug('read stream opened for %o', tarballName);\n readStream.emit('open');\n debug('file size %o', stats.size);\n readStream.emit('content-length', stats.size);\n });\n\n readStream.on('error', (error) => {\n debug('error reading tarball %o: %s', tarballName, error.message);\n });\n\n return readStream;\n }\n\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n debug('update package %o', packageName);\n try {\n const json = this.data[packageName];\n const manifest = parsePackage(json);\n const newManifest = await handleUpdate(manifest);\n return newManifest;\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n}\n\nexport default MemoryHandler;\n"],"mappings":"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"}
1
+ {"version":3,"file":"memory-handler.mjs","names":[],"sources":["../src/memory-handler.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-unused-vars */\nimport buildDebug from 'debug';\nimport { fs } from 'memfs';\nimport path from 'node:path';\nimport type { Readable, Writable } from 'node:stream';\nimport { addAbortSignal } from 'node:stream';\n\nimport type { pluginUtils } from '@verdaccio/core';\nimport { errorUtils } from '@verdaccio/core';\nimport type { Logger, Manifest } from '@verdaccio/types';\n\nimport { parsePackage, stringifyPackage } from './utils';\n\nconst debug = buildDebug('verdaccio:plugin:storage:memory-storage');\n\nexport type DataHandler = {\n [key: string]: string;\n};\n\nclass MemoryHandler implements pluginUtils.StorageHandler {\n private data: DataHandler;\n private packageName: string;\n public logger: Logger;\n\n public constructor(packageName: string, data: DataHandler, logger: Logger) {\n this.data = data;\n this.logger = logger;\n this.packageName = packageName;\n // Ensure the root directory exists in memfs\n try {\n fs.mkdirSync('/');\n } catch (e) {\n // ignore if already exists\n }\n }\n\n public async hasTarball(fileName: string): Promise<boolean> {\n const exists = this.data[this.getStorageKey(fileName)] === 'true';\n debug('hasTarball for %o: %s', fileName, exists);\n return exists;\n }\n\n public async hasPackage(packageName: string): Promise<boolean> {\n return this.data[packageName] !== undefined;\n }\n\n public async deletePackage(fileName: string): Promise<void> {\n // manifest is deleted by removePackage\n if (fileName === 'package.json') {\n return;\n }\n debug('delete tarball %o', fileName);\n try {\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n debug('deleting tarball with key: %s', tarballKey);\n this.removeFile(tarballName);\n delete this.data[tarballKey];\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async removePackage(packageName: string): Promise<void> {\n debug('remove package %o', packageName);\n delete this.data[packageName];\n }\n\n public async createPackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('create package %o', packageName);\n await this.savePackage(packageName, manifest);\n }\n\n public async savePackage(packageName: string, manifest: Manifest): Promise<void> {\n debug('save package %o', packageName);\n try {\n this.data[packageName] = stringifyPackage(manifest);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n public async readPackage(packageName: string): Promise<Manifest> {\n debug('read package %o', packageName);\n const json = this.data[packageName];\n if (json === undefined) {\n throw errorUtils.getNotFound();\n }\n try {\n return parsePackage(json);\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n\n private getStorageKey(fileName: string): string {\n return `${this.packageName}/${fileName}`;\n }\n\n private getStoragePath(fileName: string): string {\n // Use the complete package name for proper directory structure\n // memfs works with Unix-style paths on all platforms, so use forward slashes consistently\n const storagePath = path.posix.normalize(path.posix.join('/', this.packageName, fileName));\n debug('storage path for %s: %s', fileName, storagePath);\n return storagePath;\n }\n\n private async removeFile(fileName: string): Promise<void> {\n debug('remove file %o', fileName);\n try {\n fs.unlinkSync(fileName);\n } catch (err: any) {\n // Ignore if file does not exist\n if (err.code !== 'ENOENT') {\n throw err;\n }\n }\n }\n\n public async writeTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Writable> {\n debug('write tarball %o', fileName);\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Create a temporary file path to avoid conflicts (similar to local-fs)\n const temporalName = `${tarballName}.tmp-${String(Math.random()).replace(/^0\\./, '')}`;\n debug('using temporal file %o', temporalName);\n\n // Ensure the directory exists for the tarball BEFORE creating the stream\n const dirName = path.posix.dirname(temporalName);\n try {\n // Make sure all directory levels are created\n await fs.promises.mkdir(dirName, { recursive: true });\n debug('ensured directory exists %o', dirName);\n } catch (err: any) {\n debug('error creating directory %o: %s', dirName, err.message);\n throw errorUtils.getInternalError(`Failed to create directory: ${err.message}`);\n }\n\n let opened = false;\n let closed = false;\n // Create the write stream but don't rely on its built-in events entirely\n const writeStream = fs.createWriteStream(temporalName);\n\n // Emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n opened = true;\n debug('write stream opened for %o', temporalName);\n writeStream.emit('open');\n });\n\n writeStream.on('error', (err) => {\n debug('write stream error %o: %s', temporalName, err.message);\n delete this.data[tarballKey];\n if (opened) {\n this.logger.error(\n { err, fileName },\n 'error on open write tarball for @{fileName}: @{err.message}'\n );\n writeStream.on('close', () => {\n this.removeFile(temporalName);\n });\n } else {\n this.logger.error(\n { err, fileName },\n 'error on writing tarball for @{fileName}: @{err.message}'\n );\n this.removeFile(temporalName);\n }\n });\n\n writeStream.on('finish', () => {\n debug('write stream finished for %o', fileName);\n });\n\n // This is called when the underlying file descriptor is closed\n // which happens after the stream is finished/ended\n writeStream.on('close', async () => {\n // prevent double close event\n if (closed) {\n debug('close event already handled for %o', temporalName);\n return;\n }\n closed = true;\n try {\n // Check if temporary file exists before trying to rename\n if (!fs.existsSync(temporalName)) {\n debug('temporary file %o does not exist, skipping rename', temporalName);\n delete this.data[tarballKey];\n return;\n }\n\n // Rename the temporary file to the final location (atomic operation)\n await fs.promises.rename(temporalName, tarballName);\n // Store the mapping of key to true (indicating it exists)\n this.data[tarballKey] = 'true';\n debug(\n 'write stream closed successfully, renamed %o to %o, stored with key %o',\n temporalName,\n tarballName,\n tarballKey\n );\n } catch (err: any) {\n debug('error renaming %o to %o: %s', temporalName, tarballName, err.message);\n this.logger.error(\n { err, temporalName, finalTarballPath: tarballName },\n 'error renaming temporal file @{temporalName} to @{finalTarballPath}: @{err.message}'\n );\n // Clean up the mapping since rename failed\n delete this.data[tarballKey];\n this.removeFile(temporalName);\n }\n });\n\n signal?.addEventListener(\n 'abort',\n () => {\n debug('upload aborted for %o', fileName);\n delete this.data[tarballKey];\n if (opened) {\n writeStream.once('close', () => {\n this.removeFile(temporalName);\n });\n writeStream.destroy();\n } else {\n this.removeFile(temporalName);\n }\n },\n { once: true }\n );\n\n return writeStream;\n }\n\n public async readTarball(\n fileName: string,\n { signal }: { signal: AbortSignal }\n ): Promise<Readable> {\n debug('read tarball %o', fileName);\n\n // Check if the tarball exists in our mapping\n if (!this.hasTarball(fileName)) {\n debug('tarball not found: %s', fileName);\n throw errorUtils.getNotFound('no such package');\n }\n\n const tarballName = this.getStoragePath(fileName);\n const tarballKey = this.getStorageKey(fileName);\n\n // Additional filesystem check\n try {\n const exists = fs.existsSync(tarballName);\n if (!exists) {\n debug('tarball file does not exist on filesystem: %s', tarballName);\n delete this.data[tarballKey]; // Clean up incorrect state\n throw errorUtils.getNotFound('no such package');\n }\n } catch (err: any) {\n debug('error checking tarball existence: %s', err.message);\n throw errorUtils.getNotFound('no such package');\n }\n\n const readStream = addAbortSignal(signal, fs.createReadStream(tarballName));\n\n // Check stats before emitting open event to ensure content-length is available\n const stats = fs.statSync(tarballName);\n\n // Manually emit 'open' event in the next tick to ensure consistent behavior with fs streams\n process.nextTick(() => {\n debug('read stream opened for %o', tarballName);\n readStream.emit('open');\n debug('file size %o', stats.size);\n readStream.emit('content-length', stats.size);\n });\n\n readStream.on('error', (error) => {\n debug('error reading tarball %o: %s', tarballName, error.message);\n });\n\n return readStream;\n }\n\n public async updatePackage(\n packageName: string,\n handleUpdate: (manifest: Manifest) => Promise<Manifest>\n ): Promise<Manifest> {\n debug('update package %o', packageName);\n try {\n const json = this.data[packageName];\n const manifest = parsePackage(json);\n const newManifest = await handleUpdate(manifest);\n return newManifest;\n } catch (err: any) {\n throw errorUtils.getInternalError(err.message);\n }\n }\n}\n\nexport default MemoryHandler;\n"],"mappings":"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"}
package/build/utils.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import { Manifest } from '@verdaccio/types';
1
+ import type { Manifest } from '@verdaccio/types';
2
2
  export declare function stringifyPackage(manifest: Manifest): string;
3
3
  export declare function parsePackage(json: string): Manifest;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verdaccio-memory",
3
- "version": "14.0.0-next-9.21",
3
+ "version": "14.0.0-next-9.22",
4
4
  "description": "Verdaccio Memory Storage Plugin",
5
5
  "keywords": [
6
6
  "private",
@@ -30,18 +30,18 @@
30
30
  "node": ">=24"
31
31
  },
32
32
  "dependencies": {
33
- "@verdaccio/core": "9.0.0-next-9.21",
33
+ "@verdaccio/core": "9.0.0-next-9.22",
34
34
  "debug": "4.4.3",
35
35
  "memfs": "4.17.2"
36
36
  },
37
37
  "devDependencies": {
38
- "@verdaccio/config": "9.0.0-next-9.21",
39
- "@verdaccio/logger": "9.0.0-next-9.21",
40
- "@verdaccio/plugin-verifier": "1.0.0-next-9.17",
38
+ "@verdaccio/config": "9.0.0-next-9.22",
39
+ "@verdaccio/logger": "9.0.0-next-9.22",
40
+ "@verdaccio/plugin-verifier": "1.0.0-next-9.18",
41
41
  "cross-env": "10.1.0",
42
42
  "rimraf": "6.1.3",
43
- "vite": "8.0.16",
44
- "vitest": "4.1.7"
43
+ "vite": "8.1.4",
44
+ "vitest": "4.1.10"
45
45
  },
46
46
  "funding": {
47
47
  "type": "opencollective",