@platformatic/foundation 3.15.0 → 3.17.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.
package/index.d.ts CHANGED
@@ -101,6 +101,13 @@ export declare function replaceEnv (
101
101
  onMissingEnv?: (key: string) => string | undefined,
102
102
  ignore?: string[]
103
103
  ): RawConfiguration
104
+ export declare function validate (
105
+ schema: JSONSchemaType<any>,
106
+ config: RawConfiguration,
107
+ validationOptions?: object,
108
+ fixPaths?: boolean,
109
+ root?: string
110
+ ): void
104
111
  export declare function loadConfiguration (
105
112
  source: string | RawConfiguration,
106
113
  schema?: any,
package/index.js CHANGED
@@ -1,5 +1,6 @@
1
1
  export * from './lib/cli.js'
2
2
  export * from './lib/configuration.js'
3
+ export * from './lib/env-file-tool.js'
3
4
  export * from './lib/errors.js'
4
5
  export * as errors from './lib/errors.js'
5
6
  export * from './lib/execution.js'
package/lib/cli.js CHANGED
@@ -88,6 +88,7 @@ export function logo (color = true) {
88
88
  export function createCliLogger (level, noPretty) {
89
89
  let pretty
90
90
 
91
+ /* c8 ignore next 3 - Covered elsewhere */
91
92
  if (noPretty) {
92
93
  setPrettyPrint(false)
93
94
  } else {
@@ -1,11 +1,11 @@
1
1
  import toml from '@iarna/toml'
2
2
  import Ajv from 'ajv'
3
- import { parse as parseEnvFile } from 'dotenv'
4
3
  import jsonPatch from 'fast-json-patch'
5
4
  import JSON5 from 'json5'
6
5
  import { readFile, writeFile } from 'node:fs/promises'
7
6
  import { createRequire } from 'node:module'
8
7
  import { dirname, extname, isAbsolute, parse, resolve } from 'node:path'
8
+ import { parseEnv } from 'node:util'
9
9
  import { parse as rawParseYAML, stringify as stringifyYAML } from 'yaml'
10
10
  import {
11
11
  AddAModulePropertyToTheConfigOrAddAKnownSchemaError,
@@ -316,6 +316,30 @@ export function createValidator (schema, validationOptions, context = {}) {
316
316
  return ajv.compile(schema)
317
317
  }
318
318
 
319
+ export function validate (schema, config, validationOptions = {}, fixPaths = true, root = '') {
320
+ const validator = createValidator(schema, validationOptions, { root, fixPaths })
321
+ const valid = validator(config)
322
+
323
+ if (!valid) {
324
+ const validationErrors = []
325
+ let errors = ':'
326
+
327
+ for (const validationError of validator.errors) {
328
+ /* c8 ignore next - else */
329
+ const path = validationError.instancePath === '' ? '/' : validationError.instancePath
330
+
331
+ validationErrors.push({ path, message: validationError.message, params: validationError.params })
332
+ errors += `\n - ${path}: ${validationError.message}`
333
+ }
334
+
335
+ const error = new ConfigurationDoesNotValidateAgainstSchemaError()
336
+ error.message += errors + '\n'
337
+ Object.defineProperty(error, 'validationErrors', { value: validationErrors })
338
+
339
+ throw error
340
+ }
341
+ }
342
+
319
343
  export async function loadEnv (root, ignoreProcessEnv = false, additionalEnv = {}) {
320
344
  if (!isAbsolute(root)) {
321
345
  root = resolve(process.cwd(), root)
@@ -347,7 +371,7 @@ export async function loadEnv (root, ignoreProcessEnv = false, additionalEnv = {
347
371
  }
348
372
 
349
373
  const baseEnv = ignoreProcessEnv ? {} : process.env
350
- const envFromFile = envFile ? parseEnvFile(await readFile(envFile, 'utf-8')) : {}
374
+ const envFromFile = envFile ? parseEnv(await readFile(envFile, 'utf-8')) : {}
351
375
 
352
376
  return {
353
377
  ...baseEnv,
@@ -411,7 +435,7 @@ export function replaceEnv (config, env, onMissingEnv, ignore) {
411
435
 
412
436
  export async function loadConfiguration (source, schema, options = {}) {
413
437
  const {
414
- validate,
438
+ validate: shouldValidate,
415
439
  validationOptions,
416
440
  transform,
417
441
  upgrade,
@@ -470,32 +494,12 @@ export async function loadConfiguration (source, schema, options = {}) {
470
494
  }
471
495
  }
472
496
 
473
- if (validate) {
497
+ if (shouldValidate) {
474
498
  if (typeof schema === 'undefined') {
475
499
  throw new SourceMissingError()
476
500
  }
477
501
 
478
- const validator = createValidator(schema, validationOptions, { root, fixPaths })
479
- const valid = validator(config)
480
-
481
- if (!valid) {
482
- const validationErrors = []
483
- let errors = ':'
484
-
485
- for (const validationError of validator.errors) {
486
- /* c8 ignore next - else */
487
- const path = validationError.instancePath === '' ? '/' : validationError.instancePath
488
-
489
- validationErrors.push({ path, message: validationError.message, params: validationError.params })
490
- errors += `\n - ${path}: ${validationError.message}`
491
- }
492
-
493
- const error = new ConfigurationDoesNotValidateAgainstSchemaError()
494
- error.message += errors + '\n'
495
- Object.defineProperty(error, 'validationErrors', { value: validationErrors })
496
-
497
- throw error
498
- }
502
+ validate(schema, config, validationOptions, fixPaths, root)
499
503
  }
500
504
 
501
505
  if (!skipMetadata) {
@@ -0,0 +1,107 @@
1
+ import { existsSync } from 'node:fs'
2
+ import { readFile, writeFile } from 'node:fs/promises'
3
+ import { parseEnv } from 'node:util'
4
+
5
+ /**
6
+ * A utility class for programmatically manipulating .env files.
7
+ * This is a replacement for the dotenv-tool package using native Node.js APIs.
8
+ */
9
+ export class EnvFileTool {
10
+ constructor (options = {}) {
11
+ this.path = options.path
12
+ this.env = {}
13
+ this.loaded = false
14
+ }
15
+
16
+ /**
17
+ * Load the .env file and parse its contents
18
+ */
19
+ async load () {
20
+ if (!existsSync(this.path)) {
21
+ this.env = {}
22
+ this.loaded = true
23
+ return
24
+ }
25
+
26
+ const contents = await readFile(this.path, 'utf-8')
27
+ this.env = parseEnv(contents)
28
+ this.loaded = true
29
+ }
30
+
31
+ /**
32
+ * Save the current environment variables to the .env file
33
+ */
34
+ async save () {
35
+ const lines = []
36
+ for (const [key, value] of Object.entries(this.env)) {
37
+ // Escape values that contain special characters
38
+ const escapedValue = this._escapeValue(value)
39
+ lines.push(`${key}=${escapedValue}`)
40
+ }
41
+ await writeFile(this.path, lines.join('\n') + '\n', { encoding: 'utf-8', flush: true })
42
+ }
43
+
44
+ /**
45
+ * Get all environment variable keys
46
+ */
47
+ getKeys () {
48
+ return Object.keys(this.env)
49
+ }
50
+
51
+ /**
52
+ * Check if a key exists
53
+ */
54
+ hasKey (key) {
55
+ return key in this.env
56
+ }
57
+
58
+ /**
59
+ * Add a new key-value pair
60
+ */
61
+ addKey (key, value) {
62
+ this.env[key] = value
63
+ }
64
+
65
+ /**
66
+ * Update an existing key's value
67
+ */
68
+ updateKey (key, value) {
69
+ this.env[key] = value
70
+ }
71
+
72
+ /**
73
+ * Delete a key
74
+ */
75
+ deleteKey (key) {
76
+ delete this.env[key]
77
+ }
78
+
79
+ /**
80
+ * Get a value by key
81
+ */
82
+ getValue (key) {
83
+ return this.env[key]
84
+ }
85
+
86
+ /**
87
+ * Escape special characters in environment variable values
88
+ */
89
+ _escapeValue (value) {
90
+ if (typeof value !== 'string') {
91
+ return value
92
+ }
93
+
94
+ // If the value contains spaces, newlines, or special characters, wrap it in quotes
95
+ if (/[\s\n\r"'#\\]/.test(value)) {
96
+ // Escape existing quotes and backslashes
97
+ const escaped = value.replace(/\\/g, '\\\\').replace(/"/g, '\\"')
98
+ return `"${escaped}"`
99
+ }
100
+
101
+ return value
102
+ }
103
+ }
104
+
105
+ export function createEnvFileTool (options) {
106
+ return new EnvFileTool(options)
107
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platformatic/foundation",
3
- "version": "3.15.0",
3
+ "version": "3.17.0",
4
4
  "description": "Platformatic Foundation",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -23,7 +23,6 @@
23
23
  "ajv": "^8.12.0",
24
24
  "boring-name-generator": "^1.0.3",
25
25
  "colorette": "^2.0.19",
26
- "dotenv": "^16.4.5",
27
26
  "fast-json-patch": "^3.1.1",
28
27
  "json5": "^2.2.3",
29
28
  "leven": "~3.1.0",