doc-detective-common 3.0.0-dev.0 → 3.0.0-dev.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "doc-detective-common",
3
- "version": "3.0.0-dev.0",
3
+ "version": "3.0.0-dev.1",
4
4
  "description": "Shared components for Doc Detective projects.",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -13,7 +13,7 @@
13
13
  "url": "git+https://github.com/doc-detective/doc-detective-common.git"
14
14
  },
15
15
  "author": "Manny Silva",
16
- "license": "MIT",
16
+ "license": "AGPL-3.0-only",
17
17
  "bugs": {
18
18
  "url": "https://github.com/doc-detective/doc-detective-common/issues"
19
19
  },
@@ -21,16 +21,16 @@
21
21
  "devDependencies": {
22
22
  "chai": "^5.2.0",
23
23
  "mocha": "^11.1.0",
24
- "sinon": "^19.0.2"
24
+ "sinon": "^20.0.0"
25
25
  },
26
26
  "dependencies": {
27
- "@apidevtools/json-schema-ref-parser": "^11.9.3",
27
+ "@apidevtools/json-schema-ref-parser": "^12.0.1",
28
28
  "ajv": "^8.17.1",
29
29
  "ajv-errors": "^3.0.0",
30
30
  "ajv-formats": "^3.0.1",
31
31
  "ajv-keywords": "^5.1.0",
32
- "axios": "^1.8.2",
32
+ "axios": "^1.8.4",
33
33
  "uuid": "^11.1.0",
34
- "yaml": "^2.7.0"
34
+ "yaml": "^2.7.1"
35
35
  }
36
36
  }
package/src/files.js CHANGED
@@ -7,42 +7,54 @@ const { URL } = require("url");
7
7
  * Reads a file from a given URL or local file path and returns its content.
8
8
  * Supports JSON and YAML file formats.
9
9
  *
10
- * @param {string} fileURL - The URL or local file path of the file to read.
11
- * @returns {Promise<Object|string|null>} - The parsed content of the file if it's JSON or YAML,
12
- * the raw content if it's another format,
10
+ * @param {string} fileURLOrPath - The URL or local file path of the file to read.
11
+ * @returns {Promise<Object|string|null>} - The parsed content of the file if it's JSON or YAML,
12
+ * the raw content if it's another format,
13
13
  * or null if an error occurs.
14
14
  */
15
- async function readFile(fileURL) {
15
+ async function readFile({ fileURLOrPath }) {
16
+ if (!fileURLOrPath) {
17
+ throw new Error("fileURLOrPath is required");
18
+ }
19
+ if (typeof fileURLOrPath !== "string") {
20
+ throw new Error("fileURLOrPath must be a string");
21
+ }
22
+ if (fileURLOrPath.trim() === "") {
23
+ throw new Error("fileURLOrPath cannot be an empty string");
24
+ }
16
25
 
17
26
  let content;
18
27
  let isRemote = false;
19
28
 
20
29
  try {
21
- const parsedURL = new URL(fileURL);
22
- isRemote = parsedURL.protocol === "http:" || parsedURL.protocol === "https:";
30
+ const parsedURL = new URL(fileURLOrPath);
31
+ isRemote =
32
+ parsedURL.protocol === "http:" || parsedURL.protocol === "https:";
23
33
  } catch (error) {
24
34
  // Not a valid URL, assume local file path
25
35
  }
26
36
 
27
37
  if (isRemote) {
28
38
  try {
29
- const response = await axios.get(fileURL);
39
+ const response = await axios.get(fileURLOrPath);
30
40
  content = response.data;
31
41
  } catch (error) {
32
- console.warn(`Error reading remote file from ${fileURL}: ${error.message}`);
42
+ console.warn(
43
+ `Error reading remote file from ${fileURLOrPath}: ${error.message}`
44
+ );
45
+ return null;
46
+ }
47
+ } else {
48
+ try {
49
+ content = await fs.promises.readFile(fileURLOrPath, "utf8");
50
+ } catch (error) {
51
+ if (error.code === "ENOENT") {
52
+ console.warn(`File not found: ${fileURLOrPath}`);
53
+ } else {
54
+ console.warn(`Error reading file: ${error.message}`);
55
+ }
33
56
  return null;
34
57
  }
35
- } else {
36
- try {
37
- content = await fs.promises.readFile(fileURL, "utf8");
38
- } catch (error) {
39
- if (error.code === 'ENOENT') {
40
- console.warn(`File not found: ${fileURL}`);
41
- } else {
42
- console.warn(`Error reading file: ${error.message}`);
43
- }
44
- return null;
45
- }
46
58
  }
47
59
 
48
60
  // Parse based on file content, and return either object or string
@@ -58,7 +70,6 @@ async function readFile(fileURL) {
58
70
  return content;
59
71
  }
60
72
  }
61
-
62
73
  }
63
74
 
64
75
  module.exports = { readFile };
package/src/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const { schemas } = require("./schemas");
2
- const { validate } = require("./validate");
2
+ const { validate, transformToSchemaKey } = require("./validate");
3
3
  const { resolvePaths } = require("./resolvePaths");
4
4
  const { readFile } = require("./files");
5
5
 
@@ -8,4 +8,5 @@ module.exports = {
8
8
  validate,
9
9
  resolvePaths,
10
10
  readFile,
11
+ transformToSchemaKey,
11
12
  };
@@ -5,22 +5,29 @@ const { validate } = require("./validate");
5
5
  exports.resolvePaths = resolvePaths;
6
6
 
