@mirta/package 0.0.1 → 0.4.1
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/LICENSE +24 -0
- package/README.md +164 -28
- package/README.ru.md +180 -0
- package/dist/index.d.mts +409 -0
- package/dist/index.mjs +341 -0
- package/package.json +45 -7
package/LICENSE
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <https://unlicense.org>
|
package/README.md
CHANGED
|
@@ -1,45 +1,181 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@mirta/package`
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-package/README.md)
|
|
4
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-package/README.ru.md)
|
|
5
|
+
[](https://npmjs.com/package/@mirta/package)
|
|
6
|
+
[](https://npmjs.com/package/@mirta/package)
|
|
4
7
|
|
|
5
|
-
|
|
8
|
+
> A simple and reliable way to read `package.json` in Mirta tools.
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
`@mirta/package` is a utility for safely reading `package.json` in the development environment. It supports:
|
|
11
|
+
- TypeScript typing
|
|
12
|
+
- Clear error handling
|
|
13
|
+
- Reading only required fields: `name`, `exports`, `workspaces`
|
|
14
|
+
- Synchronous and asynchronous APIs
|
|
8
15
|
|
|
9
|
-
|
|
16
|
+
Used internally by `@mirta/workspace`, `@mirta/rollup`, and other Mirta tools for project structure analysis.<br/>
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
1. Configure OIDC trusted publishing for the package name `@mirta/package`
|
|
13
|
-
2. Enable secure, token-less publishing from CI/CD workflows
|
|
14
|
-
3. Establish provenance for packages published under this name
|
|
18
|
+
**Not intended for execution in the Duktape environment on Wiren Board controllers.**
|
|
15
19
|
|
|
16
|
-
##
|
|
20
|
+
## 📦 Installation
|
|
17
21
|
|
|
18
|
-
|
|
22
|
+
```bash
|
|
23
|
+
# Not required directly — used internally by Mirta
|
|
24
|
+
pnpm add -D @mirta/package
|
|
25
|
+
```
|
|
26
|
+
⚠️ This package is part of Mirta's internal infrastructure. It is typically not used directly.
|
|
19
27
|
|
|
20
|
-
##
|
|
28
|
+
## 🚀 Quick Start
|
|
21
29
|
|
|
22
|
-
|
|
30
|
+
```ts
|
|
31
|
+
import { readPackage, readPackageAsync, PackageError } from '@mirta/package'
|
|
23
32
|
|
|
24
|
-
|
|
25
|
-
2. Configure the trusted publisher (e.g., GitHub Actions)
|
|
26
|
-
3. Specify the repository and workflow that should be allowed to publish
|
|
27
|
-
4. Use the configured workflow to publish your actual package
|
|
33
|
+
// Synchronous reading
|
|
28
34
|
|
|
29
|
-
|
|
35
|
+
try {
|
|
36
|
+
const pkg = readPackage('packages/mirta-testing')
|
|
37
|
+
console.log(pkg.name)
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (err instanceof PackageError)
|
|
40
|
+
console.error('Error:', err.message)
|
|
41
|
+
}
|
|
30
42
|
|
|
31
|
-
|
|
32
|
-
- Contains no executable code
|
|
33
|
-
- Provides no functionality
|
|
34
|
-
- Should not be installed as a dependency
|
|
35
|
-
- Exists only for administrative purposes
|
|
43
|
+
// Asynchronous reading
|
|
36
44
|
|
|
37
|
-
|
|
45
|
+
try {
|
|
46
|
+
const pkg = await readPackageAsync('packages/mirta-testing')
|
|
47
|
+
console.log(pkg.name)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof PackageError)
|
|
50
|
+
console.error('Error:', err.message)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
38
53
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
54
|
+
## 🧰 API
|
|
55
|
+
|
|
56
|
+
`readPackage(path: string): Package`
|
|
57
|
+
|
|
58
|
+
Synchronously reads and parses `package.json` from the given path.
|
|
59
|
+
|
|
60
|
+
Supports:
|
|
61
|
+
- Path to file: `'package.json'`, `'packages/mirta-testing/package.json'`
|
|
62
|
+
- Path to package directory: `'.'`, `'packages/mirta-testing'`
|
|
63
|
+
|
|
64
|
+
Returns: an object of type `Package`.<br/>
|
|
65
|
+
Throws: `PackageError` with one of the error codes listed in the "[Possible error codes](#possible-error-codes)" section.
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
`readPackageAsync(path: string): Promise<Package>`
|
|
70
|
+
|
|
71
|
+
Asynchronously reads and parses `package.json` from the specified path.
|
|
72
|
+
|
|
73
|
+
Fully equivalent to `readPackage`, but works asynchronously. Recommended for use in async contexts (e.g. bundler plugins).
|
|
74
|
+
|
|
75
|
+
Returns: a Promise resolving to a `Package` object.<br/>
|
|
76
|
+
Throws: `PackageError` with one of the error codes listed in the "[Possible error codes](#possible-error-codes)" section.
|
|
42
77
|
|
|
43
78
|
---
|
|
44
79
|
|
|
45
|
-
|
|
80
|
+
`parsePackageJson(content: string): Package`
|
|
81
|
+
|
|
82
|
+
Parses a string containing `package.json` content into a `Package` object.
|
|
83
|
+
|
|
84
|
+
Use if the file content is already loaded (e.g. from cache or test).
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
`PackageError`
|
|
89
|
+
|
|
90
|
+
Error class with clear messages and codes. Helps quickly identify what went wrong.
|
|
91
|
+
|
|
92
|
+
<a name="#possible-error-codes"></a>
|
|
93
|
+
Possible error codes:
|
|
94
|
+
- `notFound`: the package.json file was not found,
|
|
95
|
+
- `accessDenied`: permission denied to read the file,
|
|
96
|
+
- `invalidPath`: the path does not point to a package.json file or package directory,
|
|
97
|
+
- `invalidJson`: the file contains invalid or malformed JSON,
|
|
98
|
+
- `invalidJsonRoot`: the JSON root is not an object,
|
|
99
|
+
- `failedToRead`: failed to read the file.
|
|
100
|
+
|
|
101
|
+
Example usage:
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
if (err.code === 'notFound') {
|
|
105
|
+
console.error('File not found:', err.message)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
## 🧩 Supported Fields
|
|
109
|
+
|
|
110
|
+
The package reads only the `package.json` fields required by the framework:
|
|
111
|
+
|
|
112
|
+
`name`
|
|
113
|
+
|
|
114
|
+
Package name, e.g.: `"@mirta/testing"`
|
|
115
|
+
|
|
116
|
+
`exports`
|
|
117
|
+
|
|
118
|
+
Simplified format with import is supported:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"exports": {
|
|
123
|
+
".": {
|
|
124
|
+
"import": "./dist/index.mjs"
|
|
125
|
+
},
|
|
126
|
+
"./setup-global": "./dist/setup/global.mjs"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
Or with types:
|
|
131
|
+
|
|
132
|
+
```json
|
|
133
|
+
{
|
|
134
|
+
"exports": {
|
|
135
|
+
".": {
|
|
136
|
+
"import": {
|
|
137
|
+
"types": "./dist/index.d.mts",
|
|
138
|
+
"default": "./dist/index.mjs"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
Ignored: `require`, `node`, `browser`, and other conditions.
|
|
145
|
+
|
|
146
|
+
`workspaces`
|
|
147
|
+
|
|
148
|
+
Only array of strings is allowed: `["packages/*"]`</br>
|
|
149
|
+
Object format (`{ packages: [...] }`) is not supported.
|
|
150
|
+
|
|
151
|
+
## ✅ Testing
|
|
152
|
+
|
|
153
|
+
The package is fully tested:
|
|
154
|
+
|
|
155
|
+
- Successful `package.json` reading
|
|
156
|
+
- Handling all error types: file not found, no access, invalid JSON
|
|
157
|
+
- Support for various paths
|
|
158
|
+
- Correct error mapping to `PackageError`
|
|
159
|
+
|
|
160
|
+
Uses Vitest, dependency mocks, and isolated tests.
|
|
161
|
+
|
|
162
|
+
⚠️ Limitations
|
|
163
|
+
|
|
164
|
+
- Works only in Node.js (not in Duktape).
|
|
165
|
+
- Supports only `import` in `exports`.
|
|
166
|
+
- The `workspaces` field must be an array of strings.
|
|
167
|
+
- Use `readPackageAsync` in asynchronous environments.
|
|
168
|
+
- Synchronous operations are not recommended in async contexts (use `readPackageAsync`).
|
|
169
|
+
|
|
170
|
+
## 🔄 Usage in `@mirta/workspace`
|
|
171
|
+
|
|
172
|
+
This package is used in `@mirta/workspace` to:
|
|
173
|
+
- Read `package.json` of the root project and individual packages.
|
|
174
|
+
- Validate the `workspaces` field.
|
|
175
|
+
- Collect metadata from every package in the monorepo.
|
|
176
|
+
|
|
177
|
+
Example:
|
|
178
|
+
```ts
|
|
179
|
+
const pkg = await readPackageAsync(`${rootDir}/packages/mirta-testing/package.json`)
|
|
180
|
+
```
|
|
181
|
+
Ensures a consistent and reliable way to access package metadata.
|
package/README.ru.md
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
# `@mirta/package`
|
|
2
|
+
|
|
3
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-package/README.md)
|
|
4
|
+
[](https://github.com/wb-mirta/core/blob/latest/packages/mirta-package/README.ru.md)
|
|
5
|
+
[](https://npmjs.com/package/@mirta/package)
|
|
6
|
+
[](https://npmjs.com/package/@mirta/package)
|
|
7
|
+
|
|
8
|
+
> Простой и надёжный способ чтения `package.json` в инструментах Mirta.
|
|
9
|
+
|
|
10
|
+
`@mirta/package` — это утилита для безопасного чтения `package.json` в среде разработки. Она поддерживает:
|
|
11
|
+
- TypeScript-типизацию
|
|
12
|
+
- Чёткую обработку ошибок
|
|
13
|
+
- Работу только с нужными полями: `name`, `exports`, `workspaces`
|
|
14
|
+
- Синхронный и асинхронный API
|
|
15
|
+
|
|
16
|
+
Используется внутри `@mirta/workspace`, `@mirta/rollup` и других инструментов Mirta для анализа структуры проекта.<br/>
|
|
17
|
+
|
|
18
|
+
**Не предназначен для выполнения в среде Duktape на контроллерах Wiren Board.**
|
|
19
|
+
|
|
20
|
+
## 📦 Установка
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Не требуется напрямую — используется внутри Mirta
|
|
24
|
+
pnpm add -D @mirta/package
|
|
25
|
+
```
|
|
26
|
+
⚠️ Этот пакет — часть внутренней инфраструктуры фреймворка Mirta. Обычно он не используется напрямую.
|
|
27
|
+
|
|
28
|
+
## 🚀 Быстрый старт
|
|
29
|
+
|
|
30
|
+
```ts
|
|
31
|
+
import { readPackage, readPackageAsync, PackageError } from '@mirta/package'
|
|
32
|
+
|
|
33
|
+
// Синхронное чтение
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const pkg = readPackage('packages/mirta-basics')
|
|
37
|
+
console.log(pkg.name)
|
|
38
|
+
} catch (err) {
|
|
39
|
+
if (err instanceof PackageError)
|
|
40
|
+
console.error('Ошибка:', err.message)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Асинхронное чтение
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const pkg = await readPackageAsync('packages/mirta-basics')
|
|
47
|
+
console.log(pkg.name)
|
|
48
|
+
} catch (err) {
|
|
49
|
+
if (err instanceof PackageError)
|
|
50
|
+
console.error('Ошибка:', err.message)
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## 🧰 API
|
|
55
|
+
|
|
56
|
+
`readPackage(path: string): Package`
|
|
57
|
+
|
|
58
|
+
Синхронно читает и парсит `package.json` по указанному пути.
|
|
59
|
+
|
|
60
|
+
Поддерживает:
|
|
61
|
+
- Путь к файлу: `'package.json'`, `'packages/core/package.json'`
|
|
62
|
+
- Путь к директории пакета: `'.'`, `'packages/core'`
|
|
63
|
+
|
|
64
|
+
Возвращает: объект типа `Package`.<br/>
|
|
65
|
+
Выбрасывает: `PackageError` с одним из кодов ошибок, перечисленных в разделе "[Возможные коды ошибок](#possible-error-codes)".
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
`readPackageAsync(path: string): Promise<Package>`
|
|
70
|
+
|
|
71
|
+
Асинхронно читает и парсит `package.json` по указанному пути.
|
|
72
|
+
|
|
73
|
+
Полностью аналогичен `readPackage`, но работает асинхронно. Рекомендуется для использования в асинхронных контекстах (например, в плагинах сборщиков).
|
|
74
|
+
|
|
75
|
+
Выбрасывает: `PackageError` с одним из кодов ошибок, перечисленных в разделе "[Возможные коды ошибок](#possible-error-codes)".
|
|
76
|
+
Возвращает: промис с объектом типа Package.<br/>
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
`parsePackageJson(content: string): Package`
|
|
81
|
+
|
|
82
|
+
Парсит строку с содержимым `package.json` в объект `Package`.
|
|
83
|
+
|
|
84
|
+
Используйте, если содержимое файла уже загружено (например, из кэша или теста).
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
`PackageError`
|
|
88
|
+
|
|
89
|
+
Класс ошибок с понятными сообщениями и кодами. Помогает быстро выяснить, что пошло не так.
|
|
90
|
+
|
|
91
|
+
<a name="#possible-error-codes"></a>
|
|
92
|
+
Возможные коды ошибок:
|
|
93
|
+
- `notFound` — файл `package.json` не найден,
|
|
94
|
+
- `accessDenied` — нет прав на чтение файла,
|
|
95
|
+
- `invalidPath` — путь не ведёт к `package.json` или к папке пакета,
|
|
96
|
+
- `invalidJson` — повреждённый или невалидный JSON,
|
|
97
|
+
- `invalidJsonRoot` — корень JSON-файла не является объектом,
|
|
98
|
+
- `failedToRead` — не удалось прочитать файл.
|
|
99
|
+
|
|
100
|
+
Пример использования:
|
|
101
|
+
|
|
102
|
+
```ts
|
|
103
|
+
if (err.code === 'notFound') {
|
|
104
|
+
console.error('Файл не найден:', err.message)
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
## 🧩 Поддерживаемые поля
|
|
108
|
+
|
|
109
|
+
Пакет читает только те поля `package.json`, которые нужны фреймворку:
|
|
110
|
+
|
|
111
|
+
`name`
|
|
112
|
+
|
|
113
|
+
Имя пакета, например: "@mirta/basics"
|
|
114
|
+
|
|
115
|
+
`exports`
|
|
116
|
+
|
|
117
|
+
Поддерживается упрощённый формат с `import`:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{
|
|
121
|
+
"exports": {
|
|
122
|
+
".": {
|
|
123
|
+
"import": "./dist/index.mjs"
|
|
124
|
+
},
|
|
125
|
+
"./setup-global": "./dist/setup/global.mjs"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
Или с типами:
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"exports": {
|
|
134
|
+
".": {
|
|
135
|
+
"import": {
|
|
136
|
+
"types": "./dist/index.d.mts",
|
|
137
|
+
"default": "./dist/index.mjs"
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
Игнорируются: `require`, `node`, `browser` и другие условия.
|
|
144
|
+
|
|
145
|
+
`workspaces`
|
|
146
|
+
|
|
147
|
+
Разрешён только массив строк: `["packages/*"]`<br/>
|
|
148
|
+
Объектный формат (`{ packages: [...] }`) не поддерживается.
|
|
149
|
+
|
|
150
|
+
## ✅ Тестирование
|
|
151
|
+
|
|
152
|
+
Пакет полностью покрыт тестами:
|
|
153
|
+
- Успешное чтение `package.json`
|
|
154
|
+
- Обработка всех типов ошибок: файл не найден, нет доступа, невалидный JSON
|
|
155
|
+
- Поддержка разных путей
|
|
156
|
+
- Корректная миграция ошибок в `PackageError`
|
|
157
|
+
|
|
158
|
+
Используется Vitest, моки зависимостей, изолированные тесты.
|
|
159
|
+
|
|
160
|
+
## ⚠️ Ограничения
|
|
161
|
+
|
|
162
|
+
- Работает только в Node.js (не в Duktape).
|
|
163
|
+
- Поддерживает только `import` в `exports`.
|
|
164
|
+
- Поле `workspaces` должно быть массивом строк.
|
|
165
|
+
- Для асинхронных операций используйте `readPackageAsync`.
|
|
166
|
+
- Синхронные операции не рекомендуются в асинхронных средах (используйте `readPackageAsync`).
|
|
167
|
+
|
|
168
|
+
## 🔄 Использование в `@mirta/workspace`
|
|
169
|
+
|
|
170
|
+
Этот пакет используется в `@mirta/workspace` для:
|
|
171
|
+
- Чтения `package.json` корневого проекта.
|
|
172
|
+
- Проверки поля `workspaces`.
|
|
173
|
+
- Сбора информации о каждом пакете в монорепозитории.
|
|
174
|
+
|
|
175
|
+
Пример:
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const pkg = await readPackageAsync(`${rootDir}/packages/mirta-basics`)
|
|
179
|
+
```
|
|
180
|
+
Это обеспечивает единый, надёжный способ доступа к метаданным пакетов.
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Путь внутри поля `exports` (например, `"./dist/index.mjs"`).
|
|
3
|
+
*
|
|
4
|
+
* Может быть строкой, `null` или `undefined`.
|
|
5
|
+
*
|
|
6
|
+
* @since 0.4.0
|
|
7
|
+
*
|
|
8
|
+
**/
|
|
9
|
+
type ExportsPath = string | null | undefined;
|
|
10
|
+
/**
|
|
11
|
+
* Объект с `types` и `default` внутри поля `exports`.
|
|
12
|
+
*
|
|
13
|
+
* Определяет альтернативные пути для условия `import`.
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
*
|
|
17
|
+
* ```json
|
|
18
|
+
* {
|
|
19
|
+
* "import": {
|
|
20
|
+
* "types": "./dist/index.d.mts",
|
|
21
|
+
* "default": "./dist/index.mjs"
|
|
22
|
+
* }
|
|
23
|
+
* }
|
|
24
|
+
*
|
|
25
|
+
* ```
|
|
26
|
+
* @since 0.4.0
|
|
27
|
+
*
|
|
28
|
+
**/
|
|
29
|
+
interface ExportsObject {
|
|
30
|
+
/** Путь к файлу типов (`.d.mts`). */
|
|
31
|
+
types?: ExportsPath;
|
|
32
|
+
/** Основной путь экспорта. */
|
|
33
|
+
default?: ExportsPath;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Упрощённая форма условного экспорта, поддерживаемая фреймворком Мирта.
|
|
37
|
+
*
|
|
38
|
+
* Поддерживается только условие `import`.
|
|
39
|
+
* Другие условия (`require`, `node`, `browser`) игнорируются.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
*
|
|
43
|
+
* ```json
|
|
44
|
+
* {
|
|
45
|
+
* "exports": {
|
|
46
|
+
* ".": {
|
|
47
|
+
* "import": "./dist/index.mjs"
|
|
48
|
+
* }
|
|
49
|
+
* }
|
|
50
|
+
* }
|
|
51
|
+
*
|
|
52
|
+
* ```
|
|
53
|
+
* @example
|
|
54
|
+
*
|
|
55
|
+
* ```json
|
|
56
|
+
* {
|
|
57
|
+
* "exports": {
|
|
58
|
+
* ".": {
|
|
59
|
+
* "import": {
|
|
60
|
+
* "types": "./dist/index.d.mts",
|
|
61
|
+
* "default": "./dist/index.mjs"
|
|
62
|
+
* }
|
|
63
|
+
* }
|
|
64
|
+
* }
|
|
65
|
+
* }
|
|
66
|
+
*
|
|
67
|
+
* ```
|
|
68
|
+
*
|
|
69
|
+
* @since 0.4.0
|
|
70
|
+
*
|
|
71
|
+
**/
|
|
72
|
+
interface ExportsConditional {
|
|
73
|
+
/**
|
|
74
|
+
* Путь или объект экспорта для условной загрузки.
|
|
75
|
+
*
|
|
76
|
+
* @remarks
|
|
77
|
+
* Условие `import` — единственный поддерживаемый вариант в Мирте.
|
|
78
|
+
*
|
|
79
|
+
**/
|
|
80
|
+
import: ExportsPath | ExportsObject;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Любой допустимый тип в записи поля `exports`.
|
|
84
|
+
* Может быть строкой, объектом или условным экспортом.
|
|
85
|
+
*
|
|
86
|
+
* @since 0.4.0
|
|
87
|
+
*
|
|
88
|
+
**/
|
|
89
|
+
type ExportsEntry = ExportsPath | ExportsConditional | ExportsObject;
|
|
90
|
+
/**
|
|
91
|
+
* Упрощённая форма поля `exports` из package.json, поддерживаемая фреймворком Мирта.
|
|
92
|
+
*
|
|
93
|
+
* Поддерживает:
|
|
94
|
+
* - Простые пути: `"import": "./dist/index.mjs"`;
|
|
95
|
+
* - Объекты с `types` и `default`;
|
|
96
|
+
* - Дополнительные точки входа (например, `./setup-global`, `./context`).
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
*
|
|
100
|
+
* ```json
|
|
101
|
+
* {
|
|
102
|
+
* "exports": {
|
|
103
|
+
* ".": {
|
|
104
|
+
* "import": "./dist/index.mjs"
|
|
105
|
+
* }
|
|
106
|
+
* }
|
|
107
|
+
* }
|
|
108
|
+
* ```
|
|
109
|
+
* @since 0.4.0
|
|
110
|
+
*
|
|
111
|
+
**/
|
|
112
|
+
type PackageExports = ExportsPath | ExportsConditional | Record<string, ExportsEntry>;
|
|
113
|
+
/**
|
|
114
|
+
* Минимальный контракт `package.json`, необходимый для работы фреймворка Мирта.
|
|
115
|
+
*
|
|
116
|
+
* @since 0.4.0
|
|
117
|
+
*
|
|
118
|
+
**/
|
|
119
|
+
interface Package {
|
|
120
|
+
/**
|
|
121
|
+
* Имя пакета (например, `"@mirta/package"`).
|
|
122
|
+
*
|
|
123
|
+
**/
|
|
124
|
+
name?: string;
|
|
125
|
+
/**
|
|
126
|
+
* Семантическая версия пакета (SemVer).
|
|
127
|
+
*
|
|
128
|
+
* Обязательна при публикации в реестр. Рекомендуется указывать
|
|
129
|
+
* даже в приватных пакетах, для отслеживания изменений.
|
|
130
|
+
*
|
|
131
|
+
* Имеет формат `major.minor.patch` (например, `1.2.3`), где каждый
|
|
132
|
+
* сегмент обозначает разные уровни изменений:
|
|
133
|
+
* - `major` — крупные изменения, возможны breaking changes,
|
|
134
|
+
* - `minor` — добавление новых возможностей без нарушения совместимости,
|
|
135
|
+
* - `patch` — исправление ошибок.
|
|
136
|
+
*
|
|
137
|
+
* Версии до `1.0.0` (например, `0.4.0`) считаются экспериментальными:
|
|
138
|
+
* любое обновление может включать breaking changes.
|
|
139
|
+
*
|
|
140
|
+
* @example
|
|
141
|
+
* "0.4.0"
|
|
142
|
+
* "1.0.0-alpha.1"
|
|
143
|
+
*
|
|
144
|
+
**/
|
|
145
|
+
version?: string;
|
|
146
|
+
/**
|
|
147
|
+
* Признак того, что пакет не предназначен для публикации.
|
|
148
|
+
*
|
|
149
|
+
* Если значение `true`, пакет нельзя опубликовать в реестре (например, npm).
|
|
150
|
+
* Используется для защиты от случайной публикации внутренних или служебных пакетов.
|
|
151
|
+
*
|
|
152
|
+
**/
|
|
153
|
+
private?: boolean;
|
|
154
|
+
/**
|
|
155
|
+
* Именованные команды для автоматизации задач (сборка, тесты, форматирование и т.п.).
|
|
156
|
+
*
|
|
157
|
+
**/
|
|
158
|
+
scripts?: Record<string, string>;
|
|
159
|
+
/**
|
|
160
|
+
* Конфигурация экспорта модуля.
|
|
161
|
+
*
|
|
162
|
+
* Поддерживается упрощённый формат с условием `import`.
|
|
163
|
+
*
|
|
164
|
+
**/
|
|
165
|
+
exports?: PackageExports;
|
|
166
|
+
/**
|
|
167
|
+
* Список шаблонов рабочих пространств (workspaces).
|
|
168
|
+
* Используется для определения структуры монорепозитория.
|
|
169
|
+
*
|
|
170
|
+
**/
|
|
171
|
+
workspaces?: string[];
|
|
172
|
+
/**
|
|
173
|
+
* Зависимости, необходимые для работы пакета.
|
|
174
|
+
*
|
|
175
|
+
* Устанавливаются вместе с пакетом при его добавлении в проект.
|
|
176
|
+
*
|
|
177
|
+
* @since 0.4.0
|
|
178
|
+
*
|
|
179
|
+
**/
|
|
180
|
+
dependencies?: Record<string, string>;
|
|
181
|
+
/**
|
|
182
|
+
* Зависимости, необходимые при разработке пакета.
|
|
183
|
+
*
|
|
184
|
+
* Не устанавливаются вместе с пакетом при его добавлении в проект.
|
|
185
|
+
*
|
|
186
|
+
**/
|
|
187
|
+
devDependencies?: Record<string, string>;
|
|
188
|
+
/**
|
|
189
|
+
* Зависимости, которые ожидаются в основном проекте.
|
|
190
|
+
*
|
|
191
|
+
* Не устанавливаются автоматически, но требуются для корректной работы.
|
|
192
|
+
* Позволяют избежать дублирования пакетов.
|
|
193
|
+
*
|
|
194
|
+
**/
|
|
195
|
+
peerDependencies?: Record<string, string>;
|
|
196
|
+
/**
|
|
197
|
+
* Опциональные зависимости, которые устанавливаются, если возможно.
|
|
198
|
+
*
|
|
199
|
+
* Ошибки при их установке игнорируются. Используются, когда:
|
|
200
|
+
* - Зависимость работает только на определённых платформах (например, macOS, Windows);
|
|
201
|
+
* - Пакет может предоставить fallback-реализацию или работать с ограниченной функциональностью.
|
|
202
|
+
*
|
|
203
|
+
**/
|
|
204
|
+
optionalDependencies?: Record<string, string>;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Синхронно читает и парсит `package.json` по указанному пути.
|
|
209
|
+
*
|
|
210
|
+
* Поддерживается:
|
|
211
|
+
* - Прямой путь к файлу — `'package.json'`, `'packages/core/package.json'`
|
|
212
|
+
* - Путь к корневой директории пакета — `'.'`, `'..'`, `'../../shared'`, `'packages/core'`
|
|
213
|
+
*
|
|
214
|
+
* Не поддерживается:
|
|
215
|
+
* - Передача пути к произвольному файлу — `'src/index.ts'`
|
|
216
|
+
*
|
|
217
|
+
* При ошибках чтения файла или невалидном JSON выбрасывается {@link PackageError}
|
|
218
|
+
* с подробным описанием и контекстом.
|
|
219
|
+
*
|
|
220
|
+
* @param path - Путь к `package.json` или к корневой директории пакета.
|
|
221
|
+
* @returns Объект типа {@link Package}, представляющий минимальный контракт,
|
|
222
|
+
* необходимый для разрешения структуры монорепозитория и сборки проектов.
|
|
223
|
+
* @throws {PackageError} Если файл не найден, нет доступа или JSON повреждён.
|
|
224
|
+
*
|
|
225
|
+
* @since 0.4.0
|
|
226
|
+
*
|
|
227
|
+
**/
|
|
228
|
+
declare function readPackage(path: string): Package;
|
|
229
|
+
/**
|
|
230
|
+
* Асинхронно читает и парсит `package.json` по указанному пути.
|
|
231
|
+
*
|
|
232
|
+
* Поддерживается:
|
|
233
|
+
* - Прямой путь к файлу — `'package.json'`, `'packages/core/package.json'`
|
|
234
|
+
* - Путь к корневой директории пакета — `'.'`, `'..'`, `'../../shared'`, `'packages/core'`
|
|
235
|
+
*
|
|
236
|
+
* Не поддерживается:
|
|
237
|
+
* - Передача пути к произвольному файлу — `'src/index.ts'`
|
|
238
|
+
*
|
|
239
|
+
* При ошибках чтения файла или невалидном JSON выбрасывается {@link PackageError}
|
|
240
|
+
* с подробным описанием и контекстом.
|
|
241
|
+
*
|
|
242
|
+
* @param path - Путь к `package.json` или к корневой директории пакета.
|
|
243
|
+
* @returns Объект типа {@link Package}, представляющий минимальный контракт.
|
|
244
|
+
* @throws {PackageError} Если файл не найден, нет доступа или JSON повреждён.
|
|
245
|
+
*
|
|
246
|
+
* @since 0.4.0
|
|
247
|
+
*
|
|
248
|
+
**/
|
|
249
|
+
declare function readPackageAsync(path: string): Promise<Package>;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Парсит строку JSON и возвращает объект типа {@link Package}.
|
|
253
|
+
*
|
|
254
|
+
* Не выполняет чтение из файловой системы.
|
|
255
|
+
*
|
|
256
|
+
* @param content - Строка в формате JSON
|
|
257
|
+
* @returns Объект типа {@link Package}
|
|
258
|
+
* @throws {SyntaxError} Если JSON некорректен.
|
|
259
|
+
* Сообщение содержит детали: позицию, причину.
|
|
260
|
+
*
|
|
261
|
+
* @throws {PackageError} Если JSON не является объектом.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
*
|
|
265
|
+
* ```ts
|
|
266
|
+
* parsePackageJson('{ "name": "my-package" }')
|
|
267
|
+
*
|
|
268
|
+
* ```
|
|
269
|
+
* @since 0.4.0
|
|
270
|
+
*
|
|
271
|
+
**/
|
|
272
|
+
declare function parsePackageJson(content: string): Package;
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Резолвит путь к `package.json` на основе входного пути.
|
|
276
|
+
*
|
|
277
|
+
* Правила:
|
|
278
|
+
* - Если `path` заканчивается на `package.json` → возвращается как есть
|
|
279
|
+
* - Если `path` указывает на директорию (включая `.` и `..`) → возвращается `path/package.json`
|
|
280
|
+
* - Если `path` указывает на файл с расширением (например, `src/index.ts`), но не на `package.json` → ошибка
|
|
281
|
+
*
|
|
282
|
+
* Не выполняет проверку существования файла.
|
|
283
|
+
*
|
|
284
|
+
* @param path - Путь к `package.json` или к директории пакета.
|
|
285
|
+
* Может быть абсолютным или относительным.
|
|
286
|
+
* @returns Абсолютный или относительный путь к `package.json`
|
|
287
|
+
* @throws {PackageError} С кодом `invalidPath`, если путь указывает на файл с расширением,
|
|
288
|
+
* но не является `package.json`.
|
|
289
|
+
*
|
|
290
|
+
* @example
|
|
291
|
+
*
|
|
292
|
+
* ```ts
|
|
293
|
+
* resolvePackagePath('.') // → './package.json'
|
|
294
|
+
* resolvePackagePath('..') // → '../package.json'
|
|
295
|
+
* resolvePackagePath('packages/core') // → 'packages/core/package.json'
|
|
296
|
+
* resolvePackagePath('package.json') // → 'package.json'
|
|
297
|
+
* resolvePackagePath('src/index.ts') // → ошибка
|
|
298
|
+
*
|
|
299
|
+
* ```
|
|
300
|
+
*
|
|
301
|
+
* @since 0.4.0
|
|
302
|
+
*
|
|
303
|
+
**/
|
|
304
|
+
declare function resolvePackagePath(path: string): string;
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Преобразует путь из формата Windows в формат POSIX.
|
|
308
|
+
*
|
|
309
|
+
* Заменяет все обратные слеши (`\`) на прямые (`/`), что необходимо для кросс-платформенной
|
|
310
|
+
* совместимости, особенно при сравнении путей или работе с инструментами сборки,
|
|
311
|
+
* которые ожидают стандартизированный формат пути.
|
|
312
|
+
*
|
|
313
|
+
* @param path - Входной путь, который может содержать разделители Windows (`\`).
|
|
314
|
+
* @returns Путь, в котором все разделители заменены на `/`.
|
|
315
|
+
*
|
|
316
|
+
* @example
|
|
317
|
+
* ```ts
|
|
318
|
+
* toPosix('C:\\projects\\app\\src\\index.ts')
|
|
319
|
+
* // Результат: 'C:/projects/app/src/index.ts'
|
|
320
|
+
* ```
|
|
321
|
+
*
|
|
322
|
+
* @remarks
|
|
323
|
+
* Функция использует {@link nodePath.win32.sep} и {@link nodePath.posix.sep} для получения
|
|
324
|
+
* платформенно-зависимых разделителей, что делает её надёжной независимо от ОС,
|
|
325
|
+
* на которой выполняется код.
|
|
326
|
+
*
|
|
327
|
+
* @since 0.4.0
|
|
328
|
+
*
|
|
329
|
+
**/
|
|
330
|
+
declare function toPosix(path: string): string;
|
|
331
|
+
declare function toPosix(path: string | undefined): string | undefined;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Специализированный класс для обработки ошибок, связанных с чтением и парсингом файла `package.json`.
|
|
335
|
+
*
|
|
336
|
+
* Предоставляет структурированные и типизированные ошибки с использованием кодов, что упрощает
|
|
337
|
+
* программную обработку исключений в инструментах, работающих с пакетами.
|
|
338
|
+
*
|
|
339
|
+
* @example
|
|
340
|
+
* ```ts
|
|
341
|
+
* throw PackageError.get('notFound', '/path/to/package.json');
|
|
342
|
+
* ```
|
|
343
|
+
* @since 0.4.0
|
|
344
|
+
*
|
|
345
|
+
**/
|
|
346
|
+
declare class PackageError extends Error {
|
|
347
|
+
/**
|
|
348
|
+
* Код ошибки для программной идентификации.
|
|
349
|
+
*
|
|
350
|
+
* Позволяет точно определить причину ошибки в обработчиках `try/catch`.
|
|
351
|
+
*
|
|
352
|
+
**/
|
|
353
|
+
readonly code: string;
|
|
354
|
+
/**
|
|
355
|
+
* Приватный конструктор, используемый только внутри
|
|
356
|
+
* класса для создания экземпляров ошибки.
|
|
357
|
+
*
|
|
358
|
+
* @param message - Полное сообщение об ошибке.
|
|
359
|
+
* @param code - Код ошибки для идентификации.
|
|
360
|
+
* @param scope - Пространство имён или модуль, в котором возникла ошибка.
|
|
361
|
+
* По умолчанию — {@link THIS_PACKAGE_NAME}.
|
|
362
|
+
*
|
|
363
|
+
**/
|
|
364
|
+
private constructor();
|
|
365
|
+
/** Карта кодов ошибок с соответствующими сообщениями. */
|
|
366
|
+
private static readonly codeMappings;
|
|
367
|
+
/**
|
|
368
|
+
* Фабричный метод для создания экземпляра ошибки по её коду.
|
|
369
|
+
*
|
|
370
|
+
* Автоматически подставляет сообщение из `codeMappings` и формирует ошибку с заданными параметрами.
|
|
371
|
+
*
|
|
372
|
+
* @template T - Ограниченный ключами `codeMappings` тип, гарантирующий корректность кода.
|
|
373
|
+
* @param code - Код ошибки (например, `'notFound'`, `'invalidJson'`).
|
|
374
|
+
* @param args - Аргументы, соответствующие параметрам функции сообщения из `codeMappings`.
|
|
375
|
+
* @returns Новый экземпляр {@link PackageError} с шаблонным сообщением.
|
|
376
|
+
*
|
|
377
|
+
* @example
|
|
378
|
+
* ```ts
|
|
379
|
+
* const error = PackageError.get('notFound', '/src/package.json');
|
|
380
|
+
* ```
|
|
381
|
+
*/
|
|
382
|
+
static get<T extends keyof typeof PackageError['codeMappings']>(code: T, ...args: Parameters<typeof PackageError['codeMappings'][T]>): PackageError;
|
|
383
|
+
/**
|
|
384
|
+
* Фабричный метод, аналогичный `get`, но с возможностью указать
|
|
385
|
+
* пользовательское пространство имён (scope).
|
|
386
|
+
*
|
|
387
|
+
* Полезно при использовании в других модулях, где нужно указать
|
|
388
|
+
* иной контекст ошибки.
|
|
389
|
+
*
|
|
390
|
+
* @template TKey - Тип кода ошибки, аналогично `get`.
|
|
391
|
+
*
|
|
392
|
+
* @param scope - Пространство имён ошибки (например, `'@mirta/cli'`).
|
|
393
|
+
* @param code - Код ошибки.
|
|
394
|
+
* @param args - Аргументы для формирования сообщения.
|
|
395
|
+
*
|
|
396
|
+
* @returns Новый экземпляр {@link PackageError} с пользовательским
|
|
397
|
+
* префиксом и шаблонным сообщением.
|
|
398
|
+
*
|
|
399
|
+
* @example
|
|
400
|
+
*
|
|
401
|
+
* ```ts
|
|
402
|
+
* const error = PackageError.getScoped('@mirta/cli', 'invalidPath', '/invalid/path');
|
|
403
|
+
* ```
|
|
404
|
+
**/
|
|
405
|
+
static getScoped<TKey extends keyof typeof PackageError['codeMappings']>(scope: string, code: TKey, ...args: Parameters<typeof PackageError['codeMappings'][TKey]>): PackageError;
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
export { PackageError, parsePackageJson, readPackage, readPackageAsync, resolvePackagePath, toPosix };
|
|
409
|
+
export type { ExportsConditional, ExportsEntry, ExportsObject, ExportsPath, Package, PackageExports };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import nodePath, { basename, posix } from 'node:path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Имя текущего пакета в формате, используемом в npm-реестре.
|
|
7
|
+
*
|
|
8
|
+
* Используется для логирования, формирования сообщений об ошибках,
|
|
9
|
+
* а также в диагностических и пользовательских интерфейсах,
|
|
10
|
+
* чтобы явно указывать источник операций.
|
|
11
|
+
*
|
|
12
|
+
* Централизованное определение позволяет избежать жёсткой привязки
|
|
13
|
+
* к строковому значению в разных частях кода и упрощает поддержку
|
|
14
|
+
* при возможном переименовании пакета.
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```ts
|
|
18
|
+
* console.log(`[${THIS_PACKAGE_NAME}] Запуск сборки...`);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @since 0.4.0
|
|
22
|
+
*
|
|
23
|
+
**/
|
|
24
|
+
const THIS_PACKAGE_NAME = '@mirta/package';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Специализированный класс для обработки ошибок, связанных с чтением и парсингом файла `package.json`.
|
|
28
|
+
*
|
|
29
|
+
* Предоставляет структурированные и типизированные ошибки с использованием кодов, что упрощает
|
|
30
|
+
* программную обработку исключений в инструментах, работающих с пакетами.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```ts
|
|
34
|
+
* throw PackageError.get('notFound', '/path/to/package.json');
|
|
35
|
+
* ```
|
|
36
|
+
* @since 0.4.0
|
|
37
|
+
*
|
|
38
|
+
**/
|
|
39
|
+
class PackageError extends Error {
|
|
40
|
+
/**
|
|
41
|
+
* Код ошибки для программной идентификации.
|
|
42
|
+
*
|
|
43
|
+
* Позволяет точно определить причину ошибки в обработчиках `try/catch`.
|
|
44
|
+
*
|
|
45
|
+
**/
|
|
46
|
+
code;
|
|
47
|
+
/**
|
|
48
|
+
* Приватный конструктор, используемый только внутри
|
|
49
|
+
* класса для создания экземпляров ошибки.
|
|
50
|
+
*
|
|
51
|
+
* @param message - Полное сообщение об ошибке.
|
|
52
|
+
* @param code - Код ошибки для идентификации.
|
|
53
|
+
* @param scope - Пространство имён или модуль, в котором возникла ошибка.
|
|
54
|
+
* По умолчанию — {@link THIS_PACKAGE_NAME}.
|
|
55
|
+
*
|
|
56
|
+
**/
|
|
57
|
+
constructor(message, code, scope) {
|
|
58
|
+
super(`[${scope ?? THIS_PACKAGE_NAME}] ${message}`);
|
|
59
|
+
this.name = 'PackageError';
|
|
60
|
+
this.code = code;
|
|
61
|
+
// Захватываем стек вызовов, исключая фабричный метод `get`,
|
|
62
|
+
// чтобы улучшить читаемость трассировки.
|
|
63
|
+
//
|
|
64
|
+
if ('captureStackTrace' in Error)
|
|
65
|
+
Error.captureStackTrace(this, scope
|
|
66
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
67
|
+
? PackageError.getScoped
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/unbound-method
|
|
69
|
+
: PackageError.get);
|
|
70
|
+
}
|
|
71
|
+
/** Карта кодов ошибок с соответствующими сообщениями. */
|
|
72
|
+
static codeMappings = {
|
|
73
|
+
/**
|
|
74
|
+
* Ошибка, возникающая, когда файл `package.json`
|
|
75
|
+
* не найден по указанному пути.
|
|
76
|
+
*
|
|
77
|
+
* @param filePath - Абсолютный или относительный путь к отсутствующему файлу.
|
|
78
|
+
*
|
|
79
|
+
**/
|
|
80
|
+
notFound: (filePath) => `File not found: "${filePath}"`,
|
|
81
|
+
/**
|
|
82
|
+
* Ошибка, возникающая при отсутствии прав на чтение файла.
|
|
83
|
+
*
|
|
84
|
+
* @param filePath - Путь к файлу, доступ к которому запрещён.
|
|
85
|
+
*
|
|
86
|
+
**/
|
|
87
|
+
accessDenied: (filePath) => `Access denied to file "${filePath}"`,
|
|
88
|
+
/**
|
|
89
|
+
* Ошибка, возникающая при передаче невалидного пути.
|
|
90
|
+
*
|
|
91
|
+
* @param path - Переданный путь, не соответствующий ожидаемому формату.
|
|
92
|
+
*
|
|
93
|
+
**/
|
|
94
|
+
invalidPath: (path) => `Invalid path "${path}": expected a path to "package.json" or a package directory`,
|
|
95
|
+
/**
|
|
96
|
+
* Ошибка парсинга JSON в файле `package.json`.
|
|
97
|
+
*
|
|
98
|
+
* @param filePath - Путь к файлу с некорректным JSON.
|
|
99
|
+
* @param message - Сообщение об ошибке от парсера (например, `Unexpected token }`).
|
|
100
|
+
*
|
|
101
|
+
**/
|
|
102
|
+
invalidJson: (filePath, message) => `Invalid JSON in file "${filePath}": ${message}`,
|
|
103
|
+
/**
|
|
104
|
+
* Ошибка, возникающая, если корневой элемент JSON не является объектом.
|
|
105
|
+
* Согласно спецификации, `package.json` должен начинаться с `{}`.
|
|
106
|
+
*
|
|
107
|
+
**/
|
|
108
|
+
invalidJsonRoot: () => 'Invalid JSON: root must be an object, not an array or primitive value',
|
|
109
|
+
/**
|
|
110
|
+
* Общая ошибка чтения файла с неуточнённой причиной.
|
|
111
|
+
* @param filePath - Путь к файлу.
|
|
112
|
+
* @param message - Возможное описание ошибки от файловой системы.
|
|
113
|
+
*
|
|
114
|
+
**/
|
|
115
|
+
failedToRead: (filePath, message) => `Failed to read "${filePath}": ${message ?? 'unknown reason'}`,
|
|
116
|
+
/**
|
|
117
|
+
* Ошибка, возникающая, если в `package.json` отсутствует поле `version`.
|
|
118
|
+
*
|
|
119
|
+
**/
|
|
120
|
+
noVersionField: () => 'No version field found in package.json',
|
|
121
|
+
};
|
|
122
|
+
/**
|
|
123
|
+
* Фабричный метод для создания экземпляра ошибки по её коду.
|
|
124
|
+
*
|
|
125
|
+
* Автоматически подставляет сообщение из `codeMappings` и формирует ошибку с заданными параметрами.
|
|
126
|
+
*
|
|
127
|
+
* @template T - Ограниченный ключами `codeMappings` тип, гарантирующий корректность кода.
|
|
128
|
+
* @param code - Код ошибки (например, `'notFound'`, `'invalidJson'`).
|
|
129
|
+
* @param args - Аргументы, соответствующие параметрам функции сообщения из `codeMappings`.
|
|
130
|
+
* @returns Новый экземпляр {@link PackageError} с шаблонным сообщением.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* const error = PackageError.get('notFound', '/src/package.json');
|
|
135
|
+
* ```
|
|
136
|
+
*/
|
|
137
|
+
static get(code, ...args) {
|
|
138
|
+
const messageFn = this.codeMappings[code];
|
|
139
|
+
const message = messageFn(...args);
|
|
140
|
+
return new PackageError(message, code);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Фабричный метод, аналогичный `get`, но с возможностью указать
|
|
144
|
+
* пользовательское пространство имён (scope).
|
|
145
|
+
*
|
|
146
|
+
* Полезно при использовании в других модулях, где нужно указать
|
|
147
|
+
* иной контекст ошибки.
|
|
148
|
+
*
|
|
149
|
+
* @template TKey - Тип кода ошибки, аналогично `get`.
|
|
150
|
+
*
|
|
151
|
+
* @param scope - Пространство имён ошибки (например, `'@mirta/cli'`).
|
|
152
|
+
* @param code - Код ошибки.
|
|
153
|
+
* @param args - Аргументы для формирования сообщения.
|
|
154
|
+
*
|
|
155
|
+
* @returns Новый экземпляр {@link PackageError} с пользовательским
|
|
156
|
+
* префиксом и шаблонным сообщением.
|
|
157
|
+
*
|
|
158
|
+
* @example
|
|
159
|
+
*
|
|
160
|
+
* ```ts
|
|
161
|
+
* const error = PackageError.getScoped('@mirta/cli', 'invalidPath', '/invalid/path');
|
|
162
|
+
* ```
|
|
163
|
+
**/
|
|
164
|
+
static getScoped(scope, code, ...args) {
|
|
165
|
+
const messageFn = this.codeMappings[code];
|
|
166
|
+
const message = messageFn(...args);
|
|
167
|
+
return new PackageError(message, code, scope);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function toPosix(path) {
|
|
172
|
+
if (path === '' || path === undefined)
|
|
173
|
+
return path;
|
|
174
|
+
return path.replaceAll(nodePath.win32.sep, nodePath.posix.sep);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Резолвит путь к `package.json` на основе входного пути.
|
|
179
|
+
*
|
|
180
|
+
* Правила:
|
|
181
|
+
* - Если `path` заканчивается на `package.json` → возвращается как есть
|
|
182
|
+
* - Если `path` указывает на директорию (включая `.` и `..`) → возвращается `path/package.json`
|
|
183
|
+
* - Если `path` указывает на файл с расширением (например, `src/index.ts`), но не на `package.json` → ошибка
|
|
184
|
+
*
|
|
185
|
+
* Не выполняет проверку существования файла.
|
|
186
|
+
*
|
|
187
|
+
* @param path - Путь к `package.json` или к директории пакета.
|
|
188
|
+
* Может быть абсолютным или относительным.
|
|
189
|
+
* @returns Абсолютный или относительный путь к `package.json`
|
|
190
|
+
* @throws {PackageError} С кодом `invalidPath`, если путь указывает на файл с расширением,
|
|
191
|
+
* но не является `package.json`.
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
*
|
|
195
|
+
* ```ts
|
|
196
|
+
* resolvePackagePath('.') // → './package.json'
|
|
197
|
+
* resolvePackagePath('..') // → '../package.json'
|
|
198
|
+
* resolvePackagePath('packages/core') // → 'packages/core/package.json'
|
|
199
|
+
* resolvePackagePath('package.json') // → 'package.json'
|
|
200
|
+
* resolvePackagePath('src/index.ts') // → ошибка
|
|
201
|
+
*
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* @since 0.4.0
|
|
205
|
+
*
|
|
206
|
+
**/
|
|
207
|
+
function resolvePackagePath(path) {
|
|
208
|
+
const normalizedPath = toPosix(path);
|
|
209
|
+
if (normalizedPath.endsWith('package.json'))
|
|
210
|
+
return normalizedPath;
|
|
211
|
+
const base = basename(normalizedPath);
|
|
212
|
+
// Проверяем последний фрагмент пути, не допуская файлы.
|
|
213
|
+
if (base !== '.' && base !== '..' && base.includes('.'))
|
|
214
|
+
throw PackageError.get('invalidPath', normalizedPath);
|
|
215
|
+
return posix.join(normalizedPath, 'package.json');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Парсит строку JSON и возвращает объект типа {@link Package}.
|
|
220
|
+
*
|
|
221
|
+
* Не выполняет чтение из файловой системы.
|
|
222
|
+
*
|
|
223
|
+
* @param content - Строка в формате JSON
|
|
224
|
+
* @returns Объект типа {@link Package}
|
|
225
|
+
* @throws {SyntaxError} Если JSON некорректен.
|
|
226
|
+
* Сообщение содержит детали: позицию, причину.
|
|
227
|
+
*
|
|
228
|
+
* @throws {PackageError} Если JSON не является объектом.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
*
|
|
232
|
+
* ```ts
|
|
233
|
+
* parsePackageJson('{ "name": "my-package" }')
|
|
234
|
+
*
|
|
235
|
+
* ```
|
|
236
|
+
* @since 0.4.0
|
|
237
|
+
*
|
|
238
|
+
**/
|
|
239
|
+
function parsePackageJson(content) {
|
|
240
|
+
const parsed = JSON.parse(content);
|
|
241
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed))
|
|
242
|
+
throw PackageError.get('invalidJsonRoot');
|
|
243
|
+
return parsed;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Обрабатывает ошибку, возникшую при чтении или парсинге файла `package.json`,
|
|
248
|
+
* и преобразует её в соответствующую ошибку типа {@link PackageError}.
|
|
249
|
+
*
|
|
250
|
+
* @param e - Неизвестное исключение, которое необходимо обработать.
|
|
251
|
+
* @param path - Путь к файлу `package.json`, на котором произошла ошибка.
|
|
252
|
+
* @returns Экземпляр {@link PackageError}, соответствующий типу исключения.
|
|
253
|
+
*
|
|
254
|
+
* @since 0.4.0
|
|
255
|
+
*
|
|
256
|
+
**/
|
|
257
|
+
function handleError(e, path) {
|
|
258
|
+
if (e instanceof PackageError)
|
|
259
|
+
return e;
|
|
260
|
+
if (e instanceof SyntaxError)
|
|
261
|
+
return PackageError.get('invalidJson', path, e.message);
|
|
262
|
+
if (e && typeof e === 'object' && 'code' in e) {
|
|
263
|
+
const code = e.code;
|
|
264
|
+
switch (code) {
|
|
265
|
+
case 'ENOENT':
|
|
266
|
+
return PackageError.get('notFound', path);
|
|
267
|
+
case 'EACCES':
|
|
268
|
+
case 'EPERM':
|
|
269
|
+
return PackageError.get('accessDenied', path);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const message = e instanceof Error
|
|
273
|
+
? e.message
|
|
274
|
+
: String(e);
|
|
275
|
+
return PackageError.get('failedToRead', path, message);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Синхронно читает и парсит `package.json` по указанному пути.
|
|
279
|
+
*
|
|
280
|
+
* Поддерживается:
|
|
281
|
+
* - Прямой путь к файлу — `'package.json'`, `'packages/core/package.json'`
|
|
282
|
+
* - Путь к корневой директории пакета — `'.'`, `'..'`, `'../../shared'`, `'packages/core'`
|
|
283
|
+
*
|
|
284
|
+
* Не поддерживается:
|
|
285
|
+
* - Передача пути к произвольному файлу — `'src/index.ts'`
|
|
286
|
+
*
|
|
287
|
+
* При ошибках чтения файла или невалидном JSON выбрасывается {@link PackageError}
|
|
288
|
+
* с подробным описанием и контекстом.
|
|
289
|
+
*
|
|
290
|
+
* @param path - Путь к `package.json` или к корневой директории пакета.
|
|
291
|
+
* @returns Объект типа {@link Package}, представляющий минимальный контракт,
|
|
292
|
+
* необходимый для разрешения структуры монорепозитория и сборки проектов.
|
|
293
|
+
* @throws {PackageError} Если файл не найден, нет доступа или JSON повреждён.
|
|
294
|
+
*
|
|
295
|
+
* @since 0.4.0
|
|
296
|
+
*
|
|
297
|
+
**/
|
|
298
|
+
function readPackage(path) {
|
|
299
|
+
const resolvedPath = resolvePackagePath(path);
|
|
300
|
+
let content;
|
|
301
|
+
try {
|
|
302
|
+
content = readFileSync(resolvedPath, 'utf-8');
|
|
303
|
+
return parsePackageJson(content);
|
|
304
|
+
}
|
|
305
|
+
catch (e) {
|
|
306
|
+
throw handleError(e, resolvedPath);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Асинхронно читает и парсит `package.json` по указанному пути.
|
|
311
|
+
*
|
|
312
|
+
* Поддерживается:
|
|
313
|
+
* - Прямой путь к файлу — `'package.json'`, `'packages/core/package.json'`
|
|
314
|
+
* - Путь к корневой директории пакета — `'.'`, `'..'`, `'../../shared'`, `'packages/core'`
|
|
315
|
+
*
|
|
316
|
+
* Не поддерживается:
|
|
317
|
+
* - Передача пути к произвольному файлу — `'src/index.ts'`
|
|
318
|
+
*
|
|
319
|
+
* При ошибках чтения файла или невалидном JSON выбрасывается {@link PackageError}
|
|
320
|
+
* с подробным описанием и контекстом.
|
|
321
|
+
*
|
|
322
|
+
* @param path - Путь к `package.json` или к корневой директории пакета.
|
|
323
|
+
* @returns Объект типа {@link Package}, представляющий минимальный контракт.
|
|
324
|
+
* @throws {PackageError} Если файл не найден, нет доступа или JSON повреждён.
|
|
325
|
+
*
|
|
326
|
+
* @since 0.4.0
|
|
327
|
+
*
|
|
328
|
+
**/
|
|
329
|
+
async function readPackageAsync(path) {
|
|
330
|
+
const resolvedPath = resolvePackagePath(path);
|
|
331
|
+
let content;
|
|
332
|
+
try {
|
|
333
|
+
content = await readFile(resolvedPath, 'utf-8');
|
|
334
|
+
return parsePackageJson(content);
|
|
335
|
+
}
|
|
336
|
+
catch (e) {
|
|
337
|
+
throw handleError(e, resolvedPath);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export { PackageError, parsePackageJson, readPackage, readPackageAsync, resolvePackagePath, toPosix };
|
package/package.json
CHANGED
|
@@ -1,10 +1,48 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mirta/package",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.4.1",
|
|
4
|
+
"sideEffects": false,
|
|
5
|
+
"description": "Internal type-safe reader & parser for package.json, used by Mirta Framework",
|
|
5
6
|
"keywords": [
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
"mirta",
|
|
8
|
+
"package.json",
|
|
9
|
+
"read",
|
|
10
|
+
"parse",
|
|
11
|
+
"exports",
|
|
12
|
+
"monorepo",
|
|
13
|
+
"typescript",
|
|
14
|
+
"config",
|
|
15
|
+
"utility"
|
|
16
|
+
],
|
|
17
|
+
"license": "Unlicense",
|
|
18
|
+
"homepage": "https://github.com/wb-mirta/core#readme",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/wb-mirta/core/issues"
|
|
21
|
+
},
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "git+https://github.com/wb-mirta/core.git",
|
|
25
|
+
"directory": "packages/mirta-package"
|
|
26
|
+
},
|
|
27
|
+
"type": "module",
|
|
28
|
+
"types": "./dist/index.d.mts",
|
|
29
|
+
"files": [
|
|
30
|
+
"dist",
|
|
31
|
+
"LICENSE",
|
|
32
|
+
"README.md"
|
|
33
|
+
],
|
|
34
|
+
"exports": {
|
|
35
|
+
".": {
|
|
36
|
+
"import": {
|
|
37
|
+
"types": "./dist/index.d.mts",
|
|
38
|
+
"default": "./dist/index.mjs"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"imports": {
|
|
43
|
+
"#src/*": "./src/*.js"
|
|
44
|
+
},
|
|
45
|
+
"engines": {
|
|
46
|
+
"node": ">=24.12.0"
|
|
47
|
+
}
|
|
48
|
+
}
|