elrh-cosca 0.3.6 → 0.4.0

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.
Files changed (39) hide show
  1. package/README.md +237 -99
  2. package/dist/chunks/magicast-B0ixXlDw.mjs +14033 -0
  3. package/dist/chunks/rolldown-runtime-Dqa2HsxW.mjs +20 -0
  4. package/dist/chunks/yaml-BwPmYYDT.mjs +4297 -0
  5. package/dist/elrh-cosca.mjs +328 -14157
  6. package/dist/types/src/_private/fetch-file.d.ts +1 -1
  7. package/dist/types/src/checks/get-package-manager.d.ts +2 -2
  8. package/dist/types/src/checks/has-json-key.d.ts +5 -3
  9. package/dist/types/src/checks/has-text.d.ts +6 -4
  10. package/dist/types/src/checks/has-yaml-key.d.ts +11 -0
  11. package/dist/types/src/checks/path-exists.d.ts +6 -4
  12. package/dist/types/src/functions/create-file-from-template.d.ts +10 -8
  13. package/dist/types/src/functions/create-file-from-web-template.d.ts +10 -8
  14. package/dist/types/src/functions/delete-path.d.ts +9 -7
  15. package/dist/types/src/functions/remove-from-json-file.d.ts +8 -6
  16. package/dist/types/src/functions/remove-from-text-file.d.ts +8 -6
  17. package/dist/types/src/functions/remove-from-yaml-file.d.ts +13 -0
  18. package/dist/types/src/functions/update-config-file.d.ts +13 -7
  19. package/dist/types/src/functions/update-json-file.d.ts +12 -10
  20. package/dist/types/src/functions/update-text-file.d.ts +11 -7
  21. package/dist/types/src/functions/update-yaml-file.d.ts +15 -0
  22. package/dist/types/src/main.d.ts +5 -1
  23. package/dist/types/src/terminal/prompt-user.d.ts +8 -8
  24. package/dist/types/src/terminal/show-error.d.ts +5 -3
  25. package/dist/types/src/terminal/show-message.d.ts +5 -3
  26. package/dist/types/src/types/data.d.ts +6 -0
  27. package/dist/types/src/types/functions.d.ts +99 -0
  28. package/dist/types/src/utils/get-env-value.d.ts +6 -4
  29. package/dist/types/src/utils/parse-qualified-path.d.ts +4 -2
  30. package/dist/types/src/utils/resolve-package-path.d.ts +7 -5
  31. package/dist/types/test/checks-has-yaml-key.test.d.ts +1 -0
  32. package/dist/types/test/functions-remove-from-yaml-file.test.d.ts +1 -0
  33. package/dist/types/test/functions-update-yaml-file.test.d.ts +1 -0
  34. package/dist/types/test/private-deep-merge-object.test.d.ts +1 -0
  35. package/dist/types/test/private-fetch-file.test.d.ts +1 -0
  36. package/dist/types/test/snapshots/created-config-file.d.ts +7 -0
  37. package/package.json +15 -11
  38. package/test/cosca-test.js +17 -17
  39. package/dist/types/src/types/json.d.ts +0 -6
package/README.md CHANGED
@@ -1,74 +1,113 @@
1
1
  # COSCA
2
2
 
3
- Library of file-writing functions that help building CLI scripts for making changes in target projects - like adding default configuration files or new sections in `package.json`.
3
+ Library of I/O functions helping with building CLI scripts for making changes in target projects - like adding default configuration files or new sections in `package.json`.
4
4
 