7
7
  /**
8
- * Resolves paths in config and spec objects based on the provided configuration, object type, object, and source file path.
8
+ * Resolves relative paths in configuration and specification objects to absolute paths.
9
9
  *
10
- * @param {object} config - The configuration object.
11
- * @param {object} object - The object containing the paths to resolve.
12
- * @param {string} filePath - The path of the file that contains the relative paths.
13
- * @param {boolean} [nested=false] - Whether the object is nested within another object.
14
- * @param {string} [objectType] - The type of object to resolve paths in ("config" or "spec"). Required if the object is nested.
15
- * @returns {object} - The object with resolved paths.
10
+ * This function traverses an object (either a config or spec) and converts all path properties
11
+ * to absolute paths based on the provided configuration and file path. It can handle nested objects
12
+ * and special path relationships like path/directory and savePath/saveDirectory.
13
+ *
14
+ * @async
15
+ * @param {Object} options - The options object.
16
+ * @param {Object} options.config - The configuration object containing settings like relativePathBase.
17
+ * @param {Object} options.object - The object whose paths need to be resolved.
18
+ * @param {string} options.filePath - The reference file path for resolving relative paths.
19
+ * @param {boolean} [options.nested=false] - Flag indicating if this is a recursive call for a nested object.
20
+ * @param {string} [options.objectType] - The type of object ('config' or 'spec'). Required for nested objects.
21
+ * @returns {Promise<Object>} The object with all paths resolved to absolute paths.
22
+ * @throws {Error} Throws an error if the object isn't a valid config or spec, or if objectType is missing for nested objects.
16
23
  */
17
- async function resolvePaths(
24
+ async function resolvePaths({
18
25
  config,
19
26
  object,
20
27
  filePath,
21
28
  nested = false,
22
- objectType
23
- ) {
29
+ objectType,
30
+ }) {
24
31
  // Config properties that contain paths
25
32
  const configPaths = [
26
33
  "input",
@@ -38,6 +45,9 @@ async function resolvePaths(
38
45
  "file",
39
46
  "path",
40
47
  "directory",
48
+ "before",
49
+ "after",
50
+ "loadVariables",
41
51
  "setup",
42
52
  "cleanup",
43
53
  "savePath",
@@ -68,13 +78,19 @@ async function resolvePaths(
68
78
  if (path.isAbsolute(relativePath)) {
69
79
  return relativePath;
70
80
  }
71
- // If filePath is a file, use its directory as the base path
72
- filePath = fs.lstatSync(filePath).isFile()
73
- ? path.dirname(filePath)
74
- : filePath;
81
+
82
+ // Check if filePath exists and is a file
83
+ const fileExists = fs.existsSync(filePath);
84
+ const isFile = fileExists
85
+ ? fs.lstatSync(filePath).isFile()
86
+ : path.parse(filePath).ext !== "";
87
+
88
+ // Use directory of filePath if it's a file (or looks like one)
89
+ const basePath = isFile ? path.dirname(filePath) : filePath;
90
+
75
91
  // Resolve the path based on the base type
76
92
  return baseType === "file"
77
- ? path.resolve(filePath, relativePath)
93
+ ? path.resolve(basePath, relativePath)
78
94
  : path.resolve(relativePath);
79
95
  }
80
96
 
@@ -83,13 +99,19 @@ async function resolvePaths(
83
99
  let pathProperties;
84
100
  if (!nested && !objectType) {
85
101
  // Check if object matches the config schema
86
- const validation = validate("config_v2", { ...object });
102
+ const validation = validate({
103
+ schemaKey: "config_v3",
104
+ object: { ...object },
105
+ });
87
106
  if (validation.valid) {
88
107
  pathProperties = configPaths;
89
108
  objectType = "config";
90
109
  } else {
91
110
  // Check if object matches the spec schema
92
- const validation = validate("spec_v2", { ...object });
111
+ const validation = validate({
112
+ schemaKey: "spec_v3",
113
+ object: { ...object },
114
+ });
93
115
  if (validation.valid) {
94
116
  pathProperties = specPaths;
95
117
  objectType = "spec";
@@ -115,13 +137,13 @@ async function resolvePaths(
115
137
  objectType === "config")
116
138
  ) {
117
139
  // If the property is an object, recursively call resolvePaths to resolve paths within the object
118
- object[property] = await resolvePaths(
119
- config,
120
- object[property],
121
- filePath,
122
- true,
123
- objectType
124
- );
140
+ object[property] = await resolvePaths({
141
+ config: config,
142
+ object: object[property],
143
+ filePath: filePath,
144
+ nested: true,
145
+ objectType: objectType,
146
+ });
125
147
  } else if (typeof object[property] === "string") {
126
148
  // If the property is a string, check if it matches any of the path properties and resolve it if it does
127
149
  pathProperties.forEach((pathProperty) => {
@@ -133,24 +155,6 @@ async function resolvePaths(
133
155
  object[pathProperty],
134
156
  object.directory
135
157
  );
136
- } else {
137
- object[pathProperty] = path.join(
138
- object.directory,
139
- object[pathProperty]
140
- );
141
- }
142
- } else if (pathProperty === "savePath" && object.saveDirectory) {
143
- if (path.isAbsolute(object.saveDirectory)) {
144
- object[pathProperty] = resolve(
145
- relativePathBase,
146
- object[pathProperty],
147
- object.saveDirectory
148
- );
149
- } else {
150
- object[pathProperty] = path.join(
151
- object.saveDirectory,
152
- object[pathProperty]
153
- );
154
158
  }
155
159
  } else {
156
160
  object[pathProperty] = resolve(