elrh-cosca 0.3.5 → 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 (92) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +419 -265
  3. package/dist/chunks/magicast-B0ixXlDw.mjs +14033 -0
  4. package/dist/chunks/rolldown-runtime-Dqa2HsxW.mjs +20 -0
  5. package/dist/chunks/yaml-BwPmYYDT.mjs +4297 -0
  6. package/dist/elrh-cosca.mjs +489 -15390
  7. package/dist/types/index.d.ts +1 -1
  8. package/dist/types/src/_private/fetch-file.d.ts +1 -0
  9. package/dist/types/src/checks/get-package-manager.d.ts +6 -0
  10. package/dist/types/src/checks/has-json-key.d.ts +11 -0
  11. package/dist/types/src/checks/has-text.d.ts +12 -0
  12. package/dist/types/src/checks/has-yaml-key.d.ts +11 -0
  13. package/dist/types/src/checks/path-exists.d.ts +10 -0
  14. package/dist/types/src/functions/create-file-from-template.d.ts +13 -0
  15. package/dist/types/src/functions/create-file-from-web-template.d.ts +13 -0
  16. package/dist/types/src/functions/delete-path.d.ts +12 -0
  17. package/dist/types/src/functions/remove-from-json-file.d.ts +13 -0
  18. package/dist/types/src/functions/remove-from-text-file.d.ts +13 -0
  19. package/dist/types/src/functions/remove-from-yaml-file.d.ts +13 -0
  20. package/dist/types/src/functions/update-config-file.d.ts +20 -0
  21. package/dist/types/src/functions/update-json-file.d.ts +15 -0
  22. package/dist/types/src/functions/update-text-file.d.ts +15 -0
  23. package/dist/types/src/functions/update-yaml-file.d.ts +15 -0
  24. package/dist/types/src/main.d.ts +23 -0
  25. package/dist/types/src/terminal/prompt-user.d.ts +11 -0
  26. package/dist/types/src/terminal/show-error.d.ts +9 -0
  27. package/dist/types/src/terminal/show-message.d.ts +9 -0
  28. package/dist/types/src/types/data.d.ts +6 -0
  29. package/dist/types/src/types/functions.d.ts +99 -0
  30. package/dist/types/src/utils/get-env-value.d.ts +10 -0
  31. package/dist/types/{utils → src/utils}/parse-qualified-path.d.ts +4 -2
  32. package/dist/types/src/utils/resolve-package-path.d.ts +12 -0
  33. package/dist/types/test/checks-get-package-manager.test.d.ts +1 -0
  34. package/dist/types/test/checks-has-json-key.test.d.ts +1 -0
  35. package/dist/types/test/checks-has-text.test.d.ts +1 -0
  36. package/dist/types/test/checks-has-yaml-key.test.d.ts +1 -0
  37. package/dist/types/test/checks-path-exists.test.d.ts +1 -0
  38. package/dist/types/test/cosca-test-setup.d.ts +1 -0
  39. package/dist/types/test/cosca-test-utils.d.ts +45 -0
  40. package/dist/types/test/fixtures/config-file-default.d.ts +10 -0
  41. package/dist/types/test/fixtures/config-file-named.d.ts +9 -0
  42. package/dist/types/test/functions-create-file-from-template.test.d.ts +1 -0
  43. package/dist/types/test/functions-create-file-from-web-template.test.d.ts +1 -0
  44. package/dist/types/test/functions-delete-path.test.d.ts +1 -0
  45. package/dist/types/test/functions-remove-from-json-file.test.d.ts +1 -0
  46. package/dist/types/test/functions-remove-from-text-file.test.d.ts +1 -0
  47. package/dist/types/test/functions-remove-from-yaml-file.test.d.ts +1 -0
  48. package/dist/types/test/functions-update-config-file.test.d.ts +1 -0
  49. package/dist/types/test/functions-update-json-file.test.d.ts +1 -0
  50. package/dist/types/test/functions-update-text-file.test.d.ts +1 -0
  51. package/dist/types/test/functions-update-yaml-file.test.d.ts +1 -0
  52. package/dist/types/test/private-check-path.test.d.ts +1 -0
  53. package/dist/types/test/private-deep-merge-object.test.d.ts +1 -0
  54. package/dist/types/test/private-fetch-file.test.d.ts +1 -0
  55. package/dist/types/test/snapshots/created-config-file.d.ts +7 -0
  56. package/dist/types/test/snapshots/updated-config-file-default-1.d.ts +13 -0
  57. package/dist/types/test/snapshots/updated-config-file-default-2.d.ts +14 -0
  58. package/dist/types/test/snapshots/updated-config-file-default-3.d.ts +16 -0
  59. package/dist/types/test/snapshots/updated-config-file-default-4.d.ts +18 -0
  60. package/dist/types/test/snapshots/updated-config-file-named-1.d.ts +12 -0
  61. package/dist/types/test/snapshots/updated-config-file-named-2.d.ts +13 -0
  62. package/dist/types/test/snapshots/updated-config-file-named-3.d.ts +15 -0
  63. package/dist/types/test/snapshots/updated-config-file-named-4.d.ts +17 -0
  64. package/dist/types/test/terminal-prompt-user.test.d.ts +1 -0
  65. package/dist/types/test/terninal-show-error.test.d.ts +1 -0
  66. package/dist/types/test/terninal-show-message.test.d.ts +1 -0
  67. package/dist/types/test/utils-get-env-value.test.d.ts +1 -0
  68. package/dist/types/test/utils-parse-qualified-path.test.d.ts +1 -0
  69. package/dist/types/test/utils-resolve-package-path.test.d.ts +1 -0
  70. package/package.json +15 -11
  71. package/test/cosca-test.js +55 -55
  72. package/dist/types/_private/fetch-file.d.ts +0 -1
  73. package/dist/types/checks/get-package-manager.d.ts +0 -6
  74. package/dist/types/checks/has-json-key.d.ts +0 -9
  75. package/dist/types/checks/has-text.d.ts +0 -10
  76. package/dist/types/checks/path-exists.d.ts +0 -8
  77. package/dist/types/functions/create-file-from-template.d.ts +0 -11
  78. package/dist/types/functions/create-file-from-web-template.d.ts +0 -11
  79. package/dist/types/functions/delete-path.d.ts +0 -10
  80. package/dist/types/functions/remove-from-json-file.d.ts +0 -11
  81. package/dist/types/functions/update-config-file.d.ts +0 -14
  82. package/dist/types/functions/update-json-file.d.ts +0 -13
  83. package/dist/types/functions/update-text-file.d.ts +0 -11
  84. package/dist/types/main.d.ts +0 -18
  85. package/dist/types/terminal/prompt-user.d.ts +0 -11
  86. package/dist/types/terminal/show-error.d.ts +0 -7
  87. package/dist/types/terminal/show-message.d.ts +0 -7
  88. package/dist/types/types/json.d.ts +0 -6
  89. package/dist/types/utils/get-env-value.d.ts +0 -8
  90. package/dist/types/utils/resolve-package-path.d.ts +0 -10
  91. /package/dist/types/{_private → src/_private}/check-path.d.ts +0 -0
  92. /package/dist/types/{_private → src/_private}/deep-merge-object.d.ts +0 -0