5
5
  The first experimental "customers" are my [Nuxt Spec](https://github.com/AloisSeckar/nuxt-spec) and [Nuxt Ignis](https://github.com/AloisSeckar/nuxt-ignis) projects.
6
6
 
7
- The **"COSCA"** abbreviation stands for **CO**de **SCA**ffolding which points out the library's purpose of providing methods for altering existing and adding new files from scratch using Node-based filesystem APIs.
7
+ The **"COSCA"** abbreviation stands for **CO**de **SCA**ffolding, which points out the library's purpose of providing methods for altering existing files and adding new ones from scratch using Node-based filesystem APIs.
8
8
 
9
9
  ## How to use
10
10
 
11
- **NOTE:** The library is **ESM only** and it is advised to use with at least **Node 18**.
11
+ **NOTE:** The library is **ESM only** and it is advised to use it with at least **Node 22**.
12
12
 
13
- `npm install elrh-cosca` to include into your project.
13
+ Run `npm install elrh-cosca` to include it in your project.
14
+
15
+ ### Options objects
16
+
17
+ All functions with arguments take a single required `opts` object with one or more properties. Each options type is exported from `elrh-cosca` and defined in [src/types/functions.ts](src/types/functions.ts).
18
+
19
+ ### Bundle size and lazy loading
20
+
21
+ The library has no runtime dependencies. Third-party parsers are bundled into the published package, but split into separate chunks that are only loaded on demand:
22
+
23
+ | Chunk | Approx. size | Loaded by |
24
+ | --- | --- | --- |
25
+ | `dist/elrh-cosca.mjs` (main entry) | ~19 kB | always |
26
+ | `dist/chunks/magicast-*.mjs` ([magicast](https://github.com/unjs/magicast) incl. `@babel/parser`) | ~590 kB | [`updateConfigFile`](#updateconfigfile) |
27
+ | `dist/chunks/yaml-*.mjs` ([yaml](https://github.com/eemeli/yaml)) | ~140 kB | [`updateYamlFile`](#updateyamlfile), [`removeFromYamlFile`](#removefromyamlfile), [`hasYamlKey`](#hasyamlkey) |
28
+
29
+ What this means in practice:
30
+
31
+ - **Installed package size** is (sadly) not reduced - all chunks are always downloaded with the package.
32
+ - **Nothing extra to install** - all chunks are part of the `elrh-cosca` package, so the parsers are always available and never clash with other versions in your dependency tree.
33
+ - **Running with Node** (e.g. a CLI script) - only the main entry is loaded on startup. A parser chunk is loaded via dynamic `import()` the first time a function needing it is called, and it is cached by Node afterwards.
34
+ - **Bundling into your own code** - the package is marked as `"sideEffects": false`, so unused functions can be tree-shaken. If you don't use any of the functions listed above, bundlers like Vite drop the related chunks entirely. Other bundlers may still emit them as separate files, but they are never loaded.
14
35
 
15
36
  ### List of file-manipulation functions
16
37
 
38
+ All file-manipulation options extend the following shared shape. `force` defaults to `false`. An omitted or empty `prompt` uses the function's built-in initial confirmation question.
39
+
40
+ ```ts
41
+ interface FileOperationOptions {
42
+ force?: boolean
43
+ prompt?: string
44
+ }
45
+ ```
46
+
17
47
  #### `createFileFromTemplate`
18
48
 
19
49
  ```ts
20
- async function createFileFromTemplate(
21
- templateFile: string, targetFile: string, force: boolean = false, prompt: string = ''
22
- ): Promise<void>
50
+ interface CreateFileFromTemplateOptions extends FileOperationOptions {
51
+ templateFile: string
52
+ targetFile: string
53
+ }
54
+ async function createFileFromTemplate(opts: CreateFileFromTemplateOptions): Promise<void>
23
55
  ```
24
56
 
25
- Gets a file definition from given `templateFile` and will create a fresh copy in target project.
57
+ Takes the file given by `templateFile` from an installed package and creates a fresh copy of it in the target project.
26
58
 
27
- Path to `templateFile` must be prefixed with the package name to allow proper resolution, e.g. `your-package:path/to/template`. The package name can be scoped.
59
+ Path to `templateFile` must be prefixed with the package name to allow proper resolution, e.g. `your-package:path/to/template` (the path is relative to the package root). The package name can be scoped (e.g. `@scope/package`). The package is resolved using [`resolvePackagePath`](#resolvepackagepath).
28
60
 
29
- Path to `targetFile` is relative to `process.cwd()` which allows consumers to run `npx your-script` in their project roots during development. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed. If the target directory does not exist, it will be automatically created.
61
+ Path to `targetFile` is relative to `process.cwd()`, which allows consumers to run `npx your-script` in their project roots during development. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. If the target directory does not exist, it will be created automatically.
30
62
 
31
- By default the function asks for confirmation before attempting to create the file and if the file with the same name as `targetFile` is detected. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
63
+ By default the function asks for confirmation before attempting to create the file and again if a file with the same name as `targetFile` already exists. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
32
64
 
33
65
  #### `createFileFromWebTemplate`
34
66
 
35
67
  ```ts
36
- async function createFileFromWebTemplate(
37
- url: string, targetFile: string, force: boolean = false, prompt: string = ''
38
- ): Promise<void>
68
+ interface CreateFileFromWebTemplateOptions extends FileOperationOptions {
69
+ url: string
70
+ targetFile: string
71
+ }
72
+ async function createFileFromWebTemplate(opts: CreateFileFromWebTemplateOptions): Promise<void>
39
73
  ```
40
74
 
41
- Gets a file definition from given `url` and will create a fresh copy in target project.
75
+ Downloads the file given by `url` and creates a fresh copy of it in the target project.
42
76
 
43
- Contents of `url` must be accessible via `node:https.get` function and will be fetched as raw text data.
77
+ Contents of `url` must be accessible via the `node:https.get` function and will be fetched as raw text data. Redirects (5) are followed before the fetch fails.
44
78
 
45
- Path to `targetFile` is relative to `process.cwd()` which allows consumers to run `npx your-script` in their project roots during development. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed. If the target directory does not exist, it will be automatically created.
79
+ Path to `targetFile` is relative to `process.cwd()`, which allows consumers to run `npx your-script` in their project roots during development. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. If the target directory does not exist, it will be created automatically.
46
80
 
47
- By default the function asks for confirmation before attempting to create the file and if the file with the same name as `targetFile` is detected. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
81
+ By default the function asks for confirmation before attempting to create the file and again if a file with the same name as `targetFile` already exists. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
48
82
 
49
83
  #### `updateConfigFile`
50
84
 
51
85
  ```ts
52
- async function updateConfigFile(
53
- targetFile: string, newConfig: Record<string | number | symbol, any>,
54
- force: boolean = false, prompt: string = ''
55
- ): Promise<void>
86
+ interface UpdateConfigFileOptions extends FileOperationOptions {
87
+ targetFile: string
88
+ newConfig: Record<string | number | symbol, any>
89
+ createMissing?: boolean
90
+ }
91
+ async function updateConfigFile(opts: UpdateConfigFileOptions): Promise<void>
56
92
  ```
57
93
 
58
94
  Takes a path to a configuration file and updates it with the provided `newConfig` object.
59
95
 
60
- Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed. The file currently must use ESM format with either `default` or named export of **exactly one** configuration object or function call with a configuration object as its argument.
96
+ Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file currently must use ESM format with either a `default` export or **exactly one** named export. The exported value must be a configuration object or a function call with a configuration object as its first argument (e.g. `defineConfig({...})`). CommonJS `module.exports` is not supported.
97
+
98
+ If `targetFile` does not exist, the function throws an error by default. Setting `opts.createMissing` to `true` will instead create the file (including missing directories) as `export default {}` with `newConfig` merged in. The user is asked to confirm the creation unless `opts.force` is `true`.
61
99
 
62
- The merger is performed using [unjs/magicast](https://github.com/unjs/magicast). It should:
100
+ The merge is performed using [unjs/magicast](https://github.com/unjs/magicast). It should:
63
101
 
64
102
  - preserve comments
65
103
  - work recursively to allow deep-merge
66
- - extend existing object with new keys from `newConfig`
67
- - overwrite keys with same name with values from `newConfig`
68
- - create a unique-union in case of arrays
104
+ - extend the existing object with new keys from `newConfig`
105
+ - overwrite keys with the same name with values from `newConfig`
106
+ - create a unique union in case of arrays
107
+
69
108
  Please [report](https://github.com/AloisSeckar/elrh-cosca/issues) any logical flaws and issues of the process.
70
109
 
71
- **Warning**: The function will fail, if the extracted object is proxied (e.g. when created using `defu`). In such case, the error would be:
110
+ **Warning**: The function will fail if the extracted object is proxied (e.g. when created using `defu`). In such case, the error would be:
72
111
 
73
112
  ```text
74
113
  TypeError: 'set' on proxy: trap returned falsish for property '<YOUR_PROPERTY>'
@@ -76,127 +115,207 @@ TypeError: 'set' on proxy: trap returned falsish for property '<YOUR_PROPERTY>'
76
115
 
77
116
  If possible, you need to alter your logic, e.g. by creating a new object via the spread operator.
78
117
 
79
- By default the function asks for confirmation before attempting to alter the `targetFile`. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
118
+ By default the function asks for confirmation before attempting to alter the `targetFile`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
80
119
 
81
120
  #### `updateJsonFile`
82
121
 
83
122
  ```ts
84
- async function updateJsonFile(
85
- targetFile: string, jsonKey: string, patch: JsonValue,
86
- force: boolean = false, prompt: string = ''
87
- ): Promise<void>
123
+ interface UpdateJsonFileOptions extends FileOperationOptions {
124
+ targetFile: string
125
+ jsonKey: string
126
+ patch: DataValue
127
+ createMissing?: boolean
128
+ }
129
+ async function updateJsonFile(opts: UpdateJsonFileOptions): Promise<void>
130
+ ```
131
+
132
+ Takes a path to a JSON file and injects `patch` under the `jsonKey` key. The `patch` is of type `DataValue` - a custom type defined as follows:
133
+
134
+ ```ts
135
+ type DataPrimitive = string | number | boolean | null
136
+ type DataObject = { [key: string]: DataValue }
137
+ type DataArray = DataValue[]
138
+ type DataValue = DataPrimitive | DataObject | DataArray
88
139
  ```
89
140
 
90
- Takes a path to a JSON file and injects `patch` under `jsonKey` key. A `patch` is of `JsonValue` - a custom type defined as follows:
141
+ Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must be a valid JSON file. It is parsed using plain `JSON.parse`.
142
+
143
+ The given `jsonKey` might point to a nested key using dot notation, e.g. `a.b.c`. Missing intermediate levels are created as empty objects, and existing intermediate levels that are not objects (primitives, arrays and `null`) are replaced with objects. Empty key segments (e.g. `a..b`) and the segments `__proto__`, `constructor` and `prototype` are rejected with an error. If `jsonKey` does not exist, a new key is added. If `patch` is an object, its keys are shallow-merged into the existing value. If the existing value is not an object, it is replaced. Other values (primitives, arrays and `null`) replace the existing value. The function tracks if any real change was made and notifies the user if not.
144
+
145
+ If `targetFile` does not exist, the function throws an error by default. Setting `opts.createMissing` to `true` will instead create the file (including missing directories) as an empty JSON object with `patch` applied. The user is asked to confirm the creation unless `opts.force` is `true`.
146
+
147
+ By default the function asks for confirmation before attempting to alter the `targetFile`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
148
+
149
+ #### `updateYamlFile`
91
150
 
92
151
  ```ts
93
- type JsonPrimitive = string | number | boolean | null
94
- type JsonObject = { [key: string]: JsonValue }
95
- type JsonArray = JsonValue[]
96
- type JsonValue = JsonPrimitive | JsonObject | JsonArray
152
+ interface UpdateYamlFileOptions extends FileOperationOptions {
153
+ targetFile: string
154
+ yamlKey: string
155
+ patch: DataValue
156
+ createMissing?: boolean
157
+ }
158
+ async function updateYamlFile(opts: UpdateYamlFileOptions): Promise<void>
97
159
  ```
98
160
 
99
- Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed. The file must be a valid JSON file. It is parsed using plain `JSON.parse`.
161
+ Takes a path to a YAML file and injects `patch` under the `yamlKey` key. The `patch` is of type `DataValue` (see [`updateJsonFile`](#updatejsonfile)).
162
+
163
+ Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must be a valid single-document YAML file with a map at its root (an empty file is accepted). It is parsed using [eemeli/yaml](https://github.com/eemeli/yaml) package.
164
+
165
+ The given `yamlKey` might point to a nested key using dot notation, e.g. `a.b.c`. Missing intermediate levels are created as empty maps, and existing intermediate levels that are not maps (scalars and sequences) are replaced with maps. Empty key segments (e.g. `a..b`) and the segments `__proto__`, `constructor` and `prototype` are rejected with an error. If `yamlKey` does not exist, a new key is added. If `patch` is an object, its keys are shallow-merged into the existing value. If the existing value is not a map, it is replaced. Other values (primitives, arrays and `null`) replace the existing value. The function tracks if any real change was made (values are compared deeply) and notifies the user if not.
100
166
 
101
- Currently it only allows adding new values under top-level keys. If the `jsonKey` exists, new values are merged into existing ones. Otherwise, new key is added. The function tracks if any real change was made and notifies the user if not.
167
+ If `targetFile` does not exist, the function throws an error by default. Setting `opts.createMissing` to `true` will instead create the file (including missing directories) as an empty YAML map with `patch` applied. The user is asked to confirm the creation unless `opts.force` is `true`.
102
168
 
103
- By default the function asks for confirmation before attempting to alter the `targetFile`. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
169
+ By default the function asks for confirmation before attempting to alter the `targetFile`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
104
170
 
105
171
  #### `updateTextFile`
106
172
 
107
173
  ```ts
108
- async function updateTextFile(
109
- targetFile: string, rowsToAdd: string[], force: boolean = false, prompt: string = ''
110
- ): Promise<void>
174
+ interface UpdateTextFileOptions extends FileOperationOptions {
175
+ targetFile: string
176
+ rowsToAdd: string[]
177
+ allowDuplicates?: boolean
178
+ createMissing?: boolean
179
+ }
180
+ async function updateTextFile(opts: UpdateTextFileOptions): Promise<void>
111
181
  ```
112
182
 
113
- Takes a path to a plain text file and injects `rowsToAdd` at the end of the file, **providing they are not already present in the file**. The function tracks if any real change was made and notifies the user if not.
183
+ Takes a path to a plain text file and appends `rowsToAdd` at the end of the file, **provided they are not already present in the file** (as an exact line match). Setting `opts.allowDuplicates` to `true` disables this check and all `rowsToAdd` are appended regardless of the current file contents. The function tracks if any real change was made and notifies the user if not.
114
184
 
115
- Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed.
185
+ Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed.
116
186
 
117
- By default the function asks for confirmation before attempting to alter the `targetFile`. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
187
+ If `targetFile` does not exist, the function throws an error by default. Setting `opts.createMissing` to `true` will instead create the file (including missing directories) containing `rowsToAdd`. The user is asked to confirm the creation unless `opts.force` is `true`.
188
+
189
+ By default the function asks for confirmation before attempting to alter the `targetFile`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
118
190
 
119
191
  #### `removeFromJsonFile`
120
192
 
121
193
  ```ts
122
- async function removeFromJsonFile(
123
- targetFile: string, jsonKey: string, force: boolean = false, prompt: string = ''
124
- ): Promise<void>
194
+ interface RemoveFromJsonFileOptions extends FileOperationOptions {
195
+ targetFile: string
196
+ jsonKey: string
197
+ }
198
+ async function removeFromJsonFile(opts: RemoveFromJsonFileOptions): Promise<void>
125
199
  ```
126
200
 
127
201
  Takes a path to a JSON file and removes the specified `jsonKey`.
128
202
 
129
- Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed. The file must be a valid JSON file. It is parsed using plain `JSON.parse`.
203
+ Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid JSON file. It is parsed using plain `JSON.parse`.
130
204
 
131
- Given `jsonKey` might point to a nested key using dot notation, e.g. `a.b.c`. If the key is not present, the function does nothing.
205
+ The given `jsonKey` might point to a nested key using dot notation, e.g. `a.b.c`. If the key is not present, the file is left untouched and the user is notified.
132
206
 
133
- By default the function asks for confirmation before attempting to alter the `targetFile`. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
207
+ By default the function asks for confirmation before attempting to alter the `targetFile`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
134
208
 
135
209
  #### `removeFromTextFile`
136
210
 
137
211
  ```ts
138
- async function removeFromTextFile(
139
- targetFile: string, searchText: string, force: boolean = false, prompt: string = ''
140
- ): Promise<void>
212
+ interface RemoveFromTextFileOptions extends FileOperationOptions {
213
+ targetFile: string
214
+ searchText: string
215
+ }
216
+ async function removeFromTextFile(opts: RemoveFromTextFileOptions): Promise<void>
141
217
  ```
142
218
 
143
219
  Takes a path to a plain text file and removes all lines that include the given `searchText`. The matching is done using `String.includes()`, so partial matches within a line will cause that line to be removed. The function tracks if any real change was made and notifies the user if not.
144
220
 
145
- Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed.
221
+ Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist.
222
+
223
+ By default the function asks for confirmation before attempting to alter the `targetFile`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
224
+
225
+ #### `removeFromYamlFile`
226
+
227
+ ```ts
228
+ interface RemoveFromYamlFileOptions extends FileOperationOptions {
229
+ targetFile: string
230
+ yamlKey: string
231
+ }
232
+ async function removeFromYamlFile(opts: RemoveFromYamlFileOptions): Promise<void>
233
+ ```
234
+
235
+ Takes a path to a YAML file and removes the specified `yamlKey`.
146
236
 
147
- By default the function asks for confirmation before attempting to alter the `targetFile`. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
237
+ Path to `targetFile` is relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid single-document YAML file. It is parsed using [eemeli/yaml](https://github.com/eemeli/yaml) package, which preserves comments and formatting of the untouched parts of the file.
238
+
239
+ The given `yamlKey` might point to a nested key using dot notation, e.g. `a.b.c`. If the key is not present (including when the file is empty or its root is not a map), the file is left untouched and the user is notified.
240
+
241
+ Comments attached to the removed key are removed as well, with two exceptions. A comment directly above the first root-level key is treated as a file header and kept at the top of the file. A comment left above a nested map that became empty is dropped.
242
+
243
+ By default the function asks for confirmation before attempting to alter the `targetFile`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
148
244
 
149
245
  #### `deletePath`
150
246
 
151
247
  ```ts
152
- async function deletePath(
153
- targetPath: string, force: boolean = false, prompt: string = ''
154
- ): Promise<void>
248
+ interface DeletePathOptions extends FileOperationOptions {
249
+ targetPath: string
250
+ }
251
+ async function deletePath(opts: DeletePathOptions): Promise<void>
155
252
  ```
156
253
 
157
- Deletes given `targetPath` from FS. Path is resolved relatively to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed.
254
+ Deletes the given `targetPath` (a file or a directory, recursively) from FS. Path is resolved relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed.
158
255
 
159
- If the `targetPath` does not exist, the function does nothing.
256
+ If the `targetPath` does not exist, nothing is deleted and the user is notified.
160
257
 
161
- By default the function asks for confirmation before attempting to delete the `targetPath`. Setting the last optional parameter `force` to `true` will suppress manual confirmation prompts. Passing a custom `prompt` allows tailoring your own question to the user.
258
+ By default the function asks for confirmation before attempting to delete the `targetPath`. Setting `opts.force` to `true` will suppress manual confirmation prompts. Passing `opts.prompt` allows tailoring your own initial question to the user.
162
259
 
163
260
  ### List of content checkers
164
261
 
165
262
  #### `pathExists`
166
263
 
167
264
  ```ts
168
- function pathExists(
169
- targetPath: string
170
- ): boolean
265
+ interface PathExistsOptions {
266
+ targetPath: string
267
+ }
268
+ function pathExists(opts: PathExistsOptions): boolean
171
269
  ```
172
270
 
173
- Checks if the specified `targetPath` exists on FS. Path is resolved relatively to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed.
271
+ Checks if the specified `targetPath` (a file or a directory) exists on FS. Path is resolved relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed.
174
272
 
175
- If the path exists , the function returns true, false otherwise.
273
+ If the path exists, the function returns `true`, `false` otherwise.
176
274
 
177
275
  #### `hasJsonKey`
178
276
 
179
277
  ```ts
180
- function hasJsonKey(
181
- targetFile: string, jsonKey: string
182
- ): boolean
278
+ interface HasJsonKeyOptions {
279
+ targetFile: string
280
+ jsonKey: string
281
+ }
282
+ function hasJsonKey(opts: HasJsonKeyOptions): boolean
283
+ ```
284
+
285
+ Checks whether the given `jsonKey` exists in the JSON file located at `targetFile`. Path is resolved relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid JSON file.
286
+
287
+ The given `jsonKey` might point to a nested key using dot notation, e.g. `a.b.c`. If the key is present, the function returns `true`, `false` otherwise.
288
+
289
+ #### `hasYamlKey`
290
+
291
+ ```ts
292
+ interface HasYamlKeyOptions {
293
+ targetFile: string
294
+ yamlKey: string
295
+ }
296
+ async function hasYamlKey(opts: HasYamlKeyOptions): Promise<boolean>
183
297
  ```
184
298
 
185
- Checks whether given `jsonKey` exists in JSON file located at `targetFile`. Path is resolved relatively to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed.
299
+ Checks whether the given `yamlKey` exists in the YAML file located at `targetFile`. Path is resolved relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist and be a valid single-document YAML file. It is parsed using [eemeli/yaml](https://github.com/eemeli/yaml) package.
300
+
301
+ The given `yamlKey` might point to a nested key using dot notation, e.g. `a.b.c`. If the key is present, the function resolves to `true`, `false` otherwise (including when the file is empty or its root is not a map).
186
302
 
187
- Given `jsonKey` might point to a nested key using dot notation, e.g. `a.b.c`. If the key is present, the function returns true, false otherwise.
303
+ Unlike other checks, this function runs asynchronously, because the YAML parser is loaded lazily on demand.
188
304
 
189
305
  #### `hasText`
190
306
 
191
307
  ```ts
192
- function hasText(
193
- targetFile: string, pattern: string | RegExp, exact: boolean = false
194
- ): boolean
308
+ interface HasTextOptions {
309
+ targetFile: string
310
+ pattern: string | RegExp
311
+ exact?: boolean
312
+ }
313
+ function hasText(opts: HasTextOptions): boolean
195
314
  ```
196
315
 
197
- Checks whether given `pattern` exists in text file located at `targetFile`. Path is resolved relatively to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths being passed in. Path traversal outside of CWD or providing absolute paths is disallowed.
316
+ Checks whether the given `pattern` exists in the text file located at `targetFile`. Path is resolved relative to `process.cwd()`. Several checks are in place to prevent accidental and malicious paths from being passed in. Path traversal outside of CWD and absolute paths are disallowed. The file must exist.
198
317
 
199
- The `pattern` might be a plain string or a regular expression. If it is present, the function returns true, false otherwise. By default partial matches are allowed for string patterns. If you set optional `exact` parameter to true, the string must completely match at least one line in the file. However, surrounding whitespaces are trimmed in both cases.
318
+ The `pattern` might be a plain string or a regular expression. The file is checked line by line. If the pattern is found, the function returns `true`, `false` otherwise. By default partial matches are allowed for string patterns (`exact` defaults to `false`). If you set the optional `exact` property to `true`, the string must completely match at least one line in the file. Surrounding whitespace is trimmed from the lines and from string patterns in both cases. The `exact` property is ignored for regular expressions.
200
319
 
201
320
  #### `getPackageManager`
202
321
 
@@ -204,72 +323,91 @@ The `pattern` might be a plain string or a regular expression. If it is present,
204
323
  function getPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun'
205
324
  ```
206
325
 
207
- Tries to detect the package manager used in the current environment by checking for specific global variables and user agent strings. Fallbacks to `npm` if common checks fail to detect otherwise.
326
+ Tries to detect the package manager (or runtime) used in the current environment by checking for `Deno` and `Bun` global variables and the `npm_config_user_agent` environment variable. Falls back to `npm` if the checks fail to detect anything else.
208
327
 
209
328
  ### List of terminal helpers
210
329
 
211
330
  #### `promptUser`
212
331
 
213
332
  ```ts
214
- async function promptUser(
215
- question: string,
216
- options?: { input?: NodeJS.ReadableStream; output?: NodeJS.WritableStream }
217
- ): Promise<boolean>
333
+ interface PromptUserOptions {
334
+ question: string
335
+ input?: NodeJS.ReadableStream
336
+ output?: NodeJS.WritableStream
337
+ }
338
+ async function promptUser(opts: PromptUserOptions): Promise<boolean>
218
339
  ```
219
340
 
220
- Prints out a `question` to the console and waits for the input. Returns `true` when `y` is pressed and `false` otherwise.
341
+ Prints out a `question` (with ` (y/N): ` appended) to the console and waits for the input. Returns `true` when the user answers `y` or `yes` (case-insensitive) and `false` otherwise.
221
342
 
222
- By default it uses `process.stdin` and `process.stdout` streams. To use custom NodeJS streams, `options` object with `input` and `output` properties can be optionally passed.
343
+ By default it uses `process.stdin` and `process.stdout` streams. To use custom NodeJS streams, pass `input` and `output` directly in `opts`, e.g. `promptUser({ question: 'Continue?', input, output })`.
223
344
 
224
345
  #### `showMessage`
225
346
 
226
347
  ```ts
227
- async function showMessage(message: string, newlines: number = 1): Promise<void>
348
+ interface ShowMessageOptions {
349
+ message: string
350
+ linesAfter?: number
351
+ }
352
+ function showMessage(opts: ShowMessageOptions): void
228
353
  ```
229
354
 
230
- Prints out a `message` to `process.stdout` and adds the specified number of newlines after it (default is 1).
355
+ Prints out a `message` to `process.stdout` and adds `linesAfter` newlines after it (default is 1). Set `linesAfter: 0` to omit newlines. This function is synchronous.
231
356
 
232
357
  #### `showError`
233
358
 
234
359
  ```ts
235
- async function showError(message: string, newlines: number = 1): Promise<void>
360
+ interface ShowErrorOptions {
361
+ message: string
362
+ linesAfter?: number
363
+ }
364
+ function showError(opts: ShowErrorOptions): void
236
365
  ```
237
366
 
238
- Prints out a `message` to `process.stderr` and adds the specified number of newlines after it (default is 1).
367
+ Prints out a `message` to `process.stderr` and adds `linesAfter` newlines after it (default is 1). Set `linesAfter: 0` to omit newlines. This function is synchronous.
239
368
 
240
369
  ### List of other utils
241
370
 
242
371
  #### `getEnvValue`
243
372
 
244
373
  ```ts
245
- export function getEnvValue(
246
- key: string, envFilePath: string = resolve(process.cwd(), '.env')
247
- ): string | undefined
374
+ interface GetEnvValueOptions {
375
+ key: string
376
+ envFilePath?: string
377
+ }
378
+ function getEnvValue(opts: GetEnvValueOptions): string | undefined
248
379
  ```
249
380
 
250
- Reads a `.env` file and returns the value of the specified key or `undefined` if key not found. By default it reads from `.env` in the current working directory (usually the root of the project). You can specify a custom path to `.env` file as the second `envFilePath` parameter.
381
+ Reads a `.env` file and returns the value of the specified `key` (without surrounding quotes) or `undefined` if the file or the key is not found. By default it reads from `.env` in the current working directory at call time (usually the root of the project). You can specify a custom path to the `.env` file with the `envFilePath` property.
251
382
 
252
383
  #### `parseQualifiedPath`
253
384
 
254
385
  ```ts
255
- function parseQualifiedPath(path: string): { pkg: string; file: string }
386
+ interface ParseQualifiedPathOptions {
387
+ path: string
388
+ }
389
+ function parseQualifiedPath(opts: ParseQualifiedPathOptions): { pkg: string; file: string }
256
390
  ```
257
391
 
258
- Expects path to file in `"package:relative/path/to/file"` format and splits it into `{ pkg, file }`. The package name can be scoped (e.g. `@scope/package`).
392
+ Expects a path to a file in `"package:relative/path/to/file"` format and splits it into `{ pkg, file }`. The package name can be scoped (e.g. `@scope/package`). Throws an error if the input format is invalid.
259
393
 
260
394
  #### `resolvePackagePath`
261
395
 
262
396
  ```ts
263
- function resolvePackagePath(pkg: string): string
397
+ interface ResolvePackagePathOptions {
398
+ packageName: string
399
+ }
400
+ function resolvePackagePath(opts: ResolvePackagePathOptions): string
264
401
  ```
265
402
 
266
- Resolve a package's installed root directory *from the target app* - which can be either from within itself during development or from corresponding package dir inside *node_modules*. The package name can be scoped (e.g. `@scope/package`).
403
+ Resolves a package's root directory *from the target app* (CWD). Returns CWD itself if its `package.json` has the same name (i.e. the package is being developed), otherwise looks for the package inside *node_modules* in CWD. The package name can be scoped (e.g. `@scope/package`). Throws an error if the package cannot be found.
267
404
 
268
405
  ## Tech stack
269
406
 
270
407
  - Developed with [TypeScript](https://www.typescriptlang.org/) in mind
271
408
  - Using [magicast](https://github.com/unjs/magicast) for parsing files
272
- - Build with [Vite](https://vitejs.dev/)
409
+ - Using [yaml](https://github.com/eemeli/yaml) for parsing YAML files
410
+ - Built with [Vite](https://vitejs.dev/)
273
411
  - Tested with [Vitest](https://vitest.dev/)
274
412
 
275
413
  See [Changelog](https://github.com/AloisSeckar/elrh-cosca/blob/main/CHANGELOG.md) for project history and development.