package/README.md CHANGED
@@ -1,265 +1,419 @@
1
- # COSCA
2
- 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
-
4
- The first experimental "customers" are my [Nuxt Spec](https://github.com/AloisSeckar/nuxt-spec) and [Nuxt Ignis](https://github.com/AloisSeckar/nuxt-ignis) projects.
5
-
6
- 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
-
8
- ## How to use
9
-
10
- **NOTE:** The library is **ESM only** and it is advised to use with at least **Node 18**.
11
-
12
- `npm install elrh-cosca` to include into your project.
13
-
14
- ### List of file-manipulation functions
15
-
16
- #### `createFileFromTemplate`
17
-
18
- ```ts
19
- async function createFileFromTemplate(
20
- templateFile: string, targetFile: string, force: boolean = false, prompt: string = ''
21
- ): Promise<void>
22
- ```
23
-
24
- Gets a file definition from given `templateFile` and will create a fresh copy in target project.
25
-
26
- 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.
27
-
28
- 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.
29
-
30
- 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.
31
-
32
- #### `createFileFromWebTemplate`
33
-
34
- ```ts
35
- async function createFileFromWebTemplate(
36
- url: string, targetFile: string, force: boolean = false, prompt: string = ''
37
- ): Promise<void>
38
- ```
39
-
40
- Gets a file definition from given `url` and will create a fresh copy in target project.
41
-
42
- Contents of `url` must be accessible via `node:https.get` function and will be fetched as raw text data.
43
-
44
- 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.
45
-
46
- 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.
47
-
48
- #### `updateConfigFile`
49
-
50
- ```ts
51
- async function updateConfigFile(
52
- targetFile: string, newConfig: Record<string | number | symbol, any>,
53
- force: boolean = false, prompt: string = ''
54
- ): Promise<void>
55
- ```
56
-
57
- Takes a path to a configuration file and updates it with the provided `newConfig` object.
58
-
59
- 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.
60
-
61
- The merger is performed using [unjs/magicast](https://github.com/unjs/magicast). It should:
62
- - preserve comments
63
- - work recursively to allow deep-merge
64
- - extend existing object with new keys from `newConfig`
65
- - overwrite keys with same name with values from `newConfig`
66
- - create a unique-union in case of arrays
67
- Please [report](https://github.com/AloisSeckar/elrh-cosca/issues) any logical flaws and issues of the process.
68
-
69
- **Warning**: The function will fail, if the extracted object is proxied (e.g. when created using `defu`). In such case, the error would be:
70
-
71
- ```
72
- TypeError: 'set' on proxy: trap returned falsish for property '<YOUR_PROPERTY>'
73
- ```
74
-
75
- If possible, you need to alter your logic, e.g. by creating a new object via the spread operator.
76
-
77
-
78
- 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.
79
-
80
- #### `updateJsonFile`
81
-
82
- ```ts
83
- async function updateJsonFile(
84
- targetFile: string, jsonKey: string, patch: JsonValue,
85
- force: boolean = false, prompt: string = ''
86
- ): Promise<void>
87
- ```
88
-
89
- Takes a path to a JSON file and injects `patch` under `jsonKey` key. A `patch` is of `JsonValue` - a custom type defined as follows:
90
-
91
- ```ts
92
- type JsonPrimitive = string | number | boolean | null
93
- type JsonObject = { [key: string]: JsonValue }
94
- type JsonArray = JsonValue[]
95
- type JsonValue = JsonPrimitive | JsonObject | JsonArray
96
- ```
97
-
98
- 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`.
99
-
100
- 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.
101
-
102
- 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.
103
-
104
- #### `updateTextFile`
105
-
106
- ```ts
107
- async function updateTextFile(
108
- targetFile: string, rowsToAdd: string[], force: boolean = false, prompt: string = ''
109
- ): Promise<void>
110
- ```
111
-
112
- 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.
113
-
114
- 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.
115
-
116
- 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.
117
-
118
- #### `removeFromJsonFile`
119
-
120
- ```ts
121
- async function removeFromJsonFile(
122
- targetFile: string, jsonKey: string, force: boolean = false, prompt: string = ''
123
- ): Promise<void>
124
- ```
125
-
126
- Takes a path to a JSON file and removes the specified `jsonKey`.
127
-
128
- 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`.
129
-
130
- 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.
131
-
132
- 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.
133
-
134
- #### `deletePath`
135
-
136
- ```ts
137
- async function deletePath(
138
- targetPath: string, force: boolean = false, prompt: string = ''
139
- ): Promise<void>
140
- ```
141
-
142
- 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.
143
-
144
- If the `targetPath` does not exist, the function does nothing.
145
-
146
- 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.
147
-
148
- ### List of content checkers
149
-
150
- #### `pathExists`
151
-
152
- ```ts
153
- function pathExists(
154
- targetPath: string
155
- ): boolean
156
- ```
157
-
158
- 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.
159
-
160
- If the path exists , the function returns true, false otherwise.
161
-
162
- #### `hasJsonKey`
163
-
164
- ```ts
165
- function hasJsonKey(
166
- targetFile: string, jsonKey: string
167
- ): boolean
168
- ```
169
-
170
- 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.
171
-
172
- 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.
173
-
174
- #### `hasText`
175
-
176
- ```ts
177
- function hasText(
178
- targetFile: string, pattern: string | RegExp, exact: boolean = false
179
- ): boolean
180
- ```
181
-
182
- 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.
183
-
184
- 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.
185
-
186
- #### `getPackageManager`
187
-
188
- ```ts
189
- function getPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun'
190
- ```
191
-
192
- 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.
193
-
194
- ### List of terminal helpers
195
-
196
- #### `promptUser`
197
-
198
- ```ts
199
- async function promptUser(
200
- question: string,
201
- options?: { input?: NodeJS.ReadableStream; output?: NodeJS.WritableStream }
202
- ): Promise<boolean>
203
- ```
204
-
205
- Prints out a `question` to the console and waits for the input. Returns `true` when `y` is pressed and `false` otherwise.
206
-
207
- 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.
208
-
209
- #### `showMessage`
210
-
211
- ```ts
212
- async function showMessage(message: string, newlines: number = 1): Promise<void>
213
- ```
214
-
215
- Prints out a `message` to `process.stdout` and adds the specified number of newlines after it (default is 1).
216
-
217
- #### `showError`
218
-
219
- ```ts
220
- async function showError(message: string, newlines: number = 1): Promise<void>
221
- ```
222
-
223
- Prints out a `message` to `process.stderr` and adds the specified number of newlines after it (default is 1).
224
-
225
- ### List of other utils
226
-
227
- #### `getEnvValue`
228
-
229
- ```ts
230
- export function getEnvValue(
231
- key: string, envFilePath: string = resolve(process.cwd(), '.env')
232
- ): string | undefined
233
- ```
234
-
235
- 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.
236
-
237
- #### `parseQualifiedPath`
238
-
239
- ```ts
240
- function parseQualifiedPath(path: string): { pkg: string; file: string }
241
- ```
242
-
243
- 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`).
244
-
245
- #### `resolvePackagePath`
246
-
247
- ```ts
248
- function resolvePackagePath(pkg: string): string
249
- ```
250
-
251
- 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`).
252
-
253
- ## Tech stack
254
-
255
- - Developed with [TypeScript](https://www.typescriptlang.org/) in mind
256
- - Using [magicast](https://github.com/unjs/magicast) for parsing files
257
- - Build with [Vite](https://vitejs.dev/)
258
- - Tested with [Vitest](https://vitest.dev/)
259
-
260
- See [Changelog](https://github.com/AloisSeckar/elrh-cosca/blob/main/CHANGELOG.md) for project history and development.
261
-
262
- ## Report bugs & contact
263
-
264
- Use GitHub issues to report bugs / propose enhancements / give feedback:
265
- https://github.com/AloisSeckar/elrh-cosca/issues
1
+ # COSCA
2
+
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
+
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
+
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
+
9
+ ## How to use
10
+
11
+ **NOTE:** The library is **ESM only** and it is advised to use it with at least **Node 22**.
12
+
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.
35
+
36
+ ### List of file-manipulation functions
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
+
47
+ #### `createFileFromTemplate`
48
+
49
+ ```ts
50
+ interface CreateFileFromTemplateOptions extends FileOperationOptions {
51
+ templateFile: string
52
+ targetFile: string
53
+ }
54
+ async function createFileFromTemplate(opts: CreateFileFromTemplateOptions): Promise<void>
55
+ ```
56
+
57
+ Takes the file given by `templateFile` from an installed package and creates a fresh copy of it in the target project.
58
+
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).
60
+
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.
62
+
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.
64
+
65
+ #### `createFileFromWebTemplate`
66
+
67
+ ```ts
68
+ interface CreateFileFromWebTemplateOptions extends FileOperationOptions {
69
+ url: string
70
+ targetFile: string
71
+ }
72
+ async function createFileFromWebTemplate(opts: CreateFileFromWebTemplateOptions): Promise<void>
73
+ ```
74
+
75
+ Downloads the file given by `url` and creates a fresh copy of it in the target project.
76
+
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.
78
+
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.
80
+
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.
82
+
83
+ #### `updateConfigFile`
84
+
85
+ ```ts
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>
92
+ ```
93
+
94
+ Takes a path to a configuration file and updates it with the provided `newConfig` object.
95
+
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`.
99
+
100
+ The merge is performed using [unjs/magicast](https://github.com/unjs/magicast). It should:
101
+
102
+ - preserve comments
103
+ - work recursively to allow deep-merge
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
+
108
+ Please [report](https://github.com/AloisSeckar/elrh-cosca/issues) any logical flaws and issues of the process.
109
+
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:
111
+
112
+ ```text
113
+ TypeError: 'set' on proxy: trap returned falsish for property '<YOUR_PROPERTY>'
114
+ ```
115
+
116
+ If possible, you need to alter your logic, e.g. by creating a new object via the spread operator.
117
+
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.
119
+
120
+ #### `updateJsonFile`
121
+
122
+ ```ts
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
139
+ ```
140
+
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`
150
+
151
+ ```ts
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>
159
+ ```
160
+
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.
166
+
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`.
168
+
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.
170
+
171
+ #### `updateTextFile`
172
+
173
+ ```ts
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>
181
+ ```
182
+
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.
184
+
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.
186
+
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.
190
+
191
+ #### `removeFromJsonFile`
192
+
193
+ ```ts
194
+ interface RemoveFromJsonFileOptions extends FileOperationOptions {
195
+ targetFile: string
196
+ jsonKey: string
197
+ }
198
+ async function removeFromJsonFile(opts: RemoveFromJsonFileOptions): Promise<void>
199
+ ```
200
+
201
+ Takes a path to a JSON file and removes the specified `jsonKey`.
202
+
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`.
204
+
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.
206
+
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.
208
+
209
+ #### `removeFromTextFile`
210
+
211
+ ```ts
212
+ interface RemoveFromTextFileOptions extends FileOperationOptions {
213
+ targetFile: string
214
+ searchText: string
215
+ }
216
+ async function removeFromTextFile(opts: RemoveFromTextFileOptions): Promise<void>
217
+ ```
218
+
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.
220
+
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`.
236
+
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.
244
+
245
+ #### `deletePath`
246
+
247
+ ```ts
248
+ interface DeletePathOptions extends FileOperationOptions {
249
+ targetPath: string
250
+ }
251
+ async function deletePath(opts: DeletePathOptions): Promise<void>
252
+ ```
253
+
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.
255
+
256
+ If the `targetPath` does not exist, nothing is deleted and the user is notified.
257
+
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.
259
+
260
+ ### List of content checkers
261
+
262
+ #### `pathExists`
263
+
264
+ ```ts
265
+ interface PathExistsOptions {
266
+ targetPath: string
267
+ }
268
+ function pathExists(opts: PathExistsOptions): boolean
269
+ ```
270
+
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.
272
+
273
+ If the path exists, the function returns `true`, `false` otherwise.
274
+
275
+ #### `hasJsonKey`
276
+
277
+ ```ts
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>
297
+ ```
298
+
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).
302
+
303
+ Unlike other checks, this function runs asynchronously, because the YAML parser is loaded lazily on demand.
304
+
305
+ #### `hasText`
306
+
307
+ ```ts
308
+ interface HasTextOptions {
309
+ targetFile: string
310
+ pattern: string | RegExp
311
+ exact?: boolean
312
+ }
313
+ function hasText(opts: HasTextOptions): boolean
314
+ ```
315
+
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.
317
+
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.
319
+
320
+ #### `getPackageManager`
321
+
322
+ ```ts
323
+ function getPackageManager(): 'npm' | 'yarn' | 'pnpm' | 'deno' | 'bun'
324
+ ```
325
+
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.
327
+
328
+ ### List of terminal helpers
329
+
330
+ #### `promptUser`
331
+
332
+ ```ts
333
+ interface PromptUserOptions {
334
+ question: string
335
+ input?: NodeJS.ReadableStream
336
+ output?: NodeJS.WritableStream
337
+ }
338
+ async function promptUser(opts: PromptUserOptions): Promise<boolean>
339
+ ```
340
+
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.
342
+
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 })`.
344
+
345
+ #### `showMessage`
346
+
347
+ ```ts
348
+ interface ShowMessageOptions {
349
+ message: string
350
+ linesAfter?: number
351
+ }
352
+ function showMessage(opts: ShowMessageOptions): void
353
+ ```
354
+
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.
356
+
357
+ #### `showError`
358
+
359
+ ```ts
360
+ interface ShowErrorOptions {
361
+ message: string
362
+ linesAfter?: number
363
+ }
364
+ function showError(opts: ShowErrorOptions): void
365
+ ```
366
+
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.
368
+
369
+ ### List of other utils
370
+
371
+ #### `getEnvValue`
372
+
373
+ ```ts
374
+ interface GetEnvValueOptions {
375
+ key: string
376
+ envFilePath?: string
377
+ }
378
+ function getEnvValue(opts: GetEnvValueOptions): string | undefined
379
+ ```
380
+
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.
382
+
383
+ #### `parseQualifiedPath`
384
+
385
+ ```ts
386
+ interface ParseQualifiedPathOptions {
387
+ path: string
388
+ }
389
+ function parseQualifiedPath(opts: ParseQualifiedPathOptions): { pkg: string; file: string }
390
+ ```
391
+
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.
393
+
394
+ #### `resolvePackagePath`
395
+
396
+ ```ts
397
+ interface ResolvePackagePathOptions {
398
+ packageName: string
399
+ }
400
+ function resolvePackagePath(opts: ResolvePackagePathOptions): string
401
+ ```
402
+
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.
404
+
405
+ ## Tech stack
406
+
407
+ - Developed with [TypeScript](https://www.typescriptlang.org/) in mind
408
+ - Using [magicast](https://github.com/unjs/magicast) for parsing files
409
+ - Using [yaml](https://github.com/eemeli/yaml) for parsing YAML files
410
+ - Built with [Vite](https://vitejs.dev/)
411
+ - Tested with [Vitest](https://vitest.dev/)
412
+
413
+ See [Changelog](https://github.com/AloisSeckar/elrh-cosca/blob/main/CHANGELOG.md) for project history and development.
414
+
415
+ ## Report bugs & contact
416
+
417
+ Use GitHub issues to report bugs / propose enhancements / give feedback:
418
+
419
+ [https://github.com/AloisSeckar/elrh-cosca/issues](https://github.com/AloisSeckar/elrh-cosca/issues)