diff --git a/jest.config.js b/jest.config.js index 016be5863..51f0cf3a6 100644 --- a/jest.config.js +++ b/jest.config.js @@ -10,10 +10,6 @@ module.exports = { }, ], }, - modulePathIgnorePatterns: [ - './build', - './src/__tests__/integration/helpers', - './src/__tests__/integration/test-data', - ], - coveragePathIgnorePatterns: ['src/config', 'src/types'], + modulePathIgnorePatterns: ['./build'], + coveragePathIgnorePatterns: ['src/.*/config/.*', 'src/.*/types/.*'], }; diff --git a/manifests/outputs/bugs/initialize-error-no-config.yaml b/manifests/outputs/bugs/initialize-error-no-config.yaml deleted file mode 100644 index 56be9add7..000000000 --- a/manifests/outputs/bugs/initialize-error-no-config.yaml +++ /dev/null @@ -1,78 +0,0 @@ -name: initialize-error-no-config -description: >- - a negative test case that fails due to plugin initialization missing some - required config -tags: null -initialize: - outputs: - - yaml - plugins: - interpolate: - method: Interpolation - path: builtin - global-config: null -execution: - status: fail - command: >- - /Users/mariamkhalatova/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node - /Users/mariamkhalatova/Projects/UK/if/src/index.ts -m - manifests/outputs/bugs/initialize-error-no-config.yml -o - manifests/outputs/bugs/initialize-error-no-config - environment: - if-version: 0.4.0 - os: macOS - os-version: '13.2' - node-version: 18.14.2 - date-time: 2024-07-01T19:45:41.936Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - error: >- - ManifestValidationError: "initialize.plugins.interpolate.global-config" - parameter is expected object, received null. Error code: invalid_type. -tree: - children: - child-0: - defaults: - cpu/thermal-design-power: 100 - pipeline: - - interpolate - inputs: - - timestamp: 2023-07-06T00:00 - duration: 1 - cpu/utilization: 20 - - timestamp: 2023-07-06T00:01 - duration: 1 - cpu/utilization: 80 - - timestamp: 2023-07-06T00:02 - duration: 1 - cpu/utilization: 20 diff --git a/manifests/outputs/bugs/initialize-error-no-path.yaml b/manifests/outputs/bugs/initialize-error-no-path.yaml deleted file mode 100644 index e2ee4d616..000000000 --- a/manifests/outputs/bugs/initialize-error-no-path.yaml +++ /dev/null @@ -1,91 +0,0 @@ -name: initialize-error-no-path -description: >- - a negative test case that fails because the path is mising in a plugin - initialization -tags: null -initialize: - plugins: - interpolate: - method: Interpolation - path: null - global-config: - method: linear - x: - - 0 - - 10 - - 50 - - 100 - 'y': - - 0.12 - - 0.32 - - 0.75 - - 1.02 - input-parameter: cpu/utilization - output-parameter: cpu-factor - outputs: - - yaml -execution: - status: fail - command: >- - /Users/mariamkhalatova/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node - /Users/mariamkhalatova/Projects/UK/if/src/index.ts -m - manifests/outputs/bugs/initialize-error-no-path.yml -o - manifests/outputs/bugs/initialize-error-no-path - environment: - if-version: 0.4.0 - os: macOS - os-version: '13.2' - node-version: 18.14.2 - date-time: 2024-07-01T19:57:11.499Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - error: >- - ManifestValidationError: "initialize.plugins.interpolate.path" parameter is - expected string, received null. Error code: invalid_type. -tree: - children: - child-0: - defaults: - cpu/thermal-design-power: 100 - pipeline: - - interpolate - inputs: - - timestamp: 2023-07-06T00:00 - duration: 1 - cpu/utilization: 20 - - timestamp: 2023-07-06T00:01 - duration: 1 - cpu/utilization: 80 - - timestamp: 2023-07-06T00:02 - duration: 1 - cpu/utilization: 20 diff --git a/manifests/outputs/bugs/initialize-error-no-plugins.yaml b/manifests/outputs/bugs/initialize-error-no-plugins.yaml deleted file mode 100644 index 208010e00..000000000 --- a/manifests/outputs/bugs/initialize-error-no-plugins.yaml +++ /dev/null @@ -1,74 +0,0 @@ -name: initialize-error-no-path -description: >- - a negative test case that fails becuase no plugins are included in the - initialize block -tags: null -initialize: - plugins: null - outputs: - - yaml -execution: - status: fail - command: >- - /Users/mariamkhalatova/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node - /Users/mariamkhalatova/Projects/UK/if/src/index.ts -m - manifests/outputs/bugs/initialize-error-no-plugins.yml -o - manifests/outputs/bugs/initialize-error-no-plugins - environment: - if-version: 0.4.0 - os: macOS - os-version: '13.2' - node-version: 18.14.2 - date-time: 2024-07-01T19:52:35.214Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - error: >- - ManifestValidationError: "initialize.plugins" parameter is expected object, - received null. Error code: invalid_type. -tree: - children: - child-0: - defaults: - cpu/thermal-design-power: 100 - pipeline: - - teads-curve - inputs: - - timestamp: 2023-07-06T00:00 - duration: 1 - cpu/utilization: 20 - - timestamp: 2023-07-06T00:01 - duration: 1 - cpu/utilization: 80 - - timestamp: 2023-07-06T00:02 - duration: 1 - cpu/utilization: 20 diff --git a/manifests/outputs/pipelines/mock-obs-group-by-cloud-meta.yaml b/manifests/outputs/pipelines/mock-obs-group-by-cloud-meta.yaml deleted file mode 100644 index 5585d4c42..000000000 --- a/manifests/outputs/pipelines/mock-obs-group-by-cloud-meta.yaml +++ /dev/null @@ -1,645 +0,0 @@ -name: My Manifest File -description: integration of mock observations, group by and cloud metadata -aggregation: - metrics: - - cpu/utilization - type: both -initialize: - plugins: - group-by: - path: builtin - method: GroupBy - global-config: - input-parameters: - - cpu/energy - - grid/carbon-intensity - output-parameter: carbon - cloud-metadata: - path: builtin - method: CSVLookup - global-config: - filepath: >- - https://raw.githubusercontent.com/Green-Software-Foundation/if-data/main/cloud-metdata-azure-instances.csv - query: - instance-class: cloud/instance-type - output: - - cpu-tdp - mock-observations: - path: builtin - method: MockObservations - global-config: - timestamp-from: 2024-03-05T00:00 - timestamp-to: 2024-03-05T01:00 - duration: 300 - components: - - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - generators: - common: - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - randint: - cpu/utilization: - min: 1 - max: 99 - outputs: - - yaml -execution: - command: >- - /Users/mariamkhalatova/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node - /Users/mariamkhalatova/Projects/UK/if/src/index.ts -m - manifests/outputs/pipelines/mock-obs-group-by-cloud-meta.yml -o - manifests/outputs/pipelines/mock-obs-group-by-cloud-meta - environment: - if-version: 0.4.0 - os: macOS - os-version: '13.2' - node-version: 18.14.2 - date-time: 2024-07-02T05:21:45.561Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - status: success -tree: - pipeline: - - mock-observations - - group-by - - cloud-metadata - config: - group-by: - group: - - name - - cloud/instance-type - children: - server-1: - children: - Standard_E64_v3: - inputs: - - timestamp: '2024-03-05T00:00:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 93 - - timestamp: '2024-03-05T00:05:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 6 - - timestamp: '2024-03-05T00:10:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 6 - - timestamp: '2024-03-05T00:15:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 3 - - timestamp: '2024-03-05T00:20:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 83 - - timestamp: '2024-03-05T00:25:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 85 - - timestamp: '2024-03-05T00:30:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 8 - - timestamp: '2024-03-05T00:35:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 59 - - timestamp: '2024-03-05T00:40:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 55 - - timestamp: '2024-03-05T00:45:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 85 - - timestamp: '2024-03-05T00:50:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 52 - - timestamp: '2024-03-05T00:55:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 54 - outputs: - - timestamp: '2024-03-05T00:00:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 93 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:05:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 6 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:10:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 6 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:15:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 3 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:20:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 83 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:25:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 85 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:30:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 8 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:35:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 59 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:40:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 55 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:45:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 85 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:50:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 52 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:55:00.000Z' - duration: 300 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: eastus - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 54 - cpu-tdp: 270 - aggregated: - cpu/utilization: 49.083333333333336 - outputs: - - cpu/utilization: 93 - timestamp: '2024-03-05T00:00:00.000Z' - duration: 300 - - cpu/utilization: 6 - timestamp: '2024-03-05T00:05:00.000Z' - duration: 300 - - cpu/utilization: 6 - timestamp: '2024-03-05T00:10:00.000Z' - duration: 300 - - cpu/utilization: 3 - timestamp: '2024-03-05T00:15:00.000Z' - duration: 300 - - cpu/utilization: 83 - timestamp: '2024-03-05T00:20:00.000Z' - duration: 300 - - cpu/utilization: 85 - timestamp: '2024-03-05T00:25:00.000Z' - duration: 300 - - cpu/utilization: 8 - timestamp: '2024-03-05T00:30:00.000Z' - duration: 300 - - cpu/utilization: 59 - timestamp: '2024-03-05T00:35:00.000Z' - duration: 300 - - cpu/utilization: 55 - timestamp: '2024-03-05T00:40:00.000Z' - duration: 300 - - cpu/utilization: 85 - timestamp: '2024-03-05T00:45:00.000Z' - duration: 300 - - cpu/utilization: 52 - timestamp: '2024-03-05T00:50:00.000Z' - duration: 300 - - cpu/utilization: 54 - timestamp: '2024-03-05T00:55:00.000Z' - duration: 300 - aggregated: - cpu/utilization: 49.083333333333336 - server-2: - children: - Standard_E64_v3: - inputs: - - timestamp: '2024-03-05T00:00:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 4 - - timestamp: '2024-03-05T00:05:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 63 - - timestamp: '2024-03-05T00:10:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 38 - - timestamp: '2024-03-05T00:15:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 10 - - timestamp: '2024-03-05T00:20:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 88 - - timestamp: '2024-03-05T00:25:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 81 - - timestamp: '2024-03-05T00:30:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 17 - - timestamp: '2024-03-05T00:35:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 11 - - timestamp: '2024-03-05T00:40:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 76 - - timestamp: '2024-03-05T00:45:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 63 - - timestamp: '2024-03-05T00:50:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 81 - - timestamp: '2024-03-05T00:55:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 74 - outputs: - - timestamp: '2024-03-05T00:00:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 4 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:05:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 63 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:10:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 38 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:15:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 10 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:20:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 88 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:25:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 81 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:30:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 17 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:35:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 11 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:40:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 76 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:45:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 63 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:50:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 81 - cpu-tdp: 270 - - timestamp: '2024-03-05T00:55:00.000Z' - duration: 300 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - geolocation: 37.7749,-122.4194 - cloud/vendor: azure - cpu/utilization: 74 - cpu-tdp: 270 - aggregated: - cpu/utilization: 50.5 - outputs: - - cpu/utilization: 4 - timestamp: '2024-03-05T00:00:00.000Z' - duration: 300 - - cpu/utilization: 63 - timestamp: '2024-03-05T00:05:00.000Z' - duration: 300 - - cpu/utilization: 38 - timestamp: '2024-03-05T00:10:00.000Z' - duration: 300 - - cpu/utilization: 10 - timestamp: '2024-03-05T00:15:00.000Z' - duration: 300 - - cpu/utilization: 88 - timestamp: '2024-03-05T00:20:00.000Z' - duration: 300 - - cpu/utilization: 81 - timestamp: '2024-03-05T00:25:00.000Z' - duration: 300 - - cpu/utilization: 17 - timestamp: '2024-03-05T00:30:00.000Z' - duration: 300 - - cpu/utilization: 11 - timestamp: '2024-03-05T00:35:00.000Z' - duration: 300 - - cpu/utilization: 76 - timestamp: '2024-03-05T00:40:00.000Z' - duration: 300 - - cpu/utilization: 63 - timestamp: '2024-03-05T00:45:00.000Z' - duration: 300 - - cpu/utilization: 81 - timestamp: '2024-03-05T00:50:00.000Z' - duration: 300 - - cpu/utilization: 74 - timestamp: '2024-03-05T00:55:00.000Z' - duration: 300 - aggregated: - cpu/utilization: 50.5 - outputs: - - cpu/utilization: 48.5 - timestamp: '2024-03-05T00:00:00.000Z' - duration: 300 - - cpu/utilization: 34.5 - timestamp: '2024-03-05T00:05:00.000Z' - duration: 300 - - cpu/utilization: 22 - timestamp: '2024-03-05T00:10:00.000Z' - duration: 300 - - cpu/utilization: 6.5 - timestamp: '2024-03-05T00:15:00.000Z' - duration: 300 - - cpu/utilization: 85.5 - timestamp: '2024-03-05T00:20:00.000Z' - duration: 300 - - cpu/utilization: 83 - timestamp: '2024-03-05T00:25:00.000Z' - duration: 300 - - cpu/utilization: 12.5 - timestamp: '2024-03-05T00:30:00.000Z' - duration: 300 - - cpu/utilization: 35 - timestamp: '2024-03-05T00:35:00.000Z' - duration: 300 - - cpu/utilization: 65.5 - timestamp: '2024-03-05T00:40:00.000Z' - duration: 300 - - cpu/utilization: 74 - timestamp: '2024-03-05T00:45:00.000Z' - duration: 300 - - cpu/utilization: 66.5 - timestamp: '2024-03-05T00:50:00.000Z' - duration: 300 - - cpu/utilization: 64 - timestamp: '2024-03-05T00:55:00.000Z' - duration: 300 - aggregated: - cpu/utilization: 49.791666666666664 diff --git a/manifests/outputs/pipelines/mock-obs-groupby.yaml b/manifests/outputs/pipelines/mock-obs-groupby.yaml deleted file mode 100644 index 4783dffde..000000000 --- a/manifests/outputs/pipelines/mock-obs-groupby.yaml +++ /dev/null @@ -1,178 +0,0 @@ -name: Mock observations + Group by -description: Integration of `mock observations` + `groupby plugins` -initialize: - plugins: - mock-observations: - path: builtin - method: MockObservations - global-config: - timestamp-from: '2024-03-05T00:00:00.000Z' - timestamp-to: '2024-03-05T00:01:00.000Z' - duration: 10 - components: - - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: westus3 - - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - generators: - common: - cloud/vendor: azure - randint: - cpu/utilization: - min: 1 - max: 99 - group-by: - path: builtin - method: GroupBy - outputs: - - yaml -execution: - command: >- - /Users/mariamkhalatova/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node - /Users/mariamkhalatova/Projects/UK/if/src/index.ts -m - manifests/outputs/pipelines/mock-obs-groupby.yml -o - manifests/outputs/pipelines/mock-obs-groupby - environment: - if-version: 0.4.0 - os: macOS - os-version: '13.2' - node-version: 18.14.2 - date-time: 2024-07-02T05:27:34.757Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - status: success -tree: - pipeline: - - mock-observations - - group-by - defaults: null - config: - group-by: - group: - - cloud/region - - name - children: - westus3: - children: - server-1: - inputs: - - timestamp: '2024-03-05T00:00:00.000Z' - duration: 10 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: westus3 - cloud/vendor: azure - cpu/utilization: 12 - - timestamp: '2024-03-05T00:00:10.000Z' - duration: 10 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: westus3 - cloud/vendor: azure - cpu/utilization: 71 - - timestamp: '2024-03-05T00:00:20.000Z' - duration: 10 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: westus3 - cloud/vendor: azure - cpu/utilization: 75 - - timestamp: '2024-03-05T00:00:30.000Z' - duration: 10 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: westus3 - cloud/vendor: azure - cpu/utilization: 39 - - timestamp: '2024-03-05T00:00:40.000Z' - duration: 10 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: westus3 - cloud/vendor: azure - cpu/utilization: 42 - - timestamp: '2024-03-05T00:00:50.000Z' - duration: 10 - name: server-1 - cloud/instance-type: Standard_E64_v3 - cloud/region: westus3 - cloud/vendor: azure - cpu/utilization: 98 - france: - children: - server-2: - inputs: - - timestamp: '2024-03-05T00:00:00.000Z' - duration: 10 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - cloud/vendor: azure - cpu/utilization: 14 - - timestamp: '2024-03-05T00:00:10.000Z' - duration: 10 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - cloud/vendor: azure - cpu/utilization: 80 - - timestamp: '2024-03-05T00:00:20.000Z' - duration: 10 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - cloud/vendor: azure - cpu/utilization: 53 - - timestamp: '2024-03-05T00:00:30.000Z' - duration: 10 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - cloud/vendor: azure - cpu/utilization: 10 - - timestamp: '2024-03-05T00:00:40.000Z' - duration: 10 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - cloud/vendor: azure - cpu/utilization: 40 - - timestamp: '2024-03-05T00:00:50.000Z' - duration: 10 - name: server-2 - cloud/instance-type: Standard_E64_v3 - cloud/region: france - cloud/vendor: azure - cpu/utilization: 92 diff --git a/manifests/outputs/plugins/csv-lookup/cloud-metadata/failure-invalid-vendor.yaml b/manifests/outputs/plugins/csv-lookup/cloud-metadata/failure-invalid-vendor.yaml deleted file mode 100644 index c474c91fe..000000000 --- a/manifests/outputs/plugins/csv-lookup/cloud-metadata/failure-invalid-vendor.yaml +++ /dev/null @@ -1,27 +0,0 @@ -name: cloud-metadata -description: failure with invalid `inputs.cloud/vendor` -tags: -initialize: - #outputs: ['yaml'] - plugins: - cloud-metadata: - path: builtin - method: CSVLookup - global-config: - filepath: >- - https://raw.githubusercontent.com/Green-Software-Foundation/if-data/main/cloud-metdata-aws-instances.csv - query: - instance-class: cloud/instance-type - output: ['cpu-cores-utilized', 'vcpus-allocated'] -tree: - children: - child: - pipeline: - - cloud-metadata - config: - inputs: - - timestamp: 2023-07-06T00:00 # [KEYWORD] [NO-SUBFIELDS] time when measurement occurred - cloud/vendor: gcp - cloud/instance-type: m5n.large - duration: 100 - cpu/utilization: 10 diff --git a/manifests/outputs/plugins/groupby/success.yaml b/manifests/outputs/plugins/groupby/success.yaml deleted file mode 100644 index 04d54837b..000000000 --- a/manifests/outputs/plugins/groupby/success.yaml +++ /dev/null @@ -1,101 +0,0 @@ -name: groupby -description: successful path -initialize: - plugins: - group-by: - path: builtin - method: GroupBy - outputs: - - yaml -execution: - command: >- - /Users/mariamkhalatova/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node - /Users/mariamkhalatova/Projects/UK/if/src/index.ts -m - manifests/outputs/plugins/groupby/success.yml -o - manifests/outputs/plugins/groupby/success - environment: - if-version: 0.4.0 - os: macOS - os-version: '13.2' - node-version: 18.14.2 - date-time: 2024-07-02T19:08:31.858Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - status: success -tree: - children: - my-app: - pipeline: - - group-by - config: - group-by: - group: - - cloud/region - - cloud/instance-type - children: - uk-west: - children: - A1: - inputs: - - timestamp: 2023-07-06T00:00 - duration: 300 - cloud/instance-type: A1 - cloud/region: uk-west - cpu/utilization: 99 - - timestamp: 2023-07-06T05:00 - duration: 300 - cloud/instance-type: A1 - cloud/region: uk-west - cpu/utilization: 23 - - timestamp: 2023-07-06T10:00 - duration: 300 - cloud/instance-type: A1 - cloud/region: uk-west - cpu/utilization: 12 - B1: - inputs: - - timestamp: 2023-07-06T00:00 - duration: 300 - cloud/instance-type: B1 - cloud/region: uk-west - cpu/utilization: 11 - - timestamp: 2023-07-06T05:00 - duration: 300 - cloud/instance-type: B1 - cloud/region: uk-west - cpu/utilization: 67 - - timestamp: 2023-07-06T10:00 - duration: 300 - cloud/instance-type: B1 - cloud/region: uk-west - cpu/utilization: 1 diff --git a/manifests/outputs/plugins/time-sync/failure-config-start-later-end.yaml b/manifests/outputs/plugins/time-sync/failure-config-start-later-end.yaml index e0957366a..ae7ec532b 100644 --- a/manifests/outputs/plugins/time-sync/failure-config-start-later-end.yaml +++ b/manifests/outputs/plugins/time-sync/failure-config-start-later-end.yaml @@ -34,7 +34,6 @@ execution: - '@commitlint/cli@18.6.0' - '@commitlint/config-conventional@18.6.0' - '@grnsft/if-core@0.0.10' - - '@jest/globals@29.7.0' - '@types/jest@29.5.8' - '@types/js-yaml@4.0.9' diff --git a/manifests/outputs/plugins/time-sync/failure-missing-global-config.yaml b/manifests/outputs/plugins/time-sync/failure-missing-global-config.yaml deleted file mode 100644 index 79f3bb69e..000000000 --- a/manifests/outputs/plugins/time-sync/failure-missing-global-config.yaml +++ /dev/null @@ -1,78 +0,0 @@ -name: time-sync -description: missing global config -tags: null -initialize: - outputs: - - yaml - plugins: - time-sync: - method: TimeSync - path: builtin - global-config: null -execution: - status: fail - command: >- - /Users/mariamkhalatova/.npm/_npx/1bf7c3c15bf47d04/node_modules/.bin/ts-node - /Users/mariamkhalatova/Projects/UK/if/src/index.ts -m - manifests/outputs/plugins/time-sync/failure-missing-global-config.yml -o - manifests/outputs/plugins/time-sync/failure-missing-global-config - environment: - if-version: 0.4.0 - os: macOS - os-version: '13.2' - node-version: 18.14.2 - date-time: 2024-07-02T21:13:12.360Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - error: >- - ManifestValidationError: "initialize.plugins.time-sync.global-config" - parameter is expected object, received null. Error code: invalid_type. -tree: - children: - child: - pipeline: - - time-sync - config: null - inputs: - - timestamp: '2023-12-12T00:00:00.000Z' - duration: 3 - energy-cpu: 0.001 - - timestamp: '2023-12-12T00:00:01.000Z' - duration: 5 - energy-cpu: 0.001 - - timestamp: '2023-12-12T00:00:06.000Z' - duration: 7 - energy-cpu: 0.001 - - timestamp: '2023-12-12T00:00:13.000Z' - duration: 30 - energy-cpu: 0.001 diff --git a/outputs/examples/copy.yaml b/outputs/examples/copy.yaml deleted file mode 100644 index 6b088ab99..000000000 --- a/outputs/examples/copy.yaml +++ /dev/null @@ -1,69 +0,0 @@ -name: copy-param -description: null -tags: null -initialize: - plugins: - copy-param: - path: builtin - method: Copy - global-config: - keep-existing: true - from: original - to: copy - outputs: - - yaml -execution: - command: >- - /Users/admin/.nvm/versions/node/v18.19.0/bin/ts-node - /Users/admin/Projects/uk/if/src/index.ts --manifest - manifests/examples/copy.yaml --output outputs/examples/copy - environment: - if-version: 0.4.0 - os: macOS - os-version: 14.3.1 - node-version: 18.19.0 - date-time: 2024-06-30T07:53:07.361Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - status: success -tree: - children: - child-1: - pipeline: - - copy-param - inputs: - - timestamp: '2023-12-12T00:00:00.000Z' - original: hello - outputs: - - timestamp: '2023-12-12T00:00:00.000Z' - original: hello - copy: hello diff --git a/outputs/examples/generics.yaml b/outputs/examples/generics.yaml deleted file mode 100644 index 2408d6b3d..000000000 --- a/outputs/examples/generics.yaml +++ /dev/null @@ -1,240 +0,0 @@ -name: generics -description: >- - a pipeline that does arbitrary calculations using our generic arithmetic - builtins -tags: null -initialize: - plugins: - interpolate: - path: builtin - method: Interpolation - global-config: - method: linear - x: - - 0 - - 10 - - 50 - - 100 - 'y': - - 0.12 - - 0.32 - - 0.75 - - 1.02 - input-parameter: cpu/utilization - output-parameter: cpu-factor - cpu-factor-to-wattage: - path: builtin - method: Multiply - global-config: - input-parameters: - - cpu-factor - - cpu/thermal-design-power - output-parameter: cpu-wattage - wattage-times-duration: - path: builtin - method: Multiply - global-config: - input-parameters: - - cpu-wattage - - duration - output-parameter: cpu-wattage-times-duration - wattage-to-energy-kwh: - path: builtin - method: Divide - global-config: - numerator: cpu-wattage-times-duration - denominator: 3600000 - output: cpu-energy-raw - calculate-vcpu-ratio: - path: builtin - method: Divide - global-config: - numerator: vcpus-total - denominator: vcpus-allocated - output: vcpu-ratio - correct-cpu-energy-for-vcpu-ratio: - path: builtin - method: Divide - global-config: - numerator: cpu-energy-raw - denominator: vcpu-ratio - output: cpu-energy-kwh - coefficient: - path: builtin - method: Coefficient - global-config: - input-parameter: cpu-energy-kwh - coefficient: 2 - output-parameter: energy-doubled - multiply: - path: builtin - method: Multiply - global-config: - input-parameters: - - cpu/utilization - - duration - output-parameter: cpu-times-duration - outputs: - - yaml -execution: - command: >- - /Users/admin/.nvm/versions/node/v18.19.0/bin/ts-node - /Users/admin/Projects/uk/if/src/index.ts --manifest - manifests/examples/generics.yml --output outputs/examples/generics - environment: - if-version: 0.4.0 - os: macOS - os-version: 14.3.1 - node-version: 18.19.0 - date-time: 2024-06-30T07:53:53.523Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - status: success -tree: - children: - child-1: - pipeline: - - interpolate - - cpu-factor-to-wattage - - wattage-times-duration - - wattage-to-energy-kwh - - calculate-vcpu-ratio - - correct-cpu-energy-for-vcpu-ratio - - coefficient - - multiply - config: null - defaults: - cpu/thermal-design-power: 100 - vcpus-allocated: 1 - vcpus-total: 8 - inputs: - - timestamp: '2023-12-12T00:00:00.000Z' - cloud/instance-type: A1 - cloud/region: uk-west - duration: 1 - cpu/utilization: 50 - network/energy: 10 - energy: 5 - - timestamp: '2023-12-12T00:00:01.000Z' - duration: 5 - cpu/utilization: 20 - cloud/instance-type: A1 - cloud/region: uk-west - network/energy: 10 - energy: 5 - - timestamp: '2023-12-12T00:00:06.000Z' - duration: 7 - cpu/utilization: 15 - cloud/instance-type: A1 - cloud/region: uk-west - network/energy: 10 - energy: 5 - - timestamp: '2023-12-12T00:00:13.000Z' - duration: 30 - cloud/instance-type: A1 - cloud/region: uk-west - cpu/utilization: 15 - network/energy: 10 - energy: 5 - outputs: - - timestamp: '2023-12-12T00:00:00.000Z' - cloud/instance-type: A1 - cloud/region: uk-west - duration: 1 - cpu/utilization: 50 - network/energy: 10 - energy: 5 - cpu/thermal-design-power: 100 - vcpus-allocated: 1 - vcpus-total: 8 - cpu-factor: 0.75 - cpu-wattage: 75 - cpu-wattage-times-duration: 75 - cpu-energy-raw: 0.000020833333333333333 - vcpu-ratio: 8 - cpu-energy-kwh: 0.0000026041666666666666 - energy-doubled: 0.000005208333333333333 - cpu-times-duration: 50 - - timestamp: '2023-12-12T00:00:01.000Z' - duration: 5 - cpu/utilization: 20 - cloud/instance-type: A1 - cloud/region: uk-west - network/energy: 10 - energy: 5 - cpu/thermal-design-power: 100 - vcpus-allocated: 1 - vcpus-total: 8 - cpu-factor: 0.4275 - cpu-wattage: 42.75 - cpu-wattage-times-duration: 213.75 - cpu-energy-raw: 0.000059375 - vcpu-ratio: 8 - cpu-energy-kwh: 0.000007421875 - energy-doubled: 0.00001484375 - cpu-times-duration: 100 - - timestamp: '2023-12-12T00:00:06.000Z' - duration: 7 - cpu/utilization: 15 - cloud/instance-type: A1 - cloud/region: uk-west - network/energy: 10 - energy: 5 - cpu/thermal-design-power: 100 - vcpus-allocated: 1 - vcpus-total: 8 - cpu-factor: 0.37375 - cpu-wattage: 37.375 - cpu-wattage-times-duration: 261.625 - cpu-energy-raw: 0.00007267361111111111 - vcpu-ratio: 8 - cpu-energy-kwh: 0.000009084201388888889 - energy-doubled: 0.000018168402777777778 - cpu-times-duration: 105 - - timestamp: '2023-12-12T00:00:13.000Z' - duration: 30 - cloud/instance-type: A1 - cloud/region: uk-west - cpu/utilization: 15 - network/energy: 10 - energy: 5 - cpu/thermal-design-power: 100 - vcpus-allocated: 1 - vcpus-total: 8 - cpu-factor: 0.37375 - cpu-wattage: 37.375 - cpu-wattage-times-duration: 1121.25 - cpu-energy-raw: 0.00031145833333333335 - vcpu-ratio: 8 - cpu-energy-kwh: 0.00003893229166666667 - energy-doubled: 0.00007786458333333334 - cpu-times-duration: 450 diff --git a/outputs/examples/instance-metadata.yaml b/outputs/examples/instance-metadata.yaml deleted file mode 100644 index 41baa0b58..000000000 --- a/outputs/examples/instance-metadata.yaml +++ /dev/null @@ -1,99 +0,0 @@ -name: csv-demo -description: null -tags: null -initialize: - plugins: - cloud-instance-metadata: - path: builtin - method: CSVLookup - global-config: - filepath: >- - https://raw.githubusercontent.com/Green-Software-Foundation/if-data/main/cloud-metdata-azure-instances.csv - query: - instance-class: cloud/instance-type - output: '*' - extract-processor-name: - path: builtin - method: Regex - global-config: - parameter: cpu-model-name - match: /^([^,])+/g - output: cpu/name - outputs: - - yaml -execution: - command: >- - /Users/admin/.nvm/versions/node/v18.19.0/bin/ts-node - /Users/admin/Projects/uk/if/src/index.ts --manifest - manifests/examples/instance-metadata.yml --output - outputs/examples/instance-metadata - environment: - if-version: 0.4.0 - os: macOS - os-version: 14.3.1 - node-version: 18.19.0 - date-time: 2024-06-30T07:54:34.384Z (UTC) - dependencies: - - '@babel/core@7.22.10' - - '@babel/preset-typescript@7.23.3' - - '@commitlint/cli@18.6.0' - - '@commitlint/config-conventional@18.6.0' - - '@grnsft/if-core@0.0.10' - - '@jest/globals@29.7.0' - - '@types/jest@29.5.8' - - '@types/js-yaml@4.0.9' - - '@types/luxon@3.4.2' - - '@types/node@20.9.0' - - axios-mock-adapter@1.22.0 - - axios@1.7.2 - - cross-env@7.0.3 - - csv-parse@5.5.6 - - csv-stringify@6.4.6 - - fixpack@4.0.0 - - gts@5.2.0 - - husky@8.0.3 - - jest@29.7.0 - - js-yaml@4.1.0 - - lint-staged@15.2.2 - - luxon@3.4.4 - - release-it@16.3.0 - - rimraf@5.0.5 - - ts-command-line-args@2.5.1 - - ts-jest@29.1.1 - - typescript-cubic-spline@1.0.1 - - typescript@5.2.2 - - winston@3.11.0 - - zod@3.22.4 - status: success -tree: - children: - child: - pipeline: - - cloud-instance-metadata - - extract-processor-name - inputs: - - timestamp: 2023-08-06T00:00 - duration: 3600 - cpu/energy: 0.001 - cloud/provider: gcp - cloud/region: asia-east - cloud/instance-type: Standard_A1_v2 - outputs: - - timestamp: 2023-08-06T00:00 - duration: 3600 - cpu/energy: 0.001 - cloud/provider: gcp - cloud/region: asia-east - cloud/instance-type: Standard_A1_v2 - cpu-cores-available: 52 - cpu-cores-utilized: 1 - cpu-manufacturer: Intel - cpu-model-name: >- - Intel® Xeon® Platinum 8272CL,Intel® Xeon® 8171M 2.1 GHz,Intel® Xeon® - E5-2673 v4 2.3 GHz,Intel® Xeon® E5-2673 v3 2.4 GHz - cpu-tdp: 205 - gpu-count: nan - gpu-model-name: nan - gpu-tdp: nan - memory-available: 2 - cpu/name: Intel® Xeon® Platinum 8272CL diff --git a/package-lock.json b/package-lock.json index 324104a9b..dd24c9128 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,10 +24,10 @@ "zod": "^3.22.4" }, "bin": { - "if-check": "build/check.js", - "if-diff": "build/diff.js", - "if-env": "build/env.js", - "if-run": "build/index.js" + "if-check": "build/if-check/index.js", + "if-diff": "build/if-diff/index.js", + "if-env": "build/if-env/index.js", + "if-run": "build/if-run/index.js" }, "devDependencies": { "@babel/core": "^7.22.10", diff --git a/package.json b/package.json index 768ab1bd0..1b888aa3d 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,10 @@ "email": "info@gsf.com" }, "bin": { - "if-diff": "./build/diff.js", - "if-run": "./build/index.js", - "if-env": "./build/env.js", - "if-check": "./build/check.js" + "if-diff": "./build/if-diff/index.js", + "if-run": "./build/if-run/index.js", + "if-env": "./build/if-env/index.js", + "if-check": "./build/if-check/index.js" }, "bugs": { "url": "https://github.com/Green-Software-Foundation/if/issues/new?assignees=&labels=feedback&projects=&template=feedback.md&title=Feedback+-+" @@ -63,7 +63,6 @@ "models" ], "license": "MIT", - "main": "build/index.js", "publishConfig": { "access": "public" }, @@ -73,21 +72,19 @@ "scripts": { "build": "npm run clean && tsc --project tsconfig.build.json", "clean": "rimraf build/", - "coverage": "jest --verbose --coverage --testPathPattern=src/__tests__/unit", + "coverage": "jest --verbose --coverage --testPathPattern=src/__tests__/", "fix": "gts fix", "fix:package": "fixpack", - "if-check": "cross-env CURRENT_DIR=$(node -p \"process.env.INIT_CWD\") npx ts-node src/check.ts", - "if-diff": "npx ts-node src/diff.ts", - "if-env": "cross-env CURRENT_DIR=$(node -p \"process.env.INIT_CWD\") npx ts-node src/env.ts", - "if-run": "npx ts-node src/index.ts", + "if-check": "cross-env CURRENT_DIR=$(node -p \"process.env.INIT_CWD\") npx ts-node src/if-check/index.ts", + "if-diff": "npx ts-node src/if-diff/index.ts", + "if-env": "cross-env CURRENT_DIR=$(node -p \"process.env.INIT_CWD\") npx ts-node src/if-env/index.ts", + "if-run": "npx ts-node src/if-run/index.ts", "lint": "gts lint", "pre-commit": "lint-staged", "prepare": "husky install", "prepublish": "npm run build", "release": "release-it", - "test": "jest --verbose --testPathPattern=src/__tests__/unit", - "test:integration": "jest --verbose --testPathPattern=src/__tests__/integration" + "test": "jest --verbose --testPathPattern=src/__tests__/" }, - "stability": "stable", - "types": "src/index.d.ts" + "stability": "stable" } diff --git a/src/__mocks__/builtins/export-csv.ts b/src/__mocks__/builtins/export-csv.ts index 1f0e00e73..66ba6ea3c 100644 --- a/src/__mocks__/builtins/export-csv.ts +++ b/src/__mocks__/builtins/export-csv.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import {Context} from '../../types/manifest'; + +import {Context} from '../../common/types/manifest'; export const tree = { children: { diff --git a/src/__tests__/common/lib/load.test.ts b/src/__tests__/common/lib/load.test.ts new file mode 100644 index 000000000..43f6ee2af --- /dev/null +++ b/src/__tests__/common/lib/load.test.ts @@ -0,0 +1,89 @@ +jest.mock('../../../if-run/util/json', () => + require('../../../__mocks__/json') +); +jest.mock( + 'mockavizta', + () => ({ + __esmodule: true, + Mockavizta: () => ({ + execute: (input: PluginParams) => input, + metadata: { + kind: 'execute', + }, + }), + }), + {virtual: true} +); +jest.mock('../../../if-diff/util/helpers', () => ({ + parseManifestFromStdin: () => { + if (process.env.readline === 'valid-source') { + return ` +name: 'mock-name' +description: 'mock-description' +`; + } + return ''; + }, +})); +jest.mock('../../../common/util/yaml', () => ({ + openYamlFileAsObject: (path: string) => { + switch (path) { + case 'load-default.yml': + return 'raw-manifest'; + case 'source-path.yml': + return 'source-manifest'; + case 'target-path.yml': + return 'target-manifest'; + default: + return ''; + } + }, +})); + +import {PluginParams} from '@grnsft/if-core/types'; + +import {PARAMETERS} from '../../../if-run/config'; +import {load} from '../../../common/lib/load'; + +describe('lib/load: ', () => { + describe('load(): ', () => { + it('loads yaml with default parameters.', async () => { + const inputPath = 'load-default.yml'; + const paramPath = undefined; + + const result = await load(inputPath, paramPath); + + const expectedValue = { + rawManifest: 'raw-manifest', + parameters: PARAMETERS, + }; + + expect(result).toEqual(expectedValue); + }); + + it('loads yaml with custom parameters.', async () => { + const inputPath = 'load-default.yml'; + const paramPath = 'param-mock.json'; + + const result = await load(inputPath, paramPath); + + const expectedValue = { + rawManifest: 'raw-manifest', + parameters: { + 'mock-carbon': { + description: 'an amount of carbon emitted into the atmosphere', + unit: 'gCO2e', + aggregation: 'sum', + }, + 'mock-cpu': { + description: 'number of cores available', + unit: 'cores', + aggregation: 'none', + }, + }, + }; + + expect(result).toEqual(expectedValue); + }); + }); +}); diff --git a/src/__tests__/unit/util/debug-logger.test.ts b/src/__tests__/common/util/debug-logger.test.ts similarity index 97% rename from src/__tests__/unit/util/debug-logger.test.ts rename to src/__tests__/common/util/debug-logger.test.ts index 63bafa0af..affdca6c5 100644 --- a/src/__tests__/unit/util/debug-logger.test.ts +++ b/src/__tests__/common/util/debug-logger.test.ts @@ -4,7 +4,7 @@ const warnSpy = jest.spyOn(console, 'warn'); const errorSpy = jest.spyOn(console, 'error'); const debugSpy = jest.spyOn(console, 'debug'); -import {debugLogger} from '../../../util/debug-logger'; +import {debugLogger} from '../../../common/util/debug-logger'; describe('util/debug-logger: ', () => { beforeEach(() => { diff --git a/src/__tests__/unit/util/fs.test.ts b/src/__tests__/common/util/fs.test.ts similarity index 99% rename from src/__tests__/unit/util/fs.test.ts rename to src/__tests__/common/util/fs.test.ts index 77b3c0370..643d80df3 100644 --- a/src/__tests__/unit/util/fs.test.ts +++ b/src/__tests__/common/util/fs.test.ts @@ -6,7 +6,7 @@ import { isFileExists, getYamlFiles, removeFileIfExists, -} from '../../../util/fs'; +} from '../../../common/util/fs'; jest.mock('fs/promises', () => require('../../../__mocks__/fs')); diff --git a/src/__tests__/unit/util/logger.test.ts b/src/__tests__/common/util/logger.test.ts similarity index 80% rename from src/__tests__/unit/util/logger.test.ts rename to src/__tests__/common/util/logger.test.ts index 9eebb4314..6d7e89ada 100644 --- a/src/__tests__/unit/util/logger.test.ts +++ b/src/__tests__/common/util/logger.test.ts @@ -1,6 +1,6 @@ import {Logger} from 'winston'; -import {logger} from '../../../util/logger'; +import {logger} from '../../../common/util/logger'; describe('util/logger: ', () => { describe('logger(): ', () => { diff --git a/src/__tests__/unit/util/yaml.test.ts b/src/__tests__/common/util/yaml.test.ts similarity index 98% rename from src/__tests__/unit/util/yaml.test.ts rename to src/__tests__/common/util/yaml.test.ts index 532ff3fd5..7ad06e13f 100644 --- a/src/__tests__/unit/util/yaml.test.ts +++ b/src/__tests__/common/util/yaml.test.ts @@ -4,7 +4,7 @@ import { checkIfFileIsYaml, openYamlFileAsObject, saveYamlFileAs, -} from '../../../util/yaml'; +} from '../../../common/util/yaml'; describe('util/yaml: ', () => { describe('checkIfFileIsYaml(): ', () => { diff --git a/src/__tests__/if-check/util/args.test.ts b/src/__tests__/if-check/util/args.test.ts new file mode 100644 index 000000000..de0f2a045 --- /dev/null +++ b/src/__tests__/if-check/util/args.test.ts @@ -0,0 +1,179 @@ +jest.mock('../../../common/util/fs', () => ({ + isFileExists: () => { + if (process.env.fileExists === 'true') { + return true; + } + return false; + }, + isDirectoryExists: () => { + if (process.env.directoryExists === 'true') { + return true; + } + return false; + }, +})); + +jest.mock('ts-command-line-args', () => ({ + __esModule: true, + parse: () => { + switch (process.env.result) { + case 'manifest-is-provided': + return {manifest: 'mock-manifest.yaml'}; + case 'directory-is-provided': + return {directory: '/mock-directory'}; + case 'flags-are-not-provided': + return {manifest: undefined, directory: undefined}; + default: + return { + manifest: 'mock-manifest.yaml', + output: 'mock-output', + }; + } + }, +})); + +jest.mock('ts-command-line-args', () => ({ + __esModule: true, + parse: () => { + switch (process.env.result) { + /** If-env mocks */ + case 'manifest-is-not-yaml': + return {manifest: 'manifest'}; + case 'env-throw-error': + throw new Error('mock-error'); + case 'env-throw': + throw 'mock-error'; + /** If-check */ + case 'manifest-is-provided': + return {manifest: 'mock-manifest.yaml'}; + case 'directory-is-provided': + return {directory: '/mock-directory'}; + case 'flags-are-not-provided': + return {manifest: undefined, directory: undefined}; + default: + return { + manifest: 'mock-manifest.yaml', + output: 'mock-output', + }; + } + }, +})); + +import {ERRORS} from '@grnsft/if-core/utils'; + +import {parseIfCheckArgs} from '../../../if-check/util/args'; + +import {STRINGS} from '../../../if-check/config'; +import {STRINGS as COMMON_STRINGS} from '../../../common/config'; + +const { + InvalidDirectoryError, + MissingCliFlagsError, + CliSourceFileError, + ParseCliParamsError, +} = ERRORS; +const {DIRECTORY_NOT_FOUND, IF_CHECK_FLAGS_MISSING} = STRINGS; +const {SOURCE_IS_NOT_YAML, MANIFEST_NOT_FOUND} = COMMON_STRINGS; + +describe('if-check/util: ', () => { + const originalEnv = process.env; + + describe('parseIfCheckArgs(): ', () => { + it('executes when `manifest` is provided.', async () => { + process.env.fileExists = 'true'; + process.env.result = 'manifest-is-provided'; + const response = await parseIfCheckArgs(); + + expect.assertions(1); + + expect(response).toEqual({manifest: 'mock-manifest.yaml'}); + }); + + it('executes when the `directory` is provided.', async () => { + process.env.directoryExists = 'true'; + process.env.result = 'directory-is-provided'; + + const response = await parseIfCheckArgs(); + + expect.assertions(1); + + expect(response).toEqual({directory: '/mock-directory'}); + }); + + it('throws an error when the `directory` does not exist.', async () => { + process.env.directoryExists = 'false'; + process.env.result = 'directory-is-provided'; + expect.assertions(1); + + try { + await parseIfCheckArgs(); + } catch (error) { + expect(error).toEqual(new InvalidDirectoryError(DIRECTORY_NOT_FOUND)); + } + }); + + it('throws an error when both `manifest` and `directory` flags are not provided.', async () => { + process.env.result = 'flags-are-not-provided'; + expect.assertions(1); + + try { + await parseIfCheckArgs(); + } catch (error) { + expect(error).toEqual(new MissingCliFlagsError(IF_CHECK_FLAGS_MISSING)); + } + }); + + it('throws an error if `manifest` is not a yaml.', async () => { + process.env.fileExists = 'true'; + process.env.result = 'manifest-is-not-yaml'; + expect.assertions(1); + + try { + await parseIfCheckArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new CliSourceFileError(SOURCE_IS_NOT_YAML)); + } + } + }); + + it('throws an error if `manifest` path is invalid.', async () => { + process.env.fileExists = 'false'; + expect.assertions(1); + + try { + await parseIfCheckArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError(MANIFEST_NOT_FOUND)); + } + } + }); + + it('throws an error if parsing failed.', async () => { + process.env.result = 'env-throw-error'; + expect.assertions(1); + + try { + await parseIfCheckArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError('mock-error')); + } + } + }); + + it('throws error if parsing failed (not instance of error).', async () => { + process.env.result = 'env-throw'; + expect.assertions(1); + + try { + await parseIfCheckArgs(); + } catch (error) { + expect(error).toEqual('mock-error'); + } + }); + }); + + process.env = originalEnv; +}); diff --git a/src/__tests__/if-check/util/helpers.test.ts b/src/__tests__/if-check/util/helpers.test.ts new file mode 100644 index 000000000..381c7534c --- /dev/null +++ b/src/__tests__/if-check/util/helpers.test.ts @@ -0,0 +1,18 @@ +import {logStdoutFailMessage} from '../../../if-check/util/helpers'; + +describe('logStdoutFailMessage(): ', () => { + it('successfully logs the failed message.', () => { + const errorMessage = {stdout: '\n\nmock error message'}; + const mockFilename = 'mock-filename.yaml'; + const logSpy = jest.spyOn(global.console, 'log'); + logStdoutFailMessage(errorMessage, mockFilename); + + expect.assertions(2); + + expect(logSpy).toHaveBeenCalledWith( + `✖ if-check could not verify ${mockFilename}. The re-executed file does not match the original.\n` + ); + + expect(logSpy).toHaveBeenCalledWith('mock error message'); + }); +}); diff --git a/src/__tests__/if-check/util/npm.test.ts b/src/__tests__/if-check/util/npm.test.ts new file mode 100644 index 000000000..4bdffc10f --- /dev/null +++ b/src/__tests__/if-check/util/npm.test.ts @@ -0,0 +1,54 @@ +jest.mock('fs/promises', () => require('../../../__mocks__/fs')); + +const mockInfo = jest.fn(); + +jest.mock('node:readline/promises', () => + require('../../../__mocks__/readline') +); +jest.mock('../../../common/util/logger', () => ({ + logger: { + info: mockInfo, + }, +})); + +jest.mock('../../../common/util/helpers', () => { + const originalModule = jest.requireActual('../../../common/util/helpers'); + return { + ...originalModule, + execPromise: async (param: any) => { + switch (process.env.NPM_INSTALL) { + case 'true': + expect(param).toEqual('npm install @grnsft/if@0.3.3-beta.0'); + break; + case 'npm init -y': + expect(param).toEqual('npm init -y'); + break; + case 'if-check': + expect(param).toEqual( + "npm run if-env -- -m ./src/__mocks__/mock-manifest.yaml && npm run if-run -- -m ./src/__mocks__/mock-manifest.yaml -o src/__mocks__/re-mock-manifest && node -p 'Boolean(process.stdout.isTTY)' | npm run if-diff -- -s src/__mocks__/re-mock-manifest.yaml -t ./src/__mocks__/mock-manifest.yaml" + ); + break; + } + return; + }, + }; +}); + +import {executeCommands} from '../../../if-check/util/npm'; + +describe('if-check/util/npm: ', () => { + describe('executeCommands(): ', () => { + it('successfully executes with correct commands.', async () => { + process.env.NPM_INSTALL = 'if-check'; + const manifest = './src/__mocks__/mock-manifest.yaml'; + const logSpy = jest.spyOn(global.console, 'log'); + + await executeCommands(manifest, false); + + expect.assertions(2); + expect(logSpy).toHaveBeenCalledWith( + '✔ if-check successfully verified mock-manifest.yaml\n' + ); + }); + }); +}); diff --git a/src/__tests__/unit/lib/compare.test.ts b/src/__tests__/if-diff/lib/compare.test.ts similarity index 97% rename from src/__tests__/unit/lib/compare.test.ts rename to src/__tests__/if-diff/lib/compare.test.ts index dbac5a490..f43e7c590 100644 --- a/src/__tests__/unit/lib/compare.test.ts +++ b/src/__tests__/if-diff/lib/compare.test.ts @@ -1,4 +1,4 @@ -import {compare} from '../../../lib/compare'; +import {compare} from '../../../if-diff/lib/compare'; describe('lib/compare: ', () => { describe('compare(): ', () => { diff --git a/src/__tests__/unit/lib/load.test.ts b/src/__tests__/if-diff/lib/load.test.ts similarity index 60% rename from src/__tests__/unit/lib/load.test.ts rename to src/__tests__/if-diff/lib/load.test.ts index ba3bca902..f722757e5 100644 --- a/src/__tests__/unit/lib/load.test.ts +++ b/src/__tests__/if-diff/lib/load.test.ts @@ -1,4 +1,6 @@ -jest.mock('../../../util/json', () => require('../../../__mocks__/json')); +jest.mock('../../../if-run/util/json', () => + require('../../../__mocks__/json') +); jest.mock( 'mockavizta', () => ({ @@ -12,7 +14,7 @@ jest.mock( }), {virtual: true} ); -jest.mock('../../../util/helpers', () => ({ +jest.mock('../../../if-diff/util/helpers', () => ({ parseManifestFromStdin: () => { if (process.env.readline === 'valid-source') { return ` @@ -23,7 +25,7 @@ description: 'mock-description' return ''; }, })); -jest.mock('../../../util/yaml', () => ({ +jest.mock('../../../common/util/yaml', () => ({ openYamlFileAsObject: (path: string) => { switch (path) { case 'load-default.yml': @@ -40,58 +42,17 @@ jest.mock('../../../util/yaml', () => ({ import {PluginParams} from '@grnsft/if-core/types'; -import {load, loadIfDiffFiles} from '../../../lib/load'; +import {loadIfDiffFiles} from '../../../if-diff/lib/load'; -import {PARAMETERS, STRINGS} from '../../../config'; +import {parseManifestFromStdin} from '../../../if-diff/util/helpers'; -import {parseManifestFromStdin} from '../../../util/helpers'; +import {STRINGS} from '../../../if-diff/config'; -import {LoadDiffParams} from '../../../types/util/args'; +import {LoadDiffParams} from '../../../if-diff/types/args'; const {INVALID_SOURCE} = STRINGS; -describe('lib/load: ', () => { - describe('load(): ', () => { - it('loads yaml with default parameters.', async () => { - const inputPath = 'load-default.yml'; - const paramPath = undefined; - - const result = await load(inputPath, paramPath); - - const expectedValue = { - rawManifest: 'raw-manifest', - parameters: PARAMETERS, - }; - - expect(result).toEqual(expectedValue); - }); - - it('loads yaml with custom parameters.', async () => { - const inputPath = 'load-default.yml'; - const paramPath = 'param-mock.json'; - - const result = await load(inputPath, paramPath); - - const expectedValue = { - rawManifest: 'raw-manifest', - parameters: { - 'mock-carbon': { - description: 'an amount of carbon emitted into the atmosphere', - unit: 'gCO2e', - aggregation: 'sum', - }, - 'mock-cpu': { - description: 'number of cores available', - unit: 'cores', - aggregation: 'none', - }, - }, - }; - - expect(result).toEqual(expectedValue); - }); - }); - +describe('if-diff/lib/load: ', () => { describe('loadIfDiffFiles(): ', () => { it('rejects with invalid source error.', async () => { const params = { diff --git a/src/__tests__/if-diff/util/args.test.ts b/src/__tests__/if-diff/util/args.test.ts new file mode 100644 index 000000000..2f43ec6ab --- /dev/null +++ b/src/__tests__/if-diff/util/args.test.ts @@ -0,0 +1,133 @@ +jest.mock('ts-command-line-args', () => ({ + __esModule: true, + parse: () => { + switch (process.env.result) { + case 'only-target': + return { + target: 'target-mock.yml', + }; + case 'target-is-not-yaml': + return { + target: 'target-mock', + }; + case 'source-is-not-yaml': + return { + target: 'target-mock.yml', + source: 'source-mock', + }; + case 'target-source': + return { + target: 'target-mock.yml', + source: 'source-mock.yml', + }; + case 'diff-throw-error': + throw new Error('mock-error'); + case 'diff-throw': + throw 'mock-error'; + default: + return { + manifest: 'mock-manifest.yaml', + output: 'mock-output', + }; + } + }, +})); + +import {ERRORS} from '@grnsft/if-core/utils'; + +import {parseIfDiffArgs} from '../../../if-diff/util/args'; + +import {STRINGS as COMMON_STRINGS} from '../../../common/config'; +import {STRINGS} from '../../../if-diff/config'; + +const {ParseCliParamsError} = ERRORS; + +const {TARGET_IS_NOT_YAML, SOURCE_IS_NOT_YAML} = COMMON_STRINGS; +const {INVALID_TARGET} = STRINGS; + +describe('util/args: ', () => { + const originalEnv = process.env; + + describe('parseIfDiffArgs(): ', () => { + it('throws error if `target` is missing.', () => { + expect.assertions(1); + + try { + parseIfDiffArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError(INVALID_TARGET)); + } + } + }); + + it('throws error if `target` is not a yaml.', () => { + process.env.result = 'target-is-not-yaml'; + expect.assertions(1); + + try { + parseIfDiffArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError(TARGET_IS_NOT_YAML)); + } + } + }); + + it('returns `target`s full path.', () => { + process.env.result = 'only-target'; + expect.assertions(1); + + const response = parseIfDiffArgs(); + expect(response).toHaveProperty('targetPath'); + }); + + it('throws error if source is not a yaml.', () => { + process.env.result = 'source-is-not-yaml'; + expect.assertions(1); + + try { + parseIfDiffArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError(SOURCE_IS_NOT_YAML)); + } + } + }); + + it('returns target and source full paths.', () => { + process.env.result = 'target-source'; + expect.assertions(2); + + const response = parseIfDiffArgs(); + expect(response).toHaveProperty('targetPath'); + expect(response).toHaveProperty('sourcePath'); + }); + + it('throws error if parsing failed.', () => { + process.env.result = 'diff-throw-error'; + expect.assertions(1); + + try { + parseIfDiffArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError('mock-error')); + } + } + }); + + it('throws error if parsing failed (not instance of error).', () => { + process.env.result = 'diff-throw'; + expect.assertions(1); + + try { + parseIfDiffArgs(); + } catch (error) { + expect(error).toEqual('mock-error'); + } + }); + }); + + process.env = originalEnv; +}); diff --git a/src/__tests__/if-diff/util/helpers.test.ts b/src/__tests__/if-diff/util/helpers.test.ts new file mode 100644 index 000000000..a7d25599e --- /dev/null +++ b/src/__tests__/if-diff/util/helpers.test.ts @@ -0,0 +1,235 @@ +jest.mock('node:readline/promises', () => + require('../../../__mocks__/readline') +); + +import {Difference} from '../../../if-diff/types/compare'; +import { + checkIfEqual, + formatNotMatchingLog, + oneIsPrimitive, + parseManifestFromStdin, +} from '../../../if-diff/util/helpers'; + +describe('if-diff/util/helpers: ', () => { + describe('checkIfEqual(): ', () => { + it('checks if values are equal.', () => { + const a = 'mock'; + const b = 'mock'; + + const response = checkIfEqual(a, b); + expect(response).toBeTruthy(); + }); + + it('returns true if one of the values is wildcard.', () => { + const a = 'mock'; + const b = '*'; + + const response = checkIfEqual(a, b); + expect(response).toBeTruthy(); + }); + + it('returns false for number and string with the same value.', () => { + const a = 5; + const b = '5'; + + const response = checkIfEqual(a, b); + expect(response).toBeFalsy(); + }); + }); + + describe('oneIsPrimitive(): ', () => { + it('returns true if values are nullish.', () => { + const source = null; + const target = undefined; + + const result = oneIsPrimitive(source, target); + expect(result).toBeTruthy(); + }); + + it('returns true if values are string or number.', () => { + const source = 'string'; + const target = 10; + + const result = oneIsPrimitive(source, target); + expect(result).toBeTruthy(); + }); + + it('returns false if one of values is object.', () => { + const source = 'string'; + const target = {}; + + const result = oneIsPrimitive(source, target); + expect(result).toBeFalsy(); + }); + }); + + describe('formatNotMatchingLog(): ', () => { + const actualLogger = console.log; + const mockLogger = jest.fn(); + console.log = mockLogger; + + beforeEach(() => { + mockLogger.mockReset(); + }); + + it('logs the message.', () => { + const difference: Difference = { + message: 'mock-message', + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(1); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + }); + + it('logs message and path.', () => { + const difference: Difference = { + message: 'mock-message', + path: 'mock.path', + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(2); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + expect(mockLogger).toHaveBeenCalledWith(difference.path); + }); + + it('logs message, path and formatted source/target (one is missing).', () => { + const difference: Difference = { + message: 'mock-message', + path: 'mock.path', + source: 'mock-source', + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(4); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + expect(mockLogger).toHaveBeenCalledWith(difference.path); + expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); + expect(mockLogger).toHaveBeenCalledWith('target: missing'); + }); + + it('logs message, path and formatted source/target.', () => { + const difference: Difference = { + message: 'mock-message', + path: 'mock.path', + source: 'mock-source', + target: 'mock-target', + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(4); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + expect(mockLogger).toHaveBeenCalledWith(difference.path); + expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); + expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); + }); + + it('logs message, path and formatted source/target (numbers).', () => { + const difference: Difference = { + message: 'mock-message', + path: 'mock.path', + source: 10, + target: 0, + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(4); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + expect(mockLogger).toHaveBeenCalledWith(difference.path); + expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); + expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); + }); + + it('logs message, path and formatted source/target (booleans).', () => { + const difference: Difference = { + message: 'mock-message', + path: 'mock.path', + source: true, + target: false, + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(4); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + expect(mockLogger).toHaveBeenCalledWith(difference.path); + expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); + expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); + }); + + it('logs message, path and formatted source/target (objects).', () => { + const difference: Difference = { + message: 'mock-message', + path: 'mock.path', + source: {}, + target: false, + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(4); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + expect(mockLogger).toHaveBeenCalledWith(difference.path); + expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); + expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); + }); + + it('logs message, path and formatted source/target (empty string).', () => { + const difference: Difference = { + message: 'mock-message', + path: 'mock.path', + source: '', + target: false, + }; + + formatNotMatchingLog(difference); + expect(mockLogger).toHaveBeenCalledTimes(4); + expect(mockLogger).toHaveBeenCalledWith(difference.message); + expect(mockLogger).toHaveBeenCalledWith(difference.path); + expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); + expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); + }); + + afterAll(() => { + console.log = actualLogger; + }); + }); + + describe('parseManifestFromStdin(): ', () => { + it('returns empty string if there is no data in stdin.', async () => { + const response = await parseManifestFromStdin(); + const expectedResult = ''; + + expect(response).toEqual(expectedResult); + }); + + it('returns empty string if nothing is piped.', async () => { + const originalIsTTY = process.stdin.isTTY; + process.stdin.isTTY = true; + const response = await parseManifestFromStdin(); + const expectedResult = ''; + + expect(response).toEqual(expectedResult); + process.stdin.isTTY = originalIsTTY; + }); + + it('throws error if there is no manifest in stdin.', async () => { + process.env.readline = 'no_manifest'; + expect.assertions(1); + + const response = await parseManifestFromStdin(); + + expect(response).toEqual(''); + }); + + it('returns empty string if there is no data in stdin.', async () => { + process.env.readline = 'manifest'; + const response = await parseManifestFromStdin(); + const expectedMessage = ` +name: mock-name +description: mock-description +`; + + expect(response).toEqual(expectedMessage); + }); + }); +}); diff --git a/src/__tests__/if-env/util/args.test.ts b/src/__tests__/if-env/util/args.test.ts new file mode 100644 index 000000000..2b0dd13f9 --- /dev/null +++ b/src/__tests__/if-env/util/args.test.ts @@ -0,0 +1,124 @@ +jest.mock('../../../common/util/fs', () => ({ + isFileExists: () => { + if (process.env.fileExists === 'true') { + return true; + } + return false; + }, +})); + +jest.mock('ts-command-line-args', () => ({ + __esModule: true, + parse: () => { + switch (process.env.result) { + case 'manifest-is-missing': + return {}; + case 'manifest-install-provided': + return { + install: true, + manifest: 'mock-manifest.yaml', + }; + case 'manifest-is-not-yaml': + return {manifest: 'manifest'}; + case 'env-throw-error': + throw new Error('mock-error'); + case 'env-throw': + throw 'mock-error'; + default: + return { + manifest: 'mock-manifest.yaml', + output: 'mock-output', + }; + } + }, +})); + +import {ERRORS} from '@grnsft/if-core/utils'; + +import {parseIfEnvArgs} from '../../../if-env/util/args'; + +import {STRINGS} from '../../../common/config'; + +const {CliSourceFileError, ParseCliParamsError} = ERRORS; + +const {SOURCE_IS_NOT_YAML, MANIFEST_NOT_FOUND} = STRINGS; + +describe('util/args: ', () => { + const originalEnv = process.env; + + describe('parseIfEnvArgs(): ', () => { + it('executes if `manifest` is missing.', async () => { + process.env.fileExists = 'true'; + process.env.result = 'manifest-is-missing'; + const response = await parseIfEnvArgs(); + + expect.assertions(1); + + expect(response).toEqual({install: undefined}); + }); + + it('executes if `manifest` and `install` are provided.', async () => { + process.env.fileExists = 'true'; + process.env.result = 'manifest-install-provided'; + + const response = await parseIfEnvArgs(); + + expect.assertions(2); + expect(response).toHaveProperty('install'); + expect(response).toHaveProperty('manifest'); + }); + + it('throws an error if `manifest` is not a yaml.', async () => { + process.env.fileExists = 'true'; + process.env.result = 'manifest-is-not-yaml'; + expect.assertions(1); + + try { + await parseIfEnvArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new CliSourceFileError(SOURCE_IS_NOT_YAML)); + } + } + }); + + it('throws an error if `manifest` path is invalid.', async () => { + process.env.fileExists = 'false'; + expect.assertions(1); + + try { + await parseIfEnvArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError(MANIFEST_NOT_FOUND)); + } + } + }); + + it('throws an error if parsing failed.', async () => { + process.env.result = 'env-throw-error'; + expect.assertions(1); + + try { + await parseIfEnvArgs(); + } catch (error) { + if (error instanceof Error) { + expect(error).toEqual(new ParseCliParamsError('mock-error')); + } + } + }); + + it('throws error if parsing failed (not instance of error).', async () => { + process.env.result = 'env-throw'; + expect.assertions(1); + + try { + await parseIfEnvArgs(); + } catch (error) { + expect(error).toEqual('mock-error'); + } + }); + }); + + process.env = originalEnv; +}); diff --git a/src/__tests__/unit/util/npm.test.ts b/src/__tests__/if-env/util/npm.test.ts similarity index 89% rename from src/__tests__/unit/util/npm.test.ts rename to src/__tests__/if-env/util/npm.test.ts index 6d1476b42..be922f07c 100644 --- a/src/__tests__/unit/util/npm.test.ts +++ b/src/__tests__/if-env/util/npm.test.ts @@ -8,14 +8,15 @@ const mockInfo = jest.fn(); jest.mock('node:readline/promises', () => require('../../../__mocks__/readline') ); -jest.mock('../../../util/logger', () => ({ +jest.mock('../../../common/util/logger', () => ({ logger: { info: mockInfo, }, })); -jest.mock('../../../util/helpers', () => { - const originalModule = jest.requireActual('../../../util/helpers'); +jest.mock('../../../common/util/helpers', () => { + const originalModule = jest.requireActual('../../../if-run/util/helpers'); + return { ...originalModule, execPromise: async (param: any) => { @@ -43,12 +44,11 @@ import { updatePackageJsonDependencies, extractPathsWithVersion, updatePackageJsonProperties, - executeCommands, -} from '../../../util/npm'; -import {isFileExists} from '../../../util/fs'; +} from '../../../if-env/util/npm'; +import {isFileExists} from '../../../common/util/fs'; -import {STRINGS} from '../../../config/strings'; -import {ManifestPlugin} from '../../../types/npm'; +import {STRINGS} from '../../../if-env/config'; +import {ManifestPlugin} from '../../../if-env/types/npm'; const {INITIALIZING_PACKAGE_JSON, INSTALLING_NPM_PACKAGES} = STRINGS; @@ -247,19 +247,4 @@ describe('util/npm: ', () => { expect(fsReadSpy).toHaveBeenCalledWith(packageJsonPath, 'utf8'); }); }); - - describe('executeCommands(): ', () => { - it('successfully executes with correct commands.', async () => { - process.env.NPM_INSTALL = 'if-check'; - const manifest = './src/__mocks__/mock-manifest.yaml'; - const logSpy = jest.spyOn(global.console, 'log'); - - await executeCommands(manifest, false); - - expect.assertions(2); - expect(logSpy).toHaveBeenCalledWith( - '✔ if-check successfully verified mock-manifest.yaml\n' - ); - }); - }); }); diff --git a/src/__tests__/unit/builtins/CommonGenerator.test.ts b/src/__tests__/if-run/builtins/CommonGenerator.test.ts similarity index 86% rename from src/__tests__/unit/builtins/CommonGenerator.test.ts rename to src/__tests__/if-run/builtins/CommonGenerator.test.ts index b0c165d1a..87b8457a8 100644 --- a/src/__tests__/unit/builtins/CommonGenerator.test.ts +++ b/src/__tests__/if-run/builtins/CommonGenerator.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {CommonGenerator} from '../../../builtins/mock-observations/helpers/common-generator'; +import {CommonGenerator} from '../../../if-run/builtins/mock-observations/helpers/common-generator'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {GlobalConfigError} = ERRORS; const {MISSING_GLOBAL_CONFIG} = STRINGS; diff --git a/src/__tests__/unit/builtins/RandIntGenerator.test.ts b/src/__tests__/if-run/builtins/RandIntGenerator.test.ts similarity index 91% rename from src/__tests__/unit/builtins/RandIntGenerator.test.ts rename to src/__tests__/if-run/builtins/RandIntGenerator.test.ts index 328f7d6f4..05fdb37f2 100644 --- a/src/__tests__/unit/builtins/RandIntGenerator.test.ts +++ b/src/__tests__/if-run/builtins/RandIntGenerator.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {RandIntGenerator} from '../../../builtins/mock-observations/helpers/rand-int-generator'; +import {RandIntGenerator} from '../../../if-run/builtins/mock-observations/helpers/rand-int-generator'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {GlobalConfigError} = ERRORS; const {INVALID_NAME, MISSING_MIN_MAX, MISSING_GLOBAL_CONFIG} = STRINGS; diff --git a/src/__tests__/unit/builtins/coefficient.test.ts b/src/__tests__/if-run/builtins/coefficient.test.ts similarity index 96% rename from src/__tests__/unit/builtins/coefficient.test.ts rename to src/__tests__/if-run/builtins/coefficient.test.ts index 034d20f95..1660edffe 100644 --- a/src/__tests__/unit/builtins/coefficient.test.ts +++ b/src/__tests__/if-run/builtins/coefficient.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Coefficient} from '../../../builtins/coefficient'; +import {Coefficient} from '../../../if-run/builtins/coefficient'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {InputValidationError, GlobalConfigError} = ERRORS; const {MISSING_GLOBAL_CONFIG} = STRINGS; diff --git a/src/__tests__/unit/builtins/copy-param.test.ts b/src/__tests__/if-run/builtins/copy-param.test.ts similarity index 96% rename from src/__tests__/unit/builtins/copy-param.test.ts rename to src/__tests__/if-run/builtins/copy-param.test.ts index 64809e44a..8ac621538 100644 --- a/src/__tests__/unit/builtins/copy-param.test.ts +++ b/src/__tests__/if-run/builtins/copy-param.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Copy} from '../../../builtins/copy-param'; +import {Copy} from '../../../if-run/builtins/copy-param'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {GlobalConfigError, InputValidationError} = ERRORS; const {MISSING_GLOBAL_CONFIG} = STRINGS; diff --git a/src/__tests__/unit/builtins/csv-lookup.test.ts b/src/__tests__/if-run/builtins/csv-lookup.test.ts similarity index 99% rename from src/__tests__/unit/builtins/csv-lookup.test.ts rename to src/__tests__/if-run/builtins/csv-lookup.test.ts index 81e0498b4..b89c7572e 100644 --- a/src/__tests__/unit/builtins/csv-lookup.test.ts +++ b/src/__tests__/if-run/builtins/csv-lookup.test.ts @@ -4,9 +4,9 @@ import axios from 'axios'; import AxiosMockAdapter from 'axios-mock-adapter'; import {ERRORS} from '@grnsft/if-core/utils'; -import {CSVLookup} from '../../../builtins'; +import {CSVLookup} from '../../../if-run/builtins'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const { GlobalConfigError, diff --git a/src/__tests__/unit/builtins/divide.test.ts b/src/__tests__/if-run/builtins/divide.test.ts similarity index 97% rename from src/__tests__/unit/builtins/divide.test.ts rename to src/__tests__/if-run/builtins/divide.test.ts index ba75e3879..b9d1d9456 100644 --- a/src/__tests__/unit/builtins/divide.test.ts +++ b/src/__tests__/if-run/builtins/divide.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Divide} from '../../../builtins'; +import {Divide} from '../../../if-run/builtins'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {InputValidationError, GlobalConfigError, MissingInputDataError} = ERRORS; const {MISSING_GLOBAL_CONFIG, MISSING_INPUT_DATA} = STRINGS; diff --git a/src/__tests__/unit/builtins/exponent.test.ts b/src/__tests__/if-run/builtins/exponent.test.ts similarity index 98% rename from src/__tests__/unit/builtins/exponent.test.ts rename to src/__tests__/if-run/builtins/exponent.test.ts index ad438179d..3b88df0cb 100644 --- a/src/__tests__/unit/builtins/exponent.test.ts +++ b/src/__tests__/if-run/builtins/exponent.test.ts @@ -1,6 +1,6 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Exponent} from '../../../builtins/exponent'; +import {Exponent} from '../../../if-run/builtins/exponent'; const {InputValidationError} = ERRORS; diff --git a/src/__tests__/unit/builtins/export-csv-raw.test.ts b/src/__tests__/if-run/builtins/export-csv-raw.test.ts similarity index 96% rename from src/__tests__/unit/builtins/export-csv-raw.test.ts rename to src/__tests__/if-run/builtins/export-csv-raw.test.ts index e97242a4d..c6a577e4e 100644 --- a/src/__tests__/unit/builtins/export-csv-raw.test.ts +++ b/src/__tests__/if-run/builtins/export-csv-raw.test.ts @@ -3,9 +3,9 @@ import * as fs from 'fs/promises'; import {jest} from '@jest/globals'; import {ERRORS} from '@grnsft/if-core/utils'; -import {ExportCSVRaw} from '../../../builtins/export-csv-raw'; +import {ExportCSVRaw} from '../../../if-run/builtins/export-csv-raw'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; import {tree, context, outputs} from '../../../__mocks__/builtins/export-csv'; diff --git a/src/__tests__/unit/builtins/export-csv.test.ts b/src/__tests__/if-run/builtins/export-csv.test.ts similarity index 98% rename from src/__tests__/unit/builtins/export-csv.test.ts rename to src/__tests__/if-run/builtins/export-csv.test.ts index 78feaf534..74d2e42dc 100644 --- a/src/__tests__/unit/builtins/export-csv.test.ts +++ b/src/__tests__/if-run/builtins/export-csv.test.ts @@ -4,9 +4,9 @@ import {stringify} from 'csv-stringify/sync'; import {jest} from '@jest/globals'; import {ERRORS} from '@grnsft/if-core/utils'; -import {ExportCSV} from '../../../builtins/export-csv'; +import {ExportCSV} from '../../../if-run/builtins/export-csv'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; import { tree, diff --git a/src/__tests__/unit/builtins/export-log.test.ts b/src/__tests__/if-run/builtins/export-log.test.ts similarity index 94% rename from src/__tests__/unit/builtins/export-log.test.ts rename to src/__tests__/if-run/builtins/export-log.test.ts index f6990ab1e..48de3b49d 100644 --- a/src/__tests__/unit/builtins/export-log.test.ts +++ b/src/__tests__/if-run/builtins/export-log.test.ts @@ -1,6 +1,6 @@ import * as YAML from 'js-yaml'; -import {ExportLog} from '../../../builtins/export-log'; +import {ExportLog} from '../../../if-run/builtins/export-log'; import {tree, context} from '../../../__mocks__/builtins/export-csv'; describe('builtins/export-log:', () => { diff --git a/src/__tests__/unit/builtins/export-yaml.test.ts b/src/__tests__/if-run/builtins/export-yaml.test.ts similarity index 85% rename from src/__tests__/unit/builtins/export-yaml.test.ts rename to src/__tests__/if-run/builtins/export-yaml.test.ts index fb7954c1e..6d1e84d8b 100644 --- a/src/__tests__/unit/builtins/export-yaml.test.ts +++ b/src/__tests__/if-run/builtins/export-yaml.test.ts @@ -1,13 +1,13 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {ExportYaml} from '../../../builtins/export-yaml'; -import {saveYamlFileAs} from '../../../util/yaml'; +import {ExportYaml} from '../../../if-run/builtins/export-yaml'; +import {saveYamlFileAs} from '../../../common/util/yaml'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; import {tree, context} from '../../../__mocks__/builtins/export-csv'; -jest.mock('../../../util/yaml', () => ({ +jest.mock('../../../common/util/yaml', () => ({ saveYamlFileAs: jest.fn(), })); diff --git a/src/__tests__/unit/builtins/group-by.test.ts b/src/__tests__/if-run/builtins/group-by.test.ts similarity index 97% rename from src/__tests__/unit/builtins/group-by.test.ts rename to src/__tests__/if-run/builtins/group-by.test.ts index 3d676148b..b59da6836 100644 --- a/src/__tests__/unit/builtins/group-by.test.ts +++ b/src/__tests__/if-run/builtins/group-by.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {GroupBy} from '../../../builtins/group-by'; +import {GroupBy} from '../../../if-run/builtins/group-by'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {InvalidGroupingError, InputValidationError, GlobalConfigError} = ERRORS; const {MISSING_GLOBAL_CONFIG, INVALID_GROUP_BY} = STRINGS; diff --git a/src/__tests__/unit/builtins/interpolation.test.ts b/src/__tests__/if-run/builtins/interpolation.test.ts similarity index 98% rename from src/__tests__/unit/builtins/interpolation.test.ts rename to src/__tests__/if-run/builtins/interpolation.test.ts index 6219e01ec..8dbd1295c 100644 --- a/src/__tests__/unit/builtins/interpolation.test.ts +++ b/src/__tests__/if-run/builtins/interpolation.test.ts @@ -1,9 +1,9 @@ import {ERRORS} from '@grnsft/if-core/utils'; import {Method} from '@grnsft/if-core/types'; -import {Interpolation} from '../../../builtins'; +import {Interpolation} from '../../../if-run/builtins'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {InputValidationError, GlobalConfigError} = ERRORS; const { diff --git a/src/__tests__/unit/builtins/mock-observations.test.ts b/src/__tests__/if-run/builtins/mock-observations.test.ts similarity index 98% rename from src/__tests__/unit/builtins/mock-observations.test.ts rename to src/__tests__/if-run/builtins/mock-observations.test.ts index fd28d5595..6b0fb22bd 100644 --- a/src/__tests__/unit/builtins/mock-observations.test.ts +++ b/src/__tests__/if-run/builtins/mock-observations.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {MockObservations} from '../../../builtins/mock-observations'; +import {MockObservations} from '../../../if-run/builtins/mock-observations'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {InputValidationError, GlobalConfigError} = ERRORS; const {INVALID_MIN_MAX} = STRINGS; diff --git a/src/__tests__/unit/builtins/multiply.test.ts b/src/__tests__/if-run/builtins/multiply.test.ts similarity index 97% rename from src/__tests__/unit/builtins/multiply.test.ts rename to src/__tests__/if-run/builtins/multiply.test.ts index d15a7e8f9..07dedf5ea 100644 --- a/src/__tests__/unit/builtins/multiply.test.ts +++ b/src/__tests__/if-run/builtins/multiply.test.ts @@ -1,6 +1,6 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Multiply} from '../../../builtins/multiply'; +import {Multiply} from '../../../if-run/builtins/multiply'; const {InputValidationError} = ERRORS; diff --git a/src/__tests__/unit/builtins/regex.test.ts b/src/__tests__/if-run/builtins/regex.test.ts similarity index 97% rename from src/__tests__/unit/builtins/regex.test.ts rename to src/__tests__/if-run/builtins/regex.test.ts index 54a70ed9e..6ac6bd4e9 100644 --- a/src/__tests__/unit/builtins/regex.test.ts +++ b/src/__tests__/if-run/builtins/regex.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Regex} from '../../../builtins/regex'; +import {Regex} from '../../../if-run/builtins/regex'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {GlobalConfigError, MissingInputDataError, RegexMismatchError} = ERRORS; const {MISSING_GLOBAL_CONFIG, MISSING_INPUT_DATA, REGEX_MISMATCH} = STRINGS; diff --git a/src/__tests__/unit/builtins/sci-embodied.test.ts b/src/__tests__/if-run/builtins/sci-embodied.test.ts similarity index 98% rename from src/__tests__/unit/builtins/sci-embodied.test.ts rename to src/__tests__/if-run/builtins/sci-embodied.test.ts index a3f5bcbff..0c1b504ac 100644 --- a/src/__tests__/unit/builtins/sci-embodied.test.ts +++ b/src/__tests__/if-run/builtins/sci-embodied.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {SciEmbodied} from '../../../builtins/sci-embodied'; +import {SciEmbodied} from '../../../if-run/builtins/sci-embodied'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {InputValidationError} = ERRORS; const {SCI_EMBODIED_ERROR} = STRINGS; diff --git a/src/__tests__/unit/builtins/sci.test.ts b/src/__tests__/if-run/builtins/sci.test.ts similarity index 98% rename from src/__tests__/unit/builtins/sci.test.ts rename to src/__tests__/if-run/builtins/sci.test.ts index 6a8ddc8db..2d75ea170 100644 --- a/src/__tests__/unit/builtins/sci.test.ts +++ b/src/__tests__/if-run/builtins/sci.test.ts @@ -1,6 +1,6 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Sci} from '../../../builtins/sci'; +import {Sci} from '../../../if-run/builtins/sci'; const {MissingInputDataError} = ERRORS; diff --git a/src/__tests__/unit/builtins/shell.test.ts b/src/__tests__/if-run/builtins/shell.test.ts similarity index 98% rename from src/__tests__/unit/builtins/shell.test.ts rename to src/__tests__/if-run/builtins/shell.test.ts index 96de944f1..379863052 100644 --- a/src/__tests__/unit/builtins/shell.test.ts +++ b/src/__tests__/if-run/builtins/shell.test.ts @@ -2,7 +2,7 @@ import {spawnSync} from 'child_process'; import {loadAll} from 'js-yaml'; import {ERRORS} from '@grnsft/if-core/utils'; -import {Shell} from '../../../builtins/shell'; +import {Shell} from '../../../if-run/builtins/shell'; const {InputValidationError, ProcessExecutionError} = ERRORS; diff --git a/src/__tests__/unit/builtins/subtract.test.ts b/src/__tests__/if-run/builtins/subtract.test.ts similarity index 97% rename from src/__tests__/unit/builtins/subtract.test.ts rename to src/__tests__/if-run/builtins/subtract.test.ts index fd4cf7c96..33ba137ab 100644 --- a/src/__tests__/unit/builtins/subtract.test.ts +++ b/src/__tests__/if-run/builtins/subtract.test.ts @@ -1,6 +1,6 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Subtract} from '../../../builtins/subtract'; +import {Subtract} from '../../../if-run/builtins/subtract'; const {InputValidationError} = ERRORS; diff --git a/src/__tests__/unit/builtins/sum.test.ts b/src/__tests__/if-run/builtins/sum.test.ts similarity index 97% rename from src/__tests__/unit/builtins/sum.test.ts rename to src/__tests__/if-run/builtins/sum.test.ts index 1810fc13a..d7530827c 100644 --- a/src/__tests__/unit/builtins/sum.test.ts +++ b/src/__tests__/if-run/builtins/sum.test.ts @@ -1,8 +1,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {Sum} from '../../../builtins/sum'; +import {Sum} from '../../../if-run/builtins/sum'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {GlobalConfigError, InputValidationError} = ERRORS; const {MISSING_GLOBAL_CONFIG} = STRINGS; diff --git a/src/__tests__/unit/builtins/time-sync.test.ts b/src/__tests__/if-run/builtins/time-sync.test.ts similarity index 99% rename from src/__tests__/unit/builtins/time-sync.test.ts rename to src/__tests__/if-run/builtins/time-sync.test.ts index 6dd984659..ab58b3e34 100644 --- a/src/__tests__/unit/builtins/time-sync.test.ts +++ b/src/__tests__/if-run/builtins/time-sync.test.ts @@ -1,9 +1,9 @@ import {ERRORS} from '@grnsft/if-core/utils'; import {Settings, DateTime} from 'luxon'; -import {TimeSync} from '../../../builtins/time-sync'; +import {TimeSync} from '../../../if-run/builtins/time-sync'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; Settings.defaultZone = 'utc'; const { diff --git a/src/__tests__/unit/lib/aggregate.test.ts b/src/__tests__/if-run/lib/aggregate.test.ts similarity index 98% rename from src/__tests__/unit/lib/aggregate.test.ts rename to src/__tests__/if-run/lib/aggregate.test.ts index fb652e219..76de6300c 100644 --- a/src/__tests__/unit/lib/aggregate.test.ts +++ b/src/__tests__/if-run/lib/aggregate.test.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import {aggregate} from '../../../lib/aggregate'; + +import {aggregate} from '../../../if-run/lib/aggregate'; describe('lib/aggregate: ', () => { describe('aggregate(): ', () => { diff --git a/src/__tests__/unit/lib/compute.test.ts b/src/__tests__/if-run/lib/compute.test.ts similarity index 97% rename from src/__tests__/unit/lib/compute.test.ts rename to src/__tests__/if-run/lib/compute.test.ts index 9ff951586..4ed48b5a5 100644 --- a/src/__tests__/unit/lib/compute.test.ts +++ b/src/__tests__/if-run/lib/compute.test.ts @@ -1,8 +1,8 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import {compute} from '../../../lib/compute'; -import {ComputeParams} from '../../../types/compute'; -import {pluginStorage} from '../../../util/plugin-storage'; +import {compute} from '../../../if-run/lib/compute'; +import {ComputeParams} from '../../../if-run/types/compute'; +import {pluginStorage} from '../../../if-run/util/plugin-storage'; describe('lib/compute: ', () => { /** diff --git a/src/__tests__/unit/lib/environment.test.ts b/src/__tests__/if-run/lib/environment.test.ts similarity index 94% rename from src/__tests__/unit/lib/environment.test.ts rename to src/__tests__/if-run/lib/environment.test.ts index c528eaee1..90d575e3e 100644 --- a/src/__tests__/unit/lib/environment.test.ts +++ b/src/__tests__/if-run/lib/environment.test.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/ban-ts-comment */ -import {injectEnvironment} from '../../../lib/environment'; + +import {injectEnvironment} from '../../../if-run/lib/environment'; describe('lib/envirnoment: ', () => { describe('injectEnvironment(): ', () => { diff --git a/src/__tests__/unit/lib/exhaust.test.ts b/src/__tests__/if-run/lib/exhaust.test.ts similarity index 95% rename from src/__tests__/unit/lib/exhaust.test.ts rename to src/__tests__/if-run/lib/exhaust.test.ts index 49db14370..b2be6ebcc 100644 --- a/src/__tests__/unit/lib/exhaust.test.ts +++ b/src/__tests__/if-run/lib/exhaust.test.ts @@ -3,9 +3,8 @@ jest.mock('fs', () => require('../../../__mocks__/fs')); import {ERRORS} from '@grnsft/if-core/utils'; -import {exhaust} from '../../../lib/exhaust'; - -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; +import {exhaust} from '../../../if-run/lib/exhaust'; const {ExhaustOutputArgError, InvalidExhaustPluginError} = ERRORS; const {INVALID_EXHAUST_PLUGIN, OUTPUT_REQUIRED} = STRINGS; diff --git a/src/__tests__/unit/lib/initialize.test.ts b/src/__tests__/if-run/lib/initialize.test.ts similarity index 91% rename from src/__tests__/unit/lib/initialize.test.ts rename to src/__tests__/if-run/lib/initialize.test.ts index ff3c10c45..f00142260 100644 --- a/src/__tests__/unit/lib/initialize.test.ts +++ b/src/__tests__/if-run/lib/initialize.test.ts @@ -2,21 +2,23 @@ jest.mock('mockavizta', () => require('../../../__mocks__/plugin'), { virtual: true, }); -jest.mock('../../../builtins', () => require('../../../__mocks__/plugin'), { - virtual: true, -}); +jest.mock( + '../../../if-run/builtins', + () => require('../../../__mocks__/plugin'), + { + virtual: true, + } +); const mockLog = jest.fn(); -jest.mock('../../../util/log-memoize', () => ({ +jest.mock('../../../if-run/util/log-memoize', () => ({ memoizedLog: mockLog, })); import {ERRORS} from '@grnsft/if-core/utils'; -import {initialize} from '../../../lib/initialize'; - -import {STRINGS} from '../../../config'; - -import {GlobalPlugins} from '../../../types/manifest'; +import {initialize} from '../../../if-run/lib/initialize'; +import {STRINGS} from '../../../if-run/config'; +import {GlobalPlugins} from '../../../common/types/manifest'; const { MissingPluginPathError, @@ -170,7 +172,7 @@ describe('lib/initalize: ', () => { INVALID_MODULE_PATH( plugins.mockavizta.path, new Error( - "Cannot find module 'failing-mock' from 'src/lib/initialize.ts'" + "Cannot find module 'failing-mock' from 'src/if-run/lib/initialize.ts'" ) ) ); diff --git a/src/__tests__/unit/lib/parameterize.test.ts b/src/__tests__/if-run/lib/parameterize.test.ts similarity index 89% rename from src/__tests__/unit/lib/parameterize.test.ts rename to src/__tests__/if-run/lib/parameterize.test.ts index 430d8e043..ed3a4b948 100644 --- a/src/__tests__/unit/lib/parameterize.test.ts +++ b/src/__tests__/if-run/lib/parameterize.test.ts @@ -2,22 +2,22 @@ import {LeveledLogMethod} from 'winston'; const mockLog = jest.fn((message: string) => message); -jest.mock('../../../util/log-memoize', () => ({ +jest.mock('../../../if-run/util/log-memoize', () => ({ memoizedLog: mockLog, })); -jest.mock('../../../util/logger', () => ({ +jest.mock('../../../common/util/logger', () => ({ logger: { warn: mockLog, debug: mockLog, }, })); -import {PARAMETERS} from '../../../config'; -import {parameterize} from '../../../lib/parameterize'; +import {PARAMETERS} from '../../../if-run/config'; +import {parameterize} from '../../../if-run/lib/parameterize'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; -import {ManifestParameter} from '../../../types/manifest'; +import {ManifestParameter} from '../../../common/types/manifest'; const {REJECTING_OVERRIDE, CHECKING_AGGREGATION_METHOD} = STRINGS; diff --git a/src/__tests__/unit/util/aggregation-helper.test.ts b/src/__tests__/if-run/util/aggregation-helper.test.ts similarity index 96% rename from src/__tests__/unit/util/aggregation-helper.test.ts rename to src/__tests__/if-run/util/aggregation-helper.test.ts index 1a23bc3c1..78c8c4aeb 100644 --- a/src/__tests__/unit/util/aggregation-helper.test.ts +++ b/src/__tests__/if-run/util/aggregation-helper.test.ts @@ -1,9 +1,9 @@ import {ERRORS} from '@grnsft/if-core/utils'; import {PluginParams} from '@grnsft/if-core/types'; -import {aggregateInputsIntoOne} from '../../../util/aggregation-helper'; +import {aggregateInputsIntoOne} from '../../../if-run/util/aggregation-helper'; -import {STRINGS} from '../../../config'; +import {STRINGS} from '../../../if-run/config'; const {InvalidAggregationMethodError, MissingAggregationParamError} = ERRORS; const {INVALID_AGGREGATION_METHOD, METRIC_MISSING} = STRINGS; diff --git a/src/__tests__/if-run/util/args.test.ts b/src/__tests__/if-run/util/args.test.ts new file mode 100644 index 000000000..62b0bf922 --- /dev/null +++ b/src/__tests__/if-run/util/args.test.ts @@ -0,0 +1,187 @@ +jest.mock('ts-command-line-args', () => ({ + __esModule: true, + parse: () => { + switch (process.env.result) { + case 'throw-error-object': + throw new Error(MANIFEST_IS_MISSING); + case 'error': + throw MANIFEST_IS_MISSING; + case 'manifest-is-missing': + return {}; + case 'manifest': + return { + manifest: 'manifest-mock.yml', + }; + case 'absolute-path': + return { + manifest: path.normalize(`${processRunningPath}/manifest-mock.yml`), + }; + case 'manifest-output': + return { + manifest: 'manifest-mock.yml', + output: 'output-mock.yml', + }; + case 'override-params': + return { + manifest: 'manifest-mock.yml', + 'override-params': 'override-params-mock.yml', + }; + case 'not-yaml': + return { + manifest: 'mock.notyaml', + }; + case 'stdout': + return { + manifest: 'manifest-mock.yaml', + stdout: true, + }; + default: + return { + manifest: 'mock-manifest.yaml', + output: 'mock-output', + }; + } + }, +})); + +const processRunningPath = process.cwd(); +import * as path from 'node:path'; + +import {ERRORS} from '@grnsft/if-core/utils'; + +import {parseIfRunProcessArgs} from '../../../if-run/util/args'; + +import {STRINGS as COMMON_STRINGS} from '../../../common/config'; + +const {SOURCE_IS_NOT_YAML, MANIFEST_IS_MISSING} = COMMON_STRINGS; +const {CliSourceFileError, ParseCliParamsError} = ERRORS; + +describe('if-run/util/args: ', () => { + describe('parseIfRunProcessArgs(): ', () => { + it('throws error if there is no argument passed.', () => { + expect.assertions(4); + + process.env.result = 'error'; // used for mocking + + try { + parseIfRunProcessArgs(); + } catch (error) { + expect(error).toEqual(MANIFEST_IS_MISSING); + } + + process.env.result = 'throw-error-object'; + + try { + parseIfRunProcessArgs(); + } catch (error) { + expect(error).toEqual(new ParseCliParamsError(MANIFEST_IS_MISSING)); + } + + process.env.result = 'manifest-is-missing'; + + try { + parseIfRunProcessArgs(); + } catch (error) { + expect(error).toBeInstanceOf(CliSourceFileError); + expect(error).toEqual(new CliSourceFileError(MANIFEST_IS_MISSING)); + } + }); + + it('returns manifest path.', () => { + expect.assertions(1); + + process.env.result = 'manifest'; + + const result = parseIfRunProcessArgs(); + const manifestPath = 'manifest-mock.yml'; + const expectedResult = { + inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), + outputOptions: { + stdout: undefined, + }, + }; + + expect(result).toEqual(expectedResult); + }); + + it('returns manifest with absolute path.', () => { + expect.assertions(1); + + process.env.result = 'absolute-path'; + + const result = parseIfRunProcessArgs(); + const manifestPath = 'manifest-mock.yml'; + const expectedResult = { + inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), + outputOptions: {}, + }; + + expect(result).toEqual(expectedResult); + }); + + it('returns manifest with `paramPath`.', () => { + expect.assertions(1); + + process.env.result = 'override-params'; + + const result = parseIfRunProcessArgs(); + const manifestPath = 'manifest-mock.yml'; + const expectedResult = { + inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), + paramPath: 'override-params-mock.yml', + outputOptions: {}, + }; + + expect(result).toEqual(expectedResult); + }); + + it('returns manifest and output path.', () => { + expect.assertions(1); + + process.env.result = 'manifest-output'; + + const result = parseIfRunProcessArgs(); + const manifestPath = 'manifest-mock.yml'; + const outputPath = 'output-mock.yml'; + const expectedResult = { + inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), + outputOptions: { + outputPath: path.normalize(`${processRunningPath}/${outputPath}`), + stdout: undefined, + }, + }; + + expect(result).toEqual(expectedResult); + }); + + it('throws error if file is not yaml.', () => { + expect.assertions(2); + + process.env.result = 'not-yaml'; + + try { + parseIfRunProcessArgs(); + } catch (error) { + expect(error).toBeInstanceOf(CliSourceFileError); + expect(error).toEqual(new CliSourceFileError(SOURCE_IS_NOT_YAML)); + } + }); + + it('returns stdout and manifest.', () => { + expect.assertions(1); + + process.env.result = 'stdout'; + const manifestPath = 'manifest-mock.yaml'; + + const response = parseIfRunProcessArgs(); + const expectedResult = { + inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), + outputOptions: { + stdout: true, + }, + }; + + expect(response).toEqual(expectedResult); + }); + }); +}); diff --git a/src/__tests__/if-run/util/helpers.test.ts b/src/__tests__/if-run/util/helpers.test.ts new file mode 100644 index 000000000..35b60a01b --- /dev/null +++ b/src/__tests__/if-run/util/helpers.test.ts @@ -0,0 +1,169 @@ +const mockWarn = jest.fn(); +const mockError = jest.fn(); + +import {ERRORS} from '@grnsft/if-core/utils'; + +import {andHandle, mergeObjects} from '../../../if-run/util/helpers'; + +const {WriteFileError} = ERRORS; + +jest.mock('../../../common/util/logger', () => ({ + logger: { + warn: mockWarn, + error: mockError, + }, +})); + +describe('if-run/util/helpers: ', () => { + describe('andHandle(): ', () => { + afterEach(() => { + mockWarn.mockReset(); + mockError.mockReset(); + }); + + it('logs error in case of error is unknown.', () => { + const message = 'mock-message'; + + andHandle(new WriteFileError(message)); + expect(mockWarn).toHaveBeenCalledTimes(0); + expect(mockError).toHaveBeenCalledTimes(1); + }); + }); + + describe('mergeObjects(): ', () => { + it('does not override input.', () => { + expect.assertions(1); + + const input = { + a: 1, + b: false, + c: 'testInput', + }; + + const defaults = { + c: 'testDefault', + }; + const result = mergeObjects(defaults, input); + + expect(result).toEqual(input); + }); + + it('overrides null/undefined inputs.', () => { + expect.assertions(1); + + const input = { + a: 1, + b: false, + c: 'testInput', + d: null, + e: undefined, + }; + + const defaults = { + c: 'testDefault', + d: 'testDefault', + e: 'testDefault', + }; + const result = mergeObjects(defaults, input); + const expectedResult = { + a: 1, + b: false, + c: 'testInput', + d: 'testDefault', + e: 'testDefault', + }; + + expect(result).toEqual(expectedResult); + }); + + it('adds only properties missing in input.', () => { + expect.assertions(1); + + const input = { + a: 1, + b: false, + c: 'testInput', + }; + + const defaults = { + b: true, + c: 'testDefault', + d: 25, + }; + + const result = mergeObjects(defaults, input); + const expectedResult = { + a: 1, + b: false, + c: 'testInput', + d: 25, + }; + + expect(result).toEqual(expectedResult); + }); + + it('keeps values from input in case of nested objects.', () => { + expect.assertions(1); + + const input = { + a: 1, + b: false, + c: 'testInput', + d: { + e: 1, + }, + }; + + const defaults = { + b: true, + c: 'testDefault1', + d: { + e: 25, + f: 'testDefault2', + }, + }; + + const result = mergeObjects(defaults, input); + const expectedResult = { + a: 1, + b: false, + c: 'testInput', + d: { + e: 1, + }, + }; + + expect(result).toEqual(expectedResult); + }); + + it('keeps value from input in case of arrays.', () => { + expect.assertions(1); + + const input = { + a: 1, + b: false, + c: 'testInput1', + d: [1, 2, 3, 4], + e: 'testInput2', + }; + + const defaults = { + b: true, + c: 'testDefault1', + d: [5, 6, 7, 8, 9], + e: [1, 2, 3], + }; + + const result = mergeObjects(defaults, input); + const expectedResult = { + a: 1, + b: false, + c: 'testInput1', + d: [1, 2, 3, 4], + e: 'testInput2', + }; + + expect(result).toEqual(expectedResult); + }); + }); +}); diff --git a/src/__tests__/unit/util/json.test.ts b/src/__tests__/if-run/util/json.test.ts similarity index 91% rename from src/__tests__/unit/util/json.test.ts rename to src/__tests__/if-run/util/json.test.ts index 1bb1cd451..5836c677a 100644 --- a/src/__tests__/unit/util/json.test.ts +++ b/src/__tests__/if-run/util/json.test.ts @@ -1,6 +1,6 @@ jest.mock('fs/promises', () => require('../../../__mocks__/fs')); -import {readAndParseJson} from '../../../util/json'; +import {readAndParseJson} from '../../../if-run/util/json'; describe('util/json: ', () => { describe('readAndParseJson(): ', () => { diff --git a/src/__tests__/unit/util/log-memoize.test.ts b/src/__tests__/if-run/util/log-memoize.test.ts similarity index 92% rename from src/__tests__/unit/util/log-memoize.test.ts rename to src/__tests__/if-run/util/log-memoize.test.ts index 275ad9c22..a5d73b15b 100644 --- a/src/__tests__/unit/util/log-memoize.test.ts +++ b/src/__tests__/if-run/util/log-memoize.test.ts @@ -1,5 +1,6 @@ import {LeveledLogMethod} from 'winston'; -import {memoizedLog} from '../../../util/log-memoize'; + +import {memoizedLog} from '../../../if-run/util/log-memoize'; describe('util/log-memoize: ', () => { describe('momoizedLog(): ', () => { diff --git a/src/__tests__/unit/util/os-checker.test.ts b/src/__tests__/if-run/util/os-checker.test.ts similarity index 97% rename from src/__tests__/unit/util/os-checker.test.ts rename to src/__tests__/if-run/util/os-checker.test.ts index d01a7a8da..20d5ffea0 100644 --- a/src/__tests__/unit/util/os-checker.test.ts +++ b/src/__tests__/if-run/util/os-checker.test.ts @@ -9,7 +9,7 @@ jest.mock('os', () => ({ }, release: () => 'm.m.m', })); -jest.mock('../../../util/helpers', () => ({ +jest.mock('../../../common/util/helpers', () => ({ execPromise: async () => { if (process.env.KIND === 'darwin' && process.env.REJECT === 'true') return { @@ -58,7 +58,7 @@ OS Version: 10.0.22631 N/A Build 22631 }, })); -import {osInfo} from '../../../util/os-checker'; +import {osInfo} from '../../../if-run/util/os-checker'; describe('util/os-checker: ', () => { describe('osInfo(): ', () => { diff --git a/src/__tests__/unit/util/plugin-storage.test.ts b/src/__tests__/if-run/util/plugin-storage.test.ts similarity index 96% rename from src/__tests__/unit/util/plugin-storage.test.ts rename to src/__tests__/if-run/util/plugin-storage.test.ts index 8b76f36cd..885ccb107 100644 --- a/src/__tests__/unit/util/plugin-storage.test.ts +++ b/src/__tests__/if-run/util/plugin-storage.test.ts @@ -1,6 +1,6 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {pluginStorage} from '../../../util/plugin-storage'; +import {pluginStorage} from '../../../if-run/util/plugin-storage'; const {PluginInitializationError} = ERRORS; diff --git a/src/__tests__/integration/helpers/common.ts b/src/__tests__/integration/helpers/common.ts deleted file mode 100644 index 47b013e88..000000000 --- a/src/__tests__/integration/helpers/common.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {exec} from 'node:child_process'; -import {promisify} from 'node:util'; - -export const execPromise = promisify(exec); - -const stripAnsi = (text: string) => { - const pattern = [ - '[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)', - '(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))', - ].join('|'); - - const ansiRegex = new RegExp(pattern, 'g'); - - return text.replaceAll(ansiRegex, ''); -}; - -export const getJSONFromText = (text: string) => { - const textWithoutAnsi = stripAnsi(text); - const jsonRegex = /\{[^]*\}/; - const jsonMatch = textWithoutAnsi.match(jsonRegex); - - if (jsonMatch) { - const jsonString = jsonMatch[0]; - - return JSON.parse(jsonString); - } else { - throw new Error('No JSON found in the log.'); - } -}; diff --git a/src/__tests__/integration/helpers/module-installer.ts b/src/__tests__/integration/helpers/module-installer.ts deleted file mode 100644 index a70ede21a..000000000 --- a/src/__tests__/integration/helpers/module-installer.ts +++ /dev/null @@ -1,11 +0,0 @@ -import {execPromise} from './common'; - -const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm'; - -export const npmInstallPackage = (module: string) => { - return execPromise(`${npmCommand} install ${module} --s`); -}; - -export const npmUninstallPackage = (module: string) => { - return execPromise(`${npmCommand} uninstall ${module}`); -}; diff --git a/src/__tests__/integration/scenarios/sci-e.ts b/src/__tests__/integration/scenarios/sci-e.ts deleted file mode 100644 index 0342240c2..000000000 --- a/src/__tests__/integration/scenarios/sci-e.ts +++ /dev/null @@ -1,75 +0,0 @@ -import * as YAML from 'js-yaml'; - -import {openYamlFileAsObject, saveYamlFileAs} from '../../../util/yaml'; -import {Manifest} from '../../../types/manifest'; -import { - npmInstallPackage, - npmUninstallPackage, -} from '../helpers/module-installer'; -import {execPromise} from '../helpers/common'; -import {sciEInputData} from '../test-data/sci-e'; - -describe('integration/sci-e', () => { - const modelName = 'sci-e'; - const absoluteManifestPath = `${__dirname}/../manifest/sci-e.yaml`; - const relativeManifestPath = 'src/__tests__/integration/manifest/sci-e.yaml'; - const implTemplatePath = `${__dirname}/../templates/integration.yaml`; - - beforeAll(() => { - return npmInstallPackage('@grnsft/if-plugins'); - }, 15000); - - it('output creation without ompl path.', async () => { - const file = await openYamlFileAsObject(implTemplatePath); - - file.initialize.plugins[modelName] = { - method: 'SciE', - path: '@grnsft/if-plugins', - }; - file.initialize.outputs = []; - - file.tree.children.child.pipeline = [modelName]; - file.tree.children.child.config = {}; - file.tree.children.child.config[modelName] = {}; - file.tree.children.child.inputs = sciEInputData['success-3-params']; - - await saveYamlFileAs(file, absoluteManifestPath); // save yaml uses absolute path - const response = ( - await execPromise( - `npm run if-run -- --manifest ${relativeManifestPath} --stdout` - ) - ).stdout; // exec promise uses relative path - - const yamlPart = response.substring( - response.indexOf('name:'), - response.length - ); - const finalOutputParsed: any = YAML.load(yamlPart); - - // assertions - const path = finalOutputParsed.tree.children.child.outputs[0]; - const manifestPath = file.tree.children.child.inputs[0]; - - // assert timestamp - expect(finalOutputParsed.tree.children.child.inputs[0].timestamp).toEqual( - file.tree.children.child.inputs[0].timestamp - ); - - // assert duration - expect(finalOutputParsed.tree.children.child.inputs[0].duration).toEqual( - file.tree.children.child.inputs[0].duration - ); - - // assert total energy - const sum = - manifestPath['cpu/energy'] + - manifestPath['memory/energy'] + - manifestPath['network/energy']; - - expect(path.energy).toEqual(sum); - }, 15000); - - afterAll(() => { - return npmUninstallPackage('@grnsft/if-plugins'); - }, 15000); -}); diff --git a/src/__tests__/integration/templates/integration.yaml b/src/__tests__/integration/templates/integration.yaml deleted file mode 100644 index 2d1523e91..000000000 --- a/src/__tests__/integration/templates/integration.yaml +++ /dev/null @@ -1,16 +0,0 @@ -name: integration-template -description: -tags: -initialize: - plugins: - 'sci-e': - model: ${model} - path: ${path} -tree: - children: - child: - pipeline: - - ${name} - config: - inputs: - - ${input} diff --git a/src/__tests__/integration/test-data/sci-e.ts b/src/__tests__/integration/test-data/sci-e.ts deleted file mode 100644 index e6a5acde7..000000000 --- a/src/__tests__/integration/test-data/sci-e.ts +++ /dev/null @@ -1,11 +0,0 @@ -export const sciEInputData = { - 'success-3-params': [ - { - timestamp: '2023-08-06T00:00', - duration: 3600, - 'cpu/energy': 10, - 'memory/energy': 100, - 'network/energy': 100, - }, - ], -}; diff --git a/src/__tests__/unit/util/args.test.ts b/src/__tests__/unit/util/args.test.ts deleted file mode 100644 index f4238cbce..000000000 --- a/src/__tests__/unit/util/args.test.ts +++ /dev/null @@ -1,522 +0,0 @@ -const processRunningPath = process.cwd(); - -jest.mock('../../../util/fs', () => ({ - isFileExists: () => { - if (process.env.fileExists === 'true') { - return true; - } - return false; - }, - isDirectoryExists: () => { - if (process.env.directoryExists === 'true') { - return true; - } - return false; - }, -})); - -jest.mock('ts-command-line-args', () => ({ - __esModule: true, - parse: () => { - switch (process.env.result) { - case 'throw-error-object': - throw new Error(MANIFEST_IS_MISSING); - case 'error': - throw MANIFEST_IS_MISSING; - case 'manifest-is-missing': - return {}; - case 'manifest': - return { - manifest: 'manifest-mock.yml', - }; - case 'absolute-path': - return { - manifest: path.normalize(`${processRunningPath}/manifest-mock.yml`), - }; - case 'manifest-output': - return { - manifest: 'manifest-mock.yml', - output: 'output-mock.yml', - }; - case 'override-params': - return { - manifest: 'manifest-mock.yml', - 'override-params': 'override-params-mock.yml', - }; - case 'not-yaml': - return { - manifest: 'mock.notyaml', - }; - case 'stdout': - return { - manifest: 'manifest-mock.yaml', - stdout: true, - }; - /** If-diff mocks */ - case 'only-target': - return { - target: 'target-mock.yml', - }; - case 'target-is-not-yaml': - return { - target: 'target-mock', - }; - case 'source-is-not-yaml': - return { - target: 'target-mock.yml', - source: 'source-mock', - }; - case 'target-source': - return { - target: 'target-mock.yml', - source: 'source-mock.yml', - }; - case 'diff-throw-error': - throw new Error('mock-error'); - case 'diff-throw': - throw 'mock-error'; - /** If-env mocks */ - case 'manifest-install-provided': - return { - install: true, - manifest: 'mock-manifest.yaml', - }; - case 'manifest-is-not-yaml': - return {manifest: 'manifest'}; - case 'manifest-path-invalid': - throw new Error(MANIFEST_NOT_FOUND); - case 'env-throw-error': - throw new Error('mock-error'); - case 'env-throw': - throw 'mock-error'; - /** If-check */ - case 'manifest-is-provided': - return {manifest: 'mock-manifest.yaml'}; - case 'directory-is-provided': - return {directory: '/mock-directory'}; - case 'flags-are-not-provided': - return {manifest: undefined, directory: undefined}; - default: - return { - manifest: 'mock-manifest.yaml', - output: 'mock-output', - }; - } - }, -})); - -import * as path from 'node:path'; -import {ERRORS} from '@grnsft/if-core/utils'; - -import { - parseIEProcessArgs, - parseIfCheckArgs, - parseIfDiffArgs, - parseIfEnvArgs, -} from '../../../util/args'; - -import {STRINGS} from '../../../config'; - -const { - CliSourceFileError, - ParseCliParamsError, - InvalidDirectoryError, - MissingCliFlagsError, -} = ERRORS; - -const { - MANIFEST_IS_MISSING, - TARGET_IS_NOT_YAML, - INVALID_TARGET, - SOURCE_IS_NOT_YAML, - MANIFEST_NOT_FOUND, - DIRECTORY_NOT_FOUND, - IF_CHECK_FLAGS_MISSING, -} = STRINGS; - -describe('util/args: ', () => { - const originalEnv = process.env; - - describe('parseIEProcessArgs(): ', () => { - it('throws error if there is no argument passed.', () => { - expect.assertions(5); - - process.env.result = 'error'; // used for mocking - - try { - parseIEProcessArgs(); - } catch (error) { - expect(error).toEqual(MANIFEST_IS_MISSING); - } - - process.env.result = 'throw-error-object'; - - try { - parseIEProcessArgs(); - } catch (error) { - expect(error).toBeInstanceOf(ParseCliParamsError); - expect(error).toEqual(new ParseCliParamsError(MANIFEST_IS_MISSING)); - } - - process.env.result = 'manifest-is-missing'; - - try { - parseIEProcessArgs(); - } catch (error) { - expect(error).toBeInstanceOf(CliSourceFileError); - expect(error).toEqual(new CliSourceFileError(MANIFEST_IS_MISSING)); - } - }); - - it('returns manifest path.', () => { - expect.assertions(1); - - process.env.result = 'manifest'; - - const result = parseIEProcessArgs(); - const manifestPath = 'manifest-mock.yml'; - const expectedResult = { - inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), - outputOptions: { - stdout: undefined, - }, - }; - - expect(result).toEqual(expectedResult); - }); - - it('returns manifest with absolute path.', () => { - expect.assertions(1); - - process.env.result = 'absolute-path'; - - const result = parseIEProcessArgs(); - const manifestPath = 'manifest-mock.yml'; - const expectedResult = { - inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), - outputOptions: {}, - }; - - expect(result).toEqual(expectedResult); - }); - - it('returns manifest with `paramPath`.', () => { - expect.assertions(1); - - process.env.result = 'override-params'; - - const result = parseIEProcessArgs(); - const manifestPath = 'manifest-mock.yml'; - const expectedResult = { - inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), - paramPath: 'override-params-mock.yml', - outputOptions: {}, - }; - - expect(result).toEqual(expectedResult); - }); - - it('returns manifest and output path.', () => { - expect.assertions(1); - - process.env.result = 'manifest-output'; - - const result = parseIEProcessArgs(); - const manifestPath = 'manifest-mock.yml'; - const outputPath = 'output-mock.yml'; - const expectedResult = { - inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), - outputOptions: { - outputPath: path.normalize(`${processRunningPath}/${outputPath}`), - stdout: undefined, - }, - }; - - expect(result).toEqual(expectedResult); - }); - - it('throws error if file is not yaml.', () => { - expect.assertions(2); - - process.env.result = 'not-yaml'; - - try { - parseIEProcessArgs(); - } catch (error) { - expect(error).toBeInstanceOf(CliSourceFileError); - expect(error).toEqual(new CliSourceFileError(SOURCE_IS_NOT_YAML)); - } - }); - - it('returns stdout and manifest.', () => { - expect.assertions(1); - - process.env.result = 'stdout'; - const manifestPath = 'manifest-mock.yaml'; - - const response = parseIEProcessArgs(); - const expectedResult = { - inputPath: path.normalize(`${processRunningPath}/${manifestPath}`), - outputOptions: { - stdout: true, - }, - }; - - expect(response).toEqual(expectedResult); - }); - }); - - describe('parseIfDiffArgs(): ', () => { - it('throws error if `target` is missing.', () => { - expect.assertions(1); - - try { - parseIfDiffArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError(INVALID_TARGET)); - } - } - }); - - it('throws error if `target` is not a yaml.', () => { - process.env.result = 'target-is-not-yaml'; - expect.assertions(1); - - try { - parseIfDiffArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError(TARGET_IS_NOT_YAML)); - } - } - }); - - it('returns `target`s full path.', () => { - process.env.result = 'only-target'; - expect.assertions(1); - - const response = parseIfDiffArgs(); - expect(response).toHaveProperty('targetPath'); - }); - - it('throws error if source is not a yaml.', () => { - process.env.result = 'source-is-not-yaml'; - expect.assertions(1); - - try { - parseIfDiffArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError(SOURCE_IS_NOT_YAML)); - } - } - }); - - it('returns target and source full paths.', () => { - process.env.result = 'target-source'; - expect.assertions(2); - - const response = parseIfDiffArgs(); - expect(response).toHaveProperty('targetPath'); - expect(response).toHaveProperty('sourcePath'); - }); - - it('throws error if parsing failed.', () => { - process.env.result = 'diff-throw-error'; - expect.assertions(1); - - try { - parseIfDiffArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError('mock-error')); - } - } - }); - - it('throws error if parsing failed (not instance of error).', () => { - process.env.result = 'diff-throw'; - expect.assertions(1); - - try { - parseIfDiffArgs(); - } catch (error) { - expect(error).toEqual('mock-error'); - } - }); - }); - - describe('parseIfEnvArgs(): ', () => { - it('executes if `manifest` is missing.', async () => { - process.env.fileExists = 'true'; - process.env.result = 'manifest-is-missing'; - const response = await parseIfEnvArgs(); - - expect.assertions(1); - - expect(response).toEqual({install: undefined}); - }); - - it('executes if `manifest` and `install` are provided.', async () => { - process.env.fileExists = 'true'; - process.env.result = 'manifest-install-provided'; - - const response = await parseIfEnvArgs(); - - expect.assertions(2); - expect(response).toHaveProperty('install'); - expect(response).toHaveProperty('manifest'); - }); - - it('throws an error if `manifest` is not a yaml.', async () => { - process.env.fileExists = 'true'; - process.env.result = 'manifest-is-not-yaml'; - expect.assertions(1); - - try { - await parseIfEnvArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new CliSourceFileError(SOURCE_IS_NOT_YAML)); - } - } - }); - - it('throws an error if `manifest` path is invalid.', async () => { - process.env.fileExists = 'false'; - expect.assertions(1); - - try { - await parseIfEnvArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError(MANIFEST_NOT_FOUND)); - } - } - }); - - it('throws an error if parsing failed.', async () => { - process.env.result = 'env-throw-error'; - expect.assertions(1); - - try { - await parseIfEnvArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError('mock-error')); - } - } - }); - - it('throws error if parsing failed (not instance of error).', async () => { - process.env.result = 'env-throw'; - expect.assertions(1); - - try { - await parseIfEnvArgs(); - } catch (error) { - expect(error).toEqual('mock-error'); - } - }); - }); - - describe('parseIfCheckArgs(): ', () => { - it('executes when `manifest` is provided.', async () => { - process.env.fileExists = 'true'; - process.env.result = 'manifest-is-provided'; - const response = await parseIfCheckArgs(); - - expect.assertions(1); - - expect(response).toEqual({manifest: 'mock-manifest.yaml'}); - }); - - it('executes when the `directory` is provided.', async () => { - process.env.directoryExists = 'true'; - process.env.result = 'directory-is-provided'; - - const response = await parseIfCheckArgs(); - - expect.assertions(1); - - expect(response).toEqual({directory: '/mock-directory'}); - }); - - it('throws an error when the `directory` does not exist.', async () => { - process.env.directoryExists = 'false'; - process.env.result = 'directory-is-provided'; - expect.assertions(1); - - try { - await parseIfCheckArgs(); - } catch (error) { - expect(error).toEqual(new InvalidDirectoryError(DIRECTORY_NOT_FOUND)); - } - }); - - it('throws an error when both `manifest` and `directory` flags are not provided.', async () => { - process.env.result = 'flags-are-not-provided'; - expect.assertions(1); - - try { - await parseIfCheckArgs(); - } catch (error) { - expect(error).toEqual(new MissingCliFlagsError(IF_CHECK_FLAGS_MISSING)); - } - }); - - it('throws an error if `manifest` is not a yaml.', async () => { - process.env.fileExists = 'true'; - process.env.result = 'manifest-is-not-yaml'; - expect.assertions(1); - - try { - await parseIfCheckArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new CliSourceFileError(SOURCE_IS_NOT_YAML)); - } - } - }); - - it('throws an error if `manifest` path is invalid.', async () => { - process.env.fileExists = 'false'; - expect.assertions(1); - - try { - await parseIfCheckArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError(MANIFEST_NOT_FOUND)); - } - } - }); - - it('throws an error if parsing failed.', async () => { - process.env.result = 'env-throw-error'; - expect.assertions(1); - - try { - await parseIfCheckArgs(); - } catch (error) { - if (error instanceof Error) { - expect(error).toEqual(new ParseCliParamsError('mock-error')); - } - } - }); - - it('throws error if parsing failed (not instance of error).', async () => { - process.env.result = 'env-throw'; - expect.assertions(1); - - try { - await parseIfCheckArgs(); - } catch (error) { - expect(error).toEqual('mock-error'); - } - }); - }); - - process.env = originalEnv; -}); diff --git a/src/__tests__/unit/util/helpers.test.ts b/src/__tests__/unit/util/helpers.test.ts deleted file mode 100644 index ed46dc0d9..000000000 --- a/src/__tests__/unit/util/helpers.test.ts +++ /dev/null @@ -1,687 +0,0 @@ -/* eslint-disable @typescript-eslint/ban-ts-comment */ -const mockWarn = jest.fn(); -const mockError = jest.fn(); - -jest.mock('node:readline/promises', () => - require('../../../__mocks__/readline') -); -jest.mock('../../../util/logger', () => ({ - logger: { - warn: mockWarn, - error: mockError, - }, -})); - -jest.mock('path', () => { - const actualPath = jest.requireActual('path') as Record; - return { - __esModule: true, - ...actualPath, - dirname: jest.fn(() => './mock-path'), - }; -}); - -jest.mock('fs/promises', () => require('../../../__mocks__/fs')); - -jest.mock('../../../lib/load', () => ({ - load: jest.fn(() => { - if (process.env.manifest === 'true') { - return { - rawManifest: { - name: 'divide', - initialize: { - plugins: { - 'cloud-metadata': { - path: '@grnsft/if-plugins', - method: 'CloudMetadata', - }, - divide: { - path: 'builtin', - method: 'Divide', - 'global-config': { - numerator: 'vcpus-allocated', - denominator: 2, - output: 'cpu/number-cores', - }, - }, - }, - }, - execution: { - environment: { - dependencies: [ - '@grnsft/if-core@0.0.7', - '@grnsft/if-plugins@v0.3.2 extraneous -> file:../../../if-models', - '@grnsft/if-unofficial-plugins@v0.3.0 extraneous -> file:../../../if-unofficial-models', - ], - }, - }, - }, - }; - } - return { - rawManifest: { - initialize: { - plugins: {'@grnsft/if-plugins': '1.0.0'}, - }, - execution: { - environment: { - dependencies: [], - }, - }, - }, - }; - }), -})); - -const initPackage = jest.fn(() => Promise.resolve('mock-path')); -const updatePackage = jest.fn(() => Promise.resolve(true)); -const installdeps = jest.fn(); -const updatedeps = jest.fn(); -jest.mock('../../../util/npm', () => { - const actualNPMUtil = jest.requireActual('../../../util/npm'); - - return { - ...actualNPMUtil, - initPackageJsonIfNotExists: (folderPath: string) => { - if (process.env.NPM_MOCK === 'true') { - return initPackage(); - } - - if (process.env.NPM_MOCK === 'error') { - throw new Error('mock-error'); - } - - return actualNPMUtil.initPackageJsonIfNotExists(folderPath); - }, - updatePackageJsonProperties: ( - newPackageJsonPath: string, - appendDependencies: boolean - ) => { - if (process.env.NPM_MOCK === 'true') { - return updatePackage(); - } - - return actualNPMUtil.updatePackageJsonProperties( - newPackageJsonPath, - appendDependencies - ); - }, - installDependencies: ( - folderPath: string, - dependencies: {[path: string]: string} - ) => { - if (process.env.NPM_MOCK === 'true') { - return installdeps(); - } - - return actualNPMUtil.installDependencies(folderPath, dependencies); - }, - updatePackageJsonDependencies: ( - packageJsonPath: string, - dependencies: any, - cwd: boolean - ) => { - if (process.env.NPM_MOCK === 'true') { - return updatedeps(); - } - - return actualNPMUtil.updatePackageJsonDependencies( - packageJsonPath, - dependencies, - cwd - ); - }, - }; -}); - -import {ERRORS} from '@grnsft/if-core/utils'; - -import { - andHandle, - checkIfEqual, - formatNotMatchingLog, - mergeObjects, - oneIsPrimitive, - parseManifestFromStdin, - getOptionsFromArgs, - addTemplateManifest, - initializeAndInstallLibs, - logStdoutFailMessage, -} from '../../../util/helpers'; -import {CONFIG} from '../../../config'; -import {Difference} from '../../../types/lib/compare'; - -const {IF_ENV} = CONFIG; -const {FAILURE_MESSAGE_DEPENDENCIES, FAILURE_MESSAGE} = IF_ENV; - -const {WriteFileError} = ERRORS; - -describe('util/helpers: ', () => { - describe('andHandle(): ', () => { - afterEach(() => { - mockWarn.mockReset(); - mockError.mockReset(); - }); - - it('logs error in case of error is unknown.', () => { - const message = 'mock-message'; - - andHandle(new WriteFileError(message)); - expect(mockWarn).toHaveBeenCalledTimes(0); - expect(mockError).toHaveBeenCalledTimes(1); - }); - }); - - describe('mergeObjects(): ', () => { - it('does not override input.', () => { - expect.assertions(1); - - const input = { - a: 1, - b: false, - c: 'testInput', - }; - - const defaults = { - c: 'testDefault', - }; - const result = mergeObjects(defaults, input); - - expect(result).toEqual(input); - }); - - it('overrides null/undefined inputs.', () => { - expect.assertions(1); - - const input = { - a: 1, - b: false, - c: 'testInput', - d: null, - e: undefined, - }; - - const defaults = { - c: 'testDefault', - d: 'testDefault', - e: 'testDefault', - }; - const result = mergeObjects(defaults, input); - const expectedResult = { - a: 1, - b: false, - c: 'testInput', - d: 'testDefault', - e: 'testDefault', - }; - - expect(result).toEqual(expectedResult); - }); - - it('adds only properties missing in input.', () => { - expect.assertions(1); - - const input = { - a: 1, - b: false, - c: 'testInput', - }; - - const defaults = { - b: true, - c: 'testDefault', - d: 25, - }; - - const result = mergeObjects(defaults, input); - const expectedResult = { - a: 1, - b: false, - c: 'testInput', - d: 25, - }; - - expect(result).toEqual(expectedResult); - }); - - it('keeps values from input in case of nested objects.', () => { - expect.assertions(1); - - const input = { - a: 1, - b: false, - c: 'testInput', - d: { - e: 1, - }, - }; - - const defaults = { - b: true, - c: 'testDefault1', - d: { - e: 25, - f: 'testDefault2', - }, - }; - - const result = mergeObjects(defaults, input); - const expectedResult = { - a: 1, - b: false, - c: 'testInput', - d: { - e: 1, - }, - }; - - expect(result).toEqual(expectedResult); - }); - - it('keeps value from input in case of arrays.', () => { - expect.assertions(1); - - const input = { - a: 1, - b: false, - c: 'testInput1', - d: [1, 2, 3, 4], - e: 'testInput2', - }; - - const defaults = { - b: true, - c: 'testDefault1', - d: [5, 6, 7, 8, 9], - e: [1, 2, 3], - }; - - const result = mergeObjects(defaults, input); - const expectedResult = { - a: 1, - b: false, - c: 'testInput1', - d: [1, 2, 3, 4], - e: 'testInput2', - }; - - expect(result).toEqual(expectedResult); - }); - }); - - describe('formatNotMatchingLog(): ', () => { - const actualLogger = console.log; - const mockLogger = jest.fn(); - console.log = mockLogger; - - beforeEach(() => { - mockLogger.mockReset(); - }); - - it('logs the message.', () => { - const difference: Difference = { - message: 'mock-message', - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(1); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - }); - - it('logs message and path.', () => { - const difference: Difference = { - message: 'mock-message', - path: 'mock.path', - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(2); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - expect(mockLogger).toHaveBeenCalledWith(difference.path); - }); - - it('logs message, path and formatted source/target (one is missing).', () => { - const difference: Difference = { - message: 'mock-message', - path: 'mock.path', - source: 'mock-source', - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(4); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - expect(mockLogger).toHaveBeenCalledWith(difference.path); - expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); - expect(mockLogger).toHaveBeenCalledWith('target: missing'); - }); - - it('logs message, path and formatted source/target.', () => { - const difference: Difference = { - message: 'mock-message', - path: 'mock.path', - source: 'mock-source', - target: 'mock-target', - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(4); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - expect(mockLogger).toHaveBeenCalledWith(difference.path); - expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); - expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); - }); - - it('logs message, path and formatted source/target (numbers).', () => { - const difference: Difference = { - message: 'mock-message', - path: 'mock.path', - source: 10, - target: 0, - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(4); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - expect(mockLogger).toHaveBeenCalledWith(difference.path); - expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); - expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); - }); - - it('logs message, path and formatted source/target (booleans).', () => { - const difference: Difference = { - message: 'mock-message', - path: 'mock.path', - source: true, - target: false, - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(4); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - expect(mockLogger).toHaveBeenCalledWith(difference.path); - expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); - expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); - }); - - it('logs message, path and formatted source/target (objects).', () => { - const difference: Difference = { - message: 'mock-message', - path: 'mock.path', - source: {}, - target: false, - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(4); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - expect(mockLogger).toHaveBeenCalledWith(difference.path); - expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); - expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); - }); - - it('logs message, path and formatted source/target (empty string).', () => { - const difference: Difference = { - message: 'mock-message', - path: 'mock.path', - source: '', - target: false, - }; - - formatNotMatchingLog(difference); - expect(mockLogger).toHaveBeenCalledTimes(4); - expect(mockLogger).toHaveBeenCalledWith(difference.message); - expect(mockLogger).toHaveBeenCalledWith(difference.path); - expect(mockLogger).toHaveBeenCalledWith(`source: ${difference.source}`); - expect(mockLogger).toHaveBeenCalledWith(`target: ${difference.target}`); - }); - - afterAll(() => { - console.log = actualLogger; - }); - }); - - describe('oneIsPrimitive(): ', () => { - it('returns true if values are nullish.', () => { - const source = null; - const target = undefined; - - const result = oneIsPrimitive(source, target); - expect(result).toBeTruthy(); - }); - - it('returns true if values are string or number.', () => { - const source = 'string'; - const target = 10; - - const result = oneIsPrimitive(source, target); - expect(result).toBeTruthy(); - }); - - it('returns false if one of values is object.', () => { - const source = 'string'; - const target = {}; - - const result = oneIsPrimitive(source, target); - expect(result).toBeFalsy(); - }); - }); - - describe('parseManifestFromStdin(): ', () => { - it('returns empty string if there is no data in stdin.', async () => { - const response = await parseManifestFromStdin(); - const expectedResult = ''; - - expect(response).toEqual(expectedResult); - }); - - it('returns empty string if nothing is piped.', async () => { - const originalIsTTY = process.stdin.isTTY; - process.stdin.isTTY = true; - const response = await parseManifestFromStdin(); - const expectedResult = ''; - - expect(response).toEqual(expectedResult); - process.stdin.isTTY = originalIsTTY; - }); - - it('throws error if there is no manifest in stdin.', async () => { - process.env.readline = 'no_manifest'; - expect.assertions(1); - - const response = await parseManifestFromStdin(); - - expect(response).toEqual(''); - }); - - it('returns empty string if there is no data in stdin.', async () => { - process.env.readline = 'manifest'; - const response = await parseManifestFromStdin(); - const expectedMessage = ` -name: mock-name -description: mock-description -`; - - expect(response).toEqual(expectedMessage); - }); - }); - - describe('checkIfEqual(): ', () => { - it('checks if values are equal.', () => { - const a = 'mock'; - const b = 'mock'; - - const response = checkIfEqual(a, b); - expect(response).toBeTruthy(); - }); - - it('returns true if one of the values is wildcard.', () => { - const a = 'mock'; - const b = '*'; - - const response = checkIfEqual(a, b); - expect(response).toBeTruthy(); - }); - - it('returns false for number and string with the same value.', () => { - const a = 5; - const b = '5'; - - const response = checkIfEqual(a, b); - expect(response).toBeFalsy(); - }); - }); - - describe('getOptionsFromArgs(): ', () => { - it('returns the correct options when dependencies are present.', async () => { - const commandArgs = { - manifest: '/path/to/mock-manifest.json', - install: false, - }; - - process.env.manifest = 'true'; - - const result = await getOptionsFromArgs(commandArgs); - expect.assertions(1); - - expect(result).toEqual({ - folderPath: './mock-path', - dependencies: { - '@grnsft/if-plugins': '^v0.3.2', - }, - install: false, - }); - }); - - it('throws an error when there are no dependencies.', async () => { - const commandArgs = { - manifest: '/path/to/mock-manifest.json', - install: false, - }; - - process.env.manifest = 'false'; - - expect.assertions(1); - try { - await getOptionsFromArgs(commandArgs); - } catch (error) { - expect(error).toEqual(new Error(FAILURE_MESSAGE_DEPENDENCIES)); - } - }); - }); - - describe('addTemplateManifest(): ', () => { - it('successfully adds the template manifest to the directory.', async () => { - await addTemplateManifest('./'); - - expect.assertions(1); - }); - - it('throws an error when the manifest is not added into the directory.', async () => { - expect.assertions(1); - - try { - await addTemplateManifest(''); - } catch (error) { - const logSpy = jest.spyOn(global.console, 'log'); - expect(logSpy).toEqual(FAILURE_MESSAGE); - } - }); - }); - - describe('initializeAndInstallLibs(): ', () => { - beforeEach(() => { - initPackage.mockReset(); - updatePackage.mockReset(); - installdeps.mockReset(); - updatedeps.mockReset(); - }); - - it('installs dependencies if install flag is truthy.', async () => { - process.env.NPM_MOCK = 'true'; - // @ts-ignore - process.exit = (code: any) => code; - const options = { - folderPath: 'mock-folderPath', - install: true, - cwd: true, - dependencies: { - mock: 'mock-dependencies', - }, - }; - - expect.assertions(4); - await initializeAndInstallLibs(options); - - expect(initPackage).toHaveBeenCalledTimes(1); - expect(updatePackage).toHaveBeenCalledTimes(1); - expect(installdeps).toHaveBeenCalledTimes(1); - expect(updatedeps).toHaveBeenCalledTimes(0); - }); - - it('updates dependencies if install flag is falsy.', async () => { - process.env.NPM_MOCK = 'true'; - // @ts-ignore - process.exit = (code: any) => code; - const options = { - folderPath: 'mock-folderPath', - install: false, - cwd: true, - dependencies: { - mock: 'mock-dependencies', - }, - }; - - expect.assertions(4); - await initializeAndInstallLibs(options); - - expect(initPackage).toHaveBeenCalledTimes(1); - expect(updatePackage).toHaveBeenCalledTimes(1); - expect(installdeps).toHaveBeenCalledTimes(0); - expect(updatedeps).toHaveBeenCalledTimes(1); - }); - - it('exits process if error is thrown.', async () => { - process.env.NPM_MOCK = 'error'; - const originalProcessExit = process.exit; - const mockExit = jest.fn(); - // @ts-ignore - process.exit = mockExit; - const options = { - folderPath: 'mock-folderPath', - install: false, - cwd: true, - dependencies: { - mock: 'mock-dependencies', - }, - }; - - expect.assertions(5); - await initializeAndInstallLibs(options); - - expect(initPackage).toHaveBeenCalledTimes(0); - expect(updatePackage).toHaveBeenCalledTimes(0); - expect(installdeps).toHaveBeenCalledTimes(0); - expect(updatedeps).toHaveBeenCalledTimes(0); - expect(mockExit).toHaveBeenCalledTimes(1); - - process.exit = originalProcessExit; - }); - }); - - describe('logStdoutFailMessage(): ', () => { - it('successfully logs the failed message.', () => { - const errorMessage = {stdout: '\n\nmock error message'}; - const mockFilename = 'mock-filename.yaml'; - const logSpy = jest.spyOn(global.console, 'log'); - logStdoutFailMessage(errorMessage, mockFilename); - - expect.assertions(2); - - expect(logSpy).toHaveBeenCalledWith( - `✖ if-check could not verify ${mockFilename}. The re-executed file does not match the original.\n` - ); - - expect(logSpy).toHaveBeenCalledWith('mock error message'); - }); - }); -}); diff --git a/src/common/config/index.ts b/src/common/config/index.ts new file mode 100644 index 000000000..3ee9fecbb --- /dev/null +++ b/src/common/config/index.ts @@ -0,0 +1 @@ +export {STRINGS} from './strings'; diff --git a/src/common/config/strings.ts b/src/common/config/strings.ts new file mode 100644 index 000000000..b68d5ce7c --- /dev/null +++ b/src/common/config/strings.ts @@ -0,0 +1,12 @@ +export const STRINGS = { + DISCLAIMER_MESSAGE: ` +Incubation Project + +This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. +Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day graduate, in which case this disclaimer will be removed.`, + SOURCE_IS_NOT_YAML: 'Given source file is not in yaml format.', + TARGET_IS_NOT_YAML: 'Given target file is not in yaml format.', + MANIFEST_NOT_FOUND: 'Manifest file not found.', + SUCCESS_MESSAGE: 'The environment is successfully setup!', + MANIFEST_IS_MISSING: 'Manifest is missing.', +}; diff --git a/src/common/lib/load.ts b/src/common/lib/load.ts new file mode 100644 index 000000000..02b44aef1 --- /dev/null +++ b/src/common/lib/load.ts @@ -0,0 +1,30 @@ +import {openYamlFileAsObject} from '../util/yaml'; +import {readAndParseJson} from '../../if-run/util/json'; + +import {PARAMETERS} from '../../if-run/config'; +import {STRINGS} from '../../if-run/config'; + +import {Parameters} from '../../if-run/types/parameters'; + +const {LOADING_MANIFEST} = STRINGS; + +/** + * Parses manifest file as an object. Checks if parameter file is passed via CLI, then loads it too. + * Returns context, tree and parameters (either the default one, or from CLI). + */ +export const load = async (inputPath: string, paramPath?: string) => { + console.debug(LOADING_MANIFEST); + + const rawManifest = await openYamlFileAsObject(inputPath); + const parametersFromCli = + paramPath && + (await readAndParseJson(paramPath)); /** @todo validate json */ + const parameters = + parametersFromCli || + PARAMETERS; /** @todo PARAMETERS should be specified in parameterize only */ + + return { + rawManifest, + parameters, + }; +}; diff --git a/src/types/manifest.ts b/src/common/types/manifest.ts similarity index 100% rename from src/types/manifest.ts rename to src/common/types/manifest.ts diff --git a/src/util/debug-logger.ts b/src/common/util/debug-logger.ts similarity index 98% rename from src/util/debug-logger.ts rename to src/common/util/debug-logger.ts index f1f34ae0e..f91f0e9c9 100644 --- a/src/util/debug-logger.ts +++ b/src/common/util/debug-logger.ts @@ -1,4 +1,4 @@ -import {STRINGS} from '../config/strings'; +import {STRINGS} from '../../if-run/config'; const logMessagesKeys: (keyof typeof STRINGS)[] = [ 'STARTING_IF', diff --git a/src/util/fs.ts b/src/common/util/fs.ts similarity index 100% rename from src/util/fs.ts rename to src/common/util/fs.ts diff --git a/src/common/util/helpers.ts b/src/common/util/helpers.ts new file mode 100644 index 000000000..8399e5b3e --- /dev/null +++ b/src/common/util/helpers.ts @@ -0,0 +1,21 @@ +import {exec} from 'child_process'; +import * as path from 'path'; +import {promisify} from 'util'; + +/** + * Promise version of Node's `exec` from `child-process`. + */ +export const execPromise = promisify(exec); + +/** + * Prepends process path to given `filePath`. + */ +export const prependFullFilePath = (filePath: string) => { + const processRunningPath = process.cwd(); + + if (path.isAbsolute(filePath)) { + return filePath; + } + + return path.normalize(`${processRunningPath}/${filePath}`); +}; diff --git a/src/util/logger.ts b/src/common/util/logger.ts similarity index 100% rename from src/util/logger.ts rename to src/common/util/logger.ts diff --git a/src/util/validations.ts b/src/common/util/validations.ts similarity index 95% rename from src/util/validations.ts rename to src/common/util/validations.ts index 920e7cbdb..cb92e974f 100644 --- a/src/util/validations.ts +++ b/src/common/util/validations.ts @@ -1,10 +1,10 @@ import {ZodIssue, ZodIssueCode, ZodSchema, z} from 'zod'; import {ERRORS} from '@grnsft/if-core/utils'; -import {STRINGS} from '../config/strings'; +import {STRINGS} from '../../if-run/config'; -import {AGGREGATION_METHODS} from '../types/aggregation'; -import {AGGREGATION_TYPES} from '../types/parameters'; +import {AGGREGATION_METHODS} from '../../if-run/types/aggregation'; +import {AGGREGATION_TYPES} from '../../if-run/types/parameters'; const {ManifestValidationError, InputValidationError} = ERRORS; const {VALIDATING_MANIFEST} = STRINGS; diff --git a/src/util/yaml.ts b/src/common/util/yaml.ts similarity index 100% rename from src/util/yaml.ts rename to src/common/util/yaml.ts diff --git a/src/config/config.ts b/src/config/config.ts deleted file mode 100644 index b8f676924..000000000 --- a/src/config/config.ts +++ /dev/null @@ -1,156 +0,0 @@ -import {ArgumentConfig, ParseOptions} from 'ts-command-line-args'; - -import {STRINGS} from './strings'; - -import { - IFDiffArgs, - IEArgs, - IFEnvArgs, - IFCheckArgs, -} from '../types/process-args'; - -const {DISCLAIMER_MESSAGE} = STRINGS; - -export const CONFIG = { - IE: { - ARGS: { - manifest: { - type: String, - optional: true, - alias: 'm', - description: '[path to the input file]', - }, - output: { - type: String, - optional: true, - alias: 'o', - description: '[path to the output file]', - }, - 'override-params': { - type: String, - optional: true, - alias: 'p', - description: '[path to a parameter file that overrides our defaults]', - }, - stdout: { - type: Boolean, - optional: true, - alias: 's', - description: '[prints out to the console]', - }, - help: { - type: Boolean, - optional: true, - alias: 'h', - description: '[prints out the above help instruction]', - }, - debug: { - type: Boolean, - optional: true, - alias: 'd', - description: '[prints out debug logs to the console]', - }, - } as ArgumentConfig, - HELP: { - helpArg: 'help', - headerContentSections: [ - {header: 'Impact Framework', content: 'Helpful keywords:'}, - ], - footerContentSections: [ - {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, - ], - } as ParseOptions, - }, - IF_DIFF: { - ARGS: { - source: { - type: String, - optional: true, - alias: 's', - description: '[path to the source file]', - }, - target: { - type: String, - optional: false, - alias: 't', - description: '[path to the target file', - }, - } as ArgumentConfig, - HELP: { - helpArg: 'help', - headerContentSections: [ - {header: 'Impact Framework', content: 'IF-Diff Helpful keywords:'}, - ], - footerContentSections: [ - {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, - ], - } as ParseOptions, - SUCCESS_MESSAGE: 'Files match!', - FAILURE_MESSAGE: 'Files do not match!', - }, - IF_ENV: { - ARGS: { - manifest: { - type: String, - optional: true, - alias: 'm', - description: '[path to the manifest file]', - }, - install: { - type: Boolean, - optional: true, - alias: 'i', - description: '[command to install package.json]', - }, - cwd: { - type: Boolean, - optional: true, - alias: 'c', - description: - '[command to generate the package.json in the command working directory]', - }, - } as ArgumentConfig, - HELP: { - helpArg: 'help', - headerContentSections: [ - {header: 'Impact Framework', content: 'IF-Env Helpful keywords:'}, - ], - footerContentSections: [ - {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, - ], - } as ParseOptions, - SUCCESS_MESSAGE: 'The environment is successfully setup!', - FAILURE_MESSAGE: 'Faied to create the environment!', - FAILURE_MESSAGE_TEMPLATE: - 'Faied to create the environment with the template manifest!', - FAILURE_MESSAGE_DEPENDENCIES: 'Manifest dependencies are not available!', - }, - IF_CHECK: { - ARGS: { - manifest: { - type: String, - optional: true, - alias: 'm', - description: '[path to the manifest file]', - }, - directory: { - type: String, - optional: true, - alias: 'd', - description: '[path to the manifests directory]', - }, - } as ArgumentConfig, - HELP: { - helpArg: 'help', - headerContentSections: [ - {header: 'Impact Framework', content: 'IF-Check Helpful keywords:'}, - ], - footerContentSections: [ - {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, - ], - } as ParseOptions, - }, - GITHUB_PATH: 'https://github.com', - NATIVE_PLUGIN: 'if-plugins', - AGGREGATION_ADDITIONAL_PARAMS: ['timestamp', 'duration'], -}; diff --git a/src/if-check/config/config.ts b/src/if-check/config/config.ts new file mode 100644 index 000000000..8d21da9f3 --- /dev/null +++ b/src/if-check/config/config.ts @@ -0,0 +1,33 @@ +import {ArgumentConfig, ParseOptions} from 'ts-command-line-args'; + +import {STRINGS} from '../../common/config'; + +import {IFCheckArgs} from '../types/process-args'; + +const {DISCLAIMER_MESSAGE} = STRINGS; + +export const CONFIG = { + ARGS: { + manifest: { + type: String, + optional: true, + alias: 'm', + description: '[path to the manifest file]', + }, + directory: { + type: String, + optional: true, + alias: 'd', + description: '[path to the manifests directory]', + }, + } as ArgumentConfig, + HELP: { + helpArg: 'help', + headerContentSections: [ + {header: 'Impact Framework', content: 'IF-Check Helpful keywords:'}, + ], + footerContentSections: [ + {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, + ], + } as ParseOptions, +}; diff --git a/src/if-check/config/index.ts b/src/if-check/config/index.ts new file mode 100644 index 000000000..4972b390b --- /dev/null +++ b/src/if-check/config/index.ts @@ -0,0 +1,2 @@ +export {CONFIG} from './config'; +export {STRINGS} from './strings'; diff --git a/src/if-check/config/strings.ts b/src/if-check/config/strings.ts new file mode 100644 index 000000000..1b6f1c3ef --- /dev/null +++ b/src/if-check/config/strings.ts @@ -0,0 +1,19 @@ +export const STRINGS = { + CHECKING: 'Checking...', + IF_CHECK_FLAGS_MISSING: + 'Either the `--manifest` or `--directory` command should be provided with a path', + DIRECTORY_NOT_FOUND: 'Directory not found.', + DIRECTORY_YAML_FILES_NOT_FOUND: + 'The directory does not contain any YAML/YML files.\n', + IF_CHECK_EXECUTING: (filename: string) => `Executing \`${filename}\``, + IF_CHECK_VERIFICATION_FAILURES: + '---------\nif-check verification failures:\n', + IF_CHECK_FAILED: (filename: string) => + `✖ if-check could not verify ${filename}. The re-executed file does not match the original.\n`, + IF_CHECK_VERIFIED: (filename: string) => + `✔ if-check successfully verified ${filename}\n`, + IF_CHECK_SUMMARY_ERROR_MESSAGE: (filename: string, message: string) => + `Executing \`${filename}\`\n✖ ${message}`, + IF_CHECK_SUMMARY_LOG: (passedCount: number, totalCount: number) => + `---------\nCheck summary:\n${passedCount} of ${totalCount} files are passed.\n`, +}; diff --git a/src/check.ts b/src/if-check/index.ts similarity index 95% rename from src/check.ts rename to src/if-check/index.ts index f1e8f0cdd..8e79131d2 100644 --- a/src/check.ts +++ b/src/if-check/index.ts @@ -2,10 +2,10 @@ /* eslint-disable no-process-exit */ import * as path from 'path'; -import {logger} from './util/logger'; -import {logStdoutFailMessage} from './util/helpers'; import {parseIfCheckArgs} from './util/args'; -import {getYamlFiles, removeFileIfExists} from './util/fs'; +import {logStdoutFailMessage} from './util/helpers'; +import {getYamlFiles, removeFileIfExists} from '../common/util/fs'; +import {logger} from '../common/util/logger'; import {STRINGS} from './config'; import {executeCommands} from './util/npm'; diff --git a/src/if-check/types/process-args.ts b/src/if-check/types/process-args.ts new file mode 100644 index 000000000..72dd2305c --- /dev/null +++ b/src/if-check/types/process-args.ts @@ -0,0 +1,4 @@ +export interface IFCheckArgs { + manifest?: string; + directory?: string; +} diff --git a/src/if-check/util/args.ts b/src/if-check/util/args.ts new file mode 100644 index 000000000..ab6e3fd93 --- /dev/null +++ b/src/if-check/util/args.ts @@ -0,0 +1,72 @@ +import {parse} from 'ts-command-line-args'; +import {ERRORS} from '@grnsft/if-core/utils'; + +import {isDirectoryExists, isFileExists} from '../../common/util/fs'; +import {prependFullFilePath} from '../../common/util/helpers'; +import {checkIfFileIsYaml} from '../../common/util/yaml'; + +import {CONFIG, STRINGS} from '../config'; +import {STRINGS as COMMON_STRINGS} from '../../common/config'; + +import {IFCheckArgs} from '../types/process-args'; + +const { + ParseCliParamsError, + CliSourceFileError, + InvalidDirectoryError, + MissingCliFlagsError, +} = ERRORS; + +const {ARGS, HELP} = CONFIG; + +const {IF_CHECK_FLAGS_MISSING, DIRECTORY_NOT_FOUND} = STRINGS; +const {MANIFEST_NOT_FOUND, SOURCE_IS_NOT_YAML} = COMMON_STRINGS; + +/** + * Parses `if-check` process arguments. + */ +const validateAndParseIfCheckArgs = () => { + try { + return parse(ARGS, HELP); + } catch (error) { + if (error instanceof Error) { + throw new ParseCliParamsError(error.message); + } + + throw error; + } +}; + +/** + * Checks if either `manifest` or `directory` command is provided. + */ +export const parseIfCheckArgs = async () => { + const {manifest, directory} = validateAndParseIfCheckArgs(); + + if (manifest) { + const response = prependFullFilePath(manifest); + const isManifestFileExists = await isFileExists(response); + + if (!isManifestFileExists) { + throw new ParseCliParamsError(MANIFEST_NOT_FOUND); + } + + if (checkIfFileIsYaml(manifest)) { + return {manifest}; + } + + throw new CliSourceFileError(SOURCE_IS_NOT_YAML); + } else if (directory) { + const isDirExists = await isDirectoryExists(directory); + + if (!isDirExists) { + throw new InvalidDirectoryError(DIRECTORY_NOT_FOUND); + } + + const response = prependFullFilePath(directory); + + return {directory: response}; + } + + throw new MissingCliFlagsError(IF_CHECK_FLAGS_MISSING); +}; diff --git a/src/if-check/util/helpers.ts b/src/if-check/util/helpers.ts new file mode 100644 index 000000000..d94b99477 --- /dev/null +++ b/src/if-check/util/helpers.ts @@ -0,0 +1,17 @@ +import {STRINGS} from '../config'; + +const {IF_CHECK_FAILED, IF_CHECK_SUMMARY_ERROR_MESSAGE} = STRINGS; + +/** + * Logs the failure message from the stdout of an error. + */ +export const logStdoutFailMessage = (error: any, fileName: string) => { + console.log(IF_CHECK_FAILED(fileName)); + + const stdout = error.stdout; + const logs = stdout.split('\n\n'); + const failMessage = logs[logs.length - 1]; + + console.log(failMessage); + return IF_CHECK_SUMMARY_ERROR_MESSAGE(fileName, failMessage); +}; diff --git a/src/if-check/util/npm.ts b/src/if-check/util/npm.ts new file mode 100644 index 000000000..a6137a9d3 --- /dev/null +++ b/src/if-check/util/npm.ts @@ -0,0 +1,49 @@ +import * as path from 'node:path'; + +import {execPromise} from '../../common/util/helpers'; +import {getFileName, removeFileIfExists} from '../../common/util/fs'; + +import {STRINGS} from '../config'; + +const {IF_CHECK_VERIFIED} = STRINGS; + +/** + * Executes a series of npm commands based on the provided manifest file. + */ +export const executeCommands = async (manifest: string, cwd: boolean) => { + // TODO: After release remove isGlobal and appropriate checks + const isGlobal = !!process.env.npm_config_global; + const manifestDirPath = path.dirname(manifest); + const manifestFileName = getFileName(manifest); + const executedManifest = path.join(manifestDirPath, `re-${manifestFileName}`); + const prefixFlag = + process.env.CURRENT_DIR && process.env.CURRENT_DIR !== process.cwd() + ? `--prefix=${path.relative(process.env.CURRENT_DIR!, process.cwd())}` + : ''; + const ifEnv = `${ + isGlobal ? `if-env ${prefixFlag}` : `npm run if-env ${prefixFlag} --` + } -m ${manifest}`; + const ifEnvCommand = cwd ? `${ifEnv} -c` : ifEnv; + const ifRunCommand = `${ + isGlobal ? `if-run ${prefixFlag}` : `npm run if-run ${prefixFlag} --` + } -m ${manifest} -o ${executedManifest}`; + const ifDiffCommand = `${ + isGlobal ? `if-diff ${prefixFlag}` : `npm run if-diff ${prefixFlag} --` + } -s ${executedManifest}.yaml -t ${manifest}`; + const ttyCommand = " node -p 'Boolean(process.stdout.isTTY)'"; + + await execPromise( + `${ifEnvCommand} && ${ifRunCommand} && ${ttyCommand} | ${ifDiffCommand}`, + { + cwd: process.env.CURRENT_DIR || process.cwd(), + } + ); + + if (!cwd) { + await removeFileIfExists(`${manifestDirPath}/package.json`); + } + + await removeFileIfExists(`${executedManifest}.yaml`); + + console.log(IF_CHECK_VERIFIED(path.basename(manifest))); +}; diff --git a/src/if-diff/config/config.ts b/src/if-diff/config/config.ts new file mode 100644 index 000000000..12f5ade09 --- /dev/null +++ b/src/if-diff/config/config.ts @@ -0,0 +1,33 @@ +import {ArgumentConfig, ParseOptions} from 'ts-command-line-args'; + +import {STRINGS} from '../../common/config'; + +import {IFDiffArgs} from '../types/process-args'; + +const {DISCLAIMER_MESSAGE} = STRINGS; + +export const CONFIG = { + ARGS: { + source: { + type: String, + optional: true, + alias: 's', + description: '[path to the source file]', + }, + target: { + type: String, + optional: false, + alias: 't', + description: '[path to the target file', + }, + } as ArgumentConfig, + HELP: { + helpArg: 'help', + headerContentSections: [ + {header: 'Impact Framework', content: 'IF-Diff Helpful keywords:'}, + ], + footerContentSections: [ + {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, + ], + } as ParseOptions, +}; diff --git a/src/if-diff/config/index.ts b/src/if-diff/config/index.ts new file mode 100644 index 000000000..4972b390b --- /dev/null +++ b/src/if-diff/config/index.ts @@ -0,0 +1,2 @@ +export {CONFIG} from './config'; +export {STRINGS} from './strings'; diff --git a/src/if-diff/config/strings.ts b/src/if-diff/config/strings.ts new file mode 100644 index 000000000..0222ca105 --- /dev/null +++ b/src/if-diff/config/strings.ts @@ -0,0 +1,6 @@ +export const STRINGS = { + INVALID_TARGET: 'Target is invalid.', + INVALID_SOURCE: 'Source is invalid.', + FAILURE_MESSAGE: 'Files do not match!', + SUCCESS_MESSAGE: 'Files match!', +}; diff --git a/src/diff.ts b/src/if-diff/index.ts similarity index 82% rename from src/diff.ts rename to src/if-diff/index.ts index aad02482c..52a6a9b4e 100644 --- a/src/diff.ts +++ b/src/if-diff/index.ts @@ -5,15 +5,14 @@ import {compare} from './lib/compare'; import {parseIfDiffArgs} from './util/args'; import {formatNotMatchingLog, parseManifestFromStdin} from './util/helpers'; -import {validateManifest} from './util/validations'; +import {validateManifest} from '../common/util/validations'; -import {logger} from './util/logger'; -import {debugLogger} from './util/debug-logger'; +import {logger} from '../common/util/logger'; +import {debugLogger} from '../common/util/debug-logger'; -import {CONFIG} from './config'; +import {STRINGS} from './config'; -const {IF_DIFF} = CONFIG; -const {SUCCESS_MESSAGE, FAILURE_MESSAGE} = IF_DIFF; +const {FAILURE_MESSAGE, SUCCESS_MESSAGE} = STRINGS; const IfDiff = async () => { const pipedSourceManifest = await parseManifestFromStdin(); diff --git a/src/lib/compare.ts b/src/if-diff/lib/compare.ts similarity index 96% rename from src/lib/compare.ts rename to src/if-diff/lib/compare.ts index 34d084488..2f4e9d442 100644 --- a/src/lib/compare.ts +++ b/src/if-diff/lib/compare.ts @@ -1,6 +1,6 @@ import {checkIfEqual, oneIsPrimitive} from '../util/helpers'; -import {Difference} from '../types/lib/compare'; +import {Difference} from '../types/compare'; /** * 1. If objects are not of the same type or are primitive types, compares directly. diff --git a/src/if-diff/lib/load.ts b/src/if-diff/lib/load.ts new file mode 100644 index 000000000..d11cd40e6 --- /dev/null +++ b/src/if-diff/lib/load.ts @@ -0,0 +1,38 @@ +import * as YAML from 'js-yaml'; + +import {ERRORS} from '@grnsft/if-core/utils'; + +import {openYamlFileAsObject} from '../../common/util/yaml'; + +import {STRINGS} from '../config'; + +import {LoadDiffParams} from '../types/args'; +import {Manifest} from '../../common/types/manifest'; + +const {CliSourceFileError} = ERRORS; + +const {INVALID_SOURCE} = STRINGS; + +/** + * Loads files to compare. As a source file checks if data is piped and then decides which one to take. + */ +export const loadIfDiffFiles = async (params: LoadDiffParams) => { + const {sourcePath, targetPath, pipedSourceManifest} = params; + + if (!sourcePath && !pipedSourceManifest) { + throw new CliSourceFileError(INVALID_SOURCE); + } + + const loadFromSource = + sourcePath && (await openYamlFileAsObject(sourcePath!)); + const loadFromSTDIN = + pipedSourceManifest && (await YAML.load(pipedSourceManifest!)); + + const rawSourceManifest = loadFromSource || loadFromSTDIN; + const rawTargetManifest = await openYamlFileAsObject(targetPath); + + return { + rawSourceManifest, + rawTargetManifest, + }; +}; diff --git a/src/types/util/args.ts b/src/if-diff/types/args.ts similarity index 100% rename from src/types/util/args.ts rename to src/if-diff/types/args.ts diff --git a/src/types/lib/compare.ts b/src/if-diff/types/compare.ts similarity index 100% rename from src/types/lib/compare.ts rename to src/if-diff/types/compare.ts diff --git a/src/if-diff/types/process-args.ts b/src/if-diff/types/process-args.ts new file mode 100644 index 000000000..88bb6eb90 --- /dev/null +++ b/src/if-diff/types/process-args.ts @@ -0,0 +1,4 @@ +export interface IFDiffArgs { + source?: string; + target: string; +} diff --git a/src/if-diff/util/args.ts b/src/if-diff/util/args.ts new file mode 100644 index 000000000..e87dc540c --- /dev/null +++ b/src/if-diff/util/args.ts @@ -0,0 +1,61 @@ +import {parse} from 'ts-command-line-args'; +import {ERRORS} from '@grnsft/if-core/utils'; + +import {checkIfFileIsYaml} from '../../common/util/yaml'; +import {prependFullFilePath} from '../../common/util/helpers'; + +import {CONFIG, STRINGS} from '../config'; +import {STRINGS as COMMON_STRINGS} from '../../common/config'; + +import {IFDiffArgs} from '../types/process-args'; +import {LoadDiffParams} from '../types/args'; + +const {ParseCliParamsError, CliTargetFileError, CliSourceFileError} = ERRORS; + +const {ARGS, HELP} = CONFIG; +const {INVALID_TARGET} = STRINGS; +const {SOURCE_IS_NOT_YAML, TARGET_IS_NOT_YAML} = COMMON_STRINGS; + +/** + * Parses `if-diff` process arguments. + */ +const validateAndParseIfDiffArgs = () => { + try { + return parse(ARGS, HELP); + } catch (error) { + if (error instanceof Error) { + throw new ParseCliParamsError(error.message); + } + + throw error; + } +}; + +/** + * Checks for `source` and `target` flags to be present. + */ +export const parseIfDiffArgs = () => { + const {source, target} = validateAndParseIfDiffArgs(); + + if (target) { + if (source && !checkIfFileIsYaml(source)) { + throw new CliSourceFileError(SOURCE_IS_NOT_YAML); + } + + if (checkIfFileIsYaml(target)) { + const response: LoadDiffParams = { + targetPath: prependFullFilePath(target), + }; + + if (source && checkIfFileIsYaml(source)) { + response.sourcePath = prependFullFilePath(source); + } + + return response; + } + + throw new CliTargetFileError(TARGET_IS_NOT_YAML); + } + + throw new ParseCliParamsError(INVALID_TARGET); +}; diff --git a/src/if-diff/util/helpers.ts b/src/if-diff/util/helpers.ts new file mode 100644 index 000000000..858d5333c --- /dev/null +++ b/src/if-diff/util/helpers.ts @@ -0,0 +1,128 @@ +import {createInterface} from 'node:readline/promises'; +import {Difference} from '../types/compare'; + +/** + * `If-diff` equality checker. + */ +export const checkIfEqual = (source: any, target: any) => { + if (source === target) { + return true; + } + + if (source === '*' || target === '*') { + return true; + } + + return false; +}; + +/** + * Checks if objects are primitive types. + */ +export const oneIsPrimitive = (source: any, target: any) => { + // eslint-disable-next-line eqeqeq + if (source == null || target == null) { + return true; + } + + return source !== Object(source) && target !== Object(target); +}; + +/** + * Converts given `value` to either `1` or `0`. + */ +const convertToXorable = (value: any) => { + if (typeof value === 'number') { + return value !== 0 ? 1 : 0; + } + + if (typeof value === 'boolean') { + return value ? 1 : 0; + } + + if (typeof value === 'string') { + return value.length > 0 ? 1 : 0; + } + + if (typeof value === 'object') { + return 1; + } + + return 0; +}; + +/** + * If one of the `valuesToCheck` values is undefined, then set `missing`, otherwise `exists`. + */ +const setValuesIfMissing = (response: Difference) => { + const source = convertToXorable(response.source); + const target = convertToXorable(response.target); + + if (source ^ target) { + ['source', 'target'].forEach(value => { + response[value] = response[value] ? 'exists' : 'missing'; + }); + + return response; + } + + return response; +}; + +/** + * Format not matching message for CLI logging. + */ +export const formatNotMatchingLog = (message: Difference) => { + const flattenMessage = setValuesIfMissing(message); + + Object.keys(flattenMessage).forEach(key => { + if (key === 'message' || key === 'path') { + console.log(message[key]); + } else { + console.log(`${key}: ${message[key]}`); + } + }); +}; + +/** + * Checks if there is data piped, then collects it. + * Otherwise returns empty string. + */ +const collectPipedData = async () => { + if (process.stdin.isTTY) { + return ''; + } + + const readline = createInterface({ + input: process.stdin, + }); + + const data: string[] = []; + + for await (const line of readline) { + data.push(line); + } + + return data.join('\n'); +}; + +/** + * Checks if there is piped data, tries to parse yaml from it. + * Returns empty string if haven't found anything. + */ +export const parseManifestFromStdin = async () => { + const pipedSourceManifest = await collectPipedData(); + + if (!pipedSourceManifest) { + return ''; + } + + const regex = /# start((?:.*\n)+?)# end/; + const match = regex.exec(pipedSourceManifest); + + if (!match) { + return ''; + } + + return match![1]; +}; diff --git a/src/if-env/config/config.ts b/src/if-env/config/config.ts new file mode 100644 index 000000000..cc603d6de --- /dev/null +++ b/src/if-env/config/config.ts @@ -0,0 +1,42 @@ +import {ArgumentConfig, ParseOptions} from 'ts-command-line-args'; + +import {IFEnvArgs} from '../types/process-args'; + +import {STRINGS} from '../../common/config'; + +const {DISCLAIMER_MESSAGE} = STRINGS; + +export const CONFIG = { + IF_ENV: { + ARGS: { + manifest: { + type: String, + optional: true, + alias: 'm', + description: '[path to the manifest file]', + }, + install: { + type: Boolean, + optional: true, + alias: 'i', + description: '[command to install package.json]', + }, + cwd: { + type: Boolean, + optional: true, + alias: 'c', + description: + '[command to generate the package.json in the command working directory]', + }, + } as ArgumentConfig, + HELP: { + helpArg: 'help', + headerContentSections: [ + {header: 'Impact Framework', content: 'IF-Env Helpful keywords:'}, + ], + footerContentSections: [ + {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, + ], + } as ParseOptions, + }, +}; diff --git a/src/config/env-template.yml b/src/if-env/config/env-template.yml similarity index 100% rename from src/config/env-template.yml rename to src/if-env/config/env-template.yml diff --git a/src/if-env/config/index.ts b/src/if-env/config/index.ts new file mode 100644 index 000000000..4972b390b --- /dev/null +++ b/src/if-env/config/index.ts @@ -0,0 +1,2 @@ +export {CONFIG} from './config'; +export {STRINGS} from './strings'; diff --git a/src/if-env/config/strings.ts b/src/if-env/config/strings.ts new file mode 100644 index 000000000..047ba89c0 --- /dev/null +++ b/src/if-env/config/strings.ts @@ -0,0 +1,8 @@ +export const STRINGS = { + FAILURE_MESSAGE: 'Faied to create the environment!', + FAILURE_MESSAGE_TEMPLATE: + 'Faied to create the environment with the template manifest!', + FAILURE_MESSAGE_DEPENDENCIES: 'Manifest dependencies are not available!', + INITIALIZING_PACKAGE_JSON: 'Initializing package.json.', + INSTALLING_NPM_PACKAGES: 'Installing npm packages...', +}; diff --git a/src/env.ts b/src/if-env/index.ts similarity index 89% rename from src/env.ts rename to src/if-env/index.ts index 48b0b76b9..98fe6cc6e 100644 --- a/src/env.ts +++ b/src/if-env/index.ts @@ -1,19 +1,19 @@ #!/usr/bin/env node /* eslint-disable no-process-exit */ import {parseIfEnvArgs} from './util/args'; -import {logger} from './util/logger'; -import {CONFIG} from './config'; +import {STRINGS} from '../common/config'; -import {EnvironmentOptions} from './types/if-env'; import { addTemplateManifest, getOptionsFromArgs, initializeAndInstallLibs, } from './util/helpers'; +import {logger} from '../common/util/logger'; + +import {EnvironmentOptions} from './types/if-env'; -const {IF_ENV} = CONFIG; -const {SUCCESS_MESSAGE} = IF_ENV; +const {SUCCESS_MESSAGE} = STRINGS; const IfEnv = async () => { const commandArgs = await parseIfEnvArgs(); diff --git a/src/types/if-env.ts b/src/if-env/types/if-env.ts similarity index 100% rename from src/types/if-env.ts rename to src/if-env/types/if-env.ts diff --git a/src/types/npm.ts b/src/if-env/types/npm.ts similarity index 100% rename from src/types/npm.ts rename to src/if-env/types/npm.ts diff --git a/src/if-env/types/process-args.ts b/src/if-env/types/process-args.ts new file mode 100644 index 000000000..7db2117a8 --- /dev/null +++ b/src/if-env/types/process-args.ts @@ -0,0 +1,5 @@ +export interface IFEnvArgs { + manifest?: string; + install?: boolean; + cwd?: boolean; +} diff --git a/src/if-env/util/args.ts b/src/if-env/util/args.ts new file mode 100644 index 000000000..940fa7b7a --- /dev/null +++ b/src/if-env/util/args.ts @@ -0,0 +1,55 @@ +import {parse} from 'ts-command-line-args'; + +import {ERRORS} from '@grnsft/if-core/utils'; + +import {isFileExists} from '../../common/util/fs'; +import {prependFullFilePath} from '../../common/util/helpers'; +import {checkIfFileIsYaml} from '../../common/util/yaml'; + +import {CONFIG} from '../config'; +import {STRINGS as COMMON_STRINGS} from '../../common/config'; + +import {IFEnvArgs} from '../types/process-args'; + +const {ParseCliParamsError, CliSourceFileError} = ERRORS; +const {IF_ENV} = CONFIG; +const {MANIFEST_NOT_FOUND, SOURCE_IS_NOT_YAML} = COMMON_STRINGS; + +/** + * Parses `if-env` process arguments. + */ +const validateAndParseIfEnvArgs = () => { + try { + return parse(IF_ENV.ARGS, IF_ENV.HELP); + } catch (error) { + if (error instanceof Error) { + throw new ParseCliParamsError(error.message); + } + + throw error; + } +}; + +/** + * Checks if the `manifest` command is provided and it is valid manifest file. + */ +export const parseIfEnvArgs = async () => { + const {manifest, install, cwd} = validateAndParseIfEnvArgs(); + + if (manifest) { + const response = prependFullFilePath(manifest); + const isManifestFileExists = await isFileExists(response); + + if (!isManifestFileExists) { + throw new ParseCliParamsError(MANIFEST_NOT_FOUND); + } + + if (checkIfFileIsYaml(manifest)) { + return {manifest: response, install, cwd}; + } + + throw new CliSourceFileError(SOURCE_IS_NOT_YAML); + } + + return {install, cwd}; +}; diff --git a/src/if-env/util/helpers.ts b/src/if-env/util/helpers.ts new file mode 100644 index 000000000..17827f58a --- /dev/null +++ b/src/if-env/util/helpers.ts @@ -0,0 +1,92 @@ +/* eslint-disable no-process-exit */ +import * as path from 'path'; +import * as fs from 'fs/promises'; +import {ERRORS} from '@grnsft/if-core/utils'; + +import { + extractPathsWithVersion, + initPackageJsonIfNotExists, + installDependencies, + updatePackageJsonDependencies, + updatePackageJsonProperties, +} from './npm'; +import {load} from '../../common/lib/load'; + +import {STRINGS} from '../config'; + +import {EnvironmentOptions} from '../types/if-env'; + +const {MissingPluginDependenciesError} = ERRORS; +const { + FAILURE_MESSAGE_DEPENDENCIES, + FAILURE_MESSAGE, + FAILURE_MESSAGE_TEMPLATE, +} = STRINGS; + +/** + * Gets the folder path of the manifest file, dependencies from manifest file and install argument from the given arguments. + */ +export const getOptionsFromArgs = async (commandArgs: { + manifest: string; + install: boolean | undefined; +}) => { + const {manifest, install} = commandArgs; + const folderPath = path.dirname(manifest); + const loadedManifest = await load(manifest); + const rawManifest = loadedManifest.rawManifest; + const plugins = rawManifest?.initialize?.plugins || {}; + const dependencies = rawManifest?.execution?.environment.dependencies || []; + + if (!dependencies.length) { + throw new MissingPluginDependenciesError(FAILURE_MESSAGE_DEPENDENCIES); + } + + const pathsWithVersion = extractPathsWithVersion(plugins, dependencies); + + return { + folderPath, + dependencies: pathsWithVersion, + install, + }; +}; + +/** + * Creates folder if not exists, installs dependencies if required, update depenedencies. + */ +export const initializeAndInstallLibs = async (options: EnvironmentOptions) => { + try { + const {folderPath, install, cwd, dependencies} = options; + const packageJsonPath = await initPackageJsonIfNotExists(folderPath); + + await updatePackageJsonProperties(packageJsonPath, cwd); + + if (install) { + await installDependencies(folderPath, dependencies); + } else { + await updatePackageJsonDependencies(packageJsonPath, dependencies, cwd); + } + } catch (error) { + console.log(FAILURE_MESSAGE); + process.exit(2); + } +}; + +/** + * Adds a manifest template to the folder where the if-env CLI command runs. + */ +export const addTemplateManifest = async (destinationDir: string) => { + try { + const templateManifest = path.resolve( + __dirname, + '../config/env-template.yml' + ); + + const destinationPath = path.resolve(destinationDir, 'manifest.yml'); + const data = await fs.readFile(templateManifest, 'utf-8'); + + await fs.writeFile(destinationPath, data, 'utf-8'); + } catch (error) { + console.log(FAILURE_MESSAGE_TEMPLATE); + process.exit(1); + } +}; diff --git a/src/util/npm.ts b/src/if-env/util/npm.ts similarity index 66% rename from src/util/npm.ts rename to src/if-env/util/npm.ts index de27ae4de..f029bcdf2 100644 --- a/src/util/npm.ts +++ b/src/if-env/util/npm.ts @@ -3,22 +3,16 @@ import * as fs from 'fs/promises'; import * as path from 'path'; -import {execPromise} from './helpers'; -import { - isDirectoryExists, - getFileName, - isFileExists, - removeFileIfExists, -} from './fs'; -import {logger} from './logger'; +import {execPromise} from '../../common/util/helpers'; +import {isDirectoryExists, isFileExists} from '../../common/util/fs'; +import {logger} from '../../common/util/logger'; import {STRINGS} from '../config'; import {ManifestPlugin, PathWithVersion} from '../types/npm'; -const packageJson = require('../../package.json'); +const packageJson = require('../../../package.json'); -const {INITIALIZING_PACKAGE_JSON, INSTALLING_NPM_PACKAGES, IF_CHECK_VERIFIED} = - STRINGS; +const {INITIALIZING_PACKAGE_JSON, INSTALLING_NPM_PACKAGES} = STRINGS; /** * Checks if the package.json is exists, if not, initializes it. @@ -150,44 +144,3 @@ export const updatePackageJsonProperties = async ( JSON.stringify(newPackageJson, null, 2) ); }; - -/** - * Executes a series of npm commands based on the provided manifest file. - */ -export const executeCommands = async (manifest: string, cwd: boolean) => { - // TODO: After release remove isGlobal and appropriate checks - const isGlobal = !!process.env.npm_config_global; - const manifestDirPath = path.dirname(manifest); - const manifestFileName = getFileName(manifest); - const executedManifest = path.join(manifestDirPath, `re-${manifestFileName}`); - const prefixFlag = - process.env.CURRENT_DIR && process.env.CURRENT_DIR !== process.cwd() - ? `--prefix=${path.relative(process.env.CURRENT_DIR!, process.cwd())}` - : ''; - const ifEnv = `${ - isGlobal ? `if-env ${prefixFlag}` : `npm run if-env ${prefixFlag} --` - } -m ${manifest}`; - const ifEnvCommand = cwd ? `${ifEnv} -c` : ifEnv; - const ifRunCommand = `${ - isGlobal ? `if-run ${prefixFlag}` : `npm run if-run ${prefixFlag} --` - } -m ${manifest} -o ${executedManifest}`; - const ifDiffCommand = `${ - isGlobal ? `if-diff ${prefixFlag}` : `npm run if-diff ${prefixFlag} --` - } -s ${executedManifest}.yaml -t ${manifest}`; - const ttyCommand = " node -p 'Boolean(process.stdout.isTTY)'"; - - await execPromise( - `${ifEnvCommand} && ${ifRunCommand} && ${ttyCommand} | ${ifDiffCommand}`, - { - cwd: process.env.CURRENT_DIR || process.cwd(), - } - ); - - if (!cwd) { - await removeFileIfExists(`${manifestDirPath}/package.json`); - } - - await removeFileIfExists(`${executedManifest}.yaml`); - - console.log(IF_CHECK_VERIFIED(path.basename(manifest))); -}; diff --git a/src/builtins/README.md b/src/if-run/builtins/README.md similarity index 100% rename from src/builtins/README.md rename to src/if-run/builtins/README.md diff --git a/src/builtins/coefficient/README.md b/src/if-run/builtins/coefficient/README.md similarity index 100% rename from src/builtins/coefficient/README.md rename to src/if-run/builtins/coefficient/README.md diff --git a/src/builtins/coefficient/index.ts b/src/if-run/builtins/coefficient/index.ts similarity index 97% rename from src/builtins/coefficient/index.ts rename to src/if-run/builtins/coefficient/index.ts index 2d4f6a1c9..4b80be633 100644 --- a/src/builtins/coefficient/index.ts +++ b/src/if-run/builtins/coefficient/index.ts @@ -6,7 +6,7 @@ import { PluginParams, } from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/copy-param/README.md b/src/if-run/builtins/copy-param/README.md similarity index 100% rename from src/builtins/copy-param/README.md rename to src/if-run/builtins/copy-param/README.md diff --git a/src/builtins/copy-param/index.ts b/src/if-run/builtins/copy-param/index.ts similarity index 97% rename from src/builtins/copy-param/index.ts rename to src/if-run/builtins/copy-param/index.ts index 0602f6f75..cf21cfb8e 100644 --- a/src/builtins/copy-param/index.ts +++ b/src/if-run/builtins/copy-param/index.ts @@ -2,7 +2,7 @@ import {z} from 'zod'; import {ERRORS} from '@grnsft/if-core/utils'; import {ExecutePlugin, PluginParams} from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/csv-lookup/README.md b/src/if-run/builtins/csv-lookup/README.md similarity index 100% rename from src/builtins/csv-lookup/README.md rename to src/if-run/builtins/csv-lookup/README.md diff --git a/src/builtins/csv-lookup/index.ts b/src/if-run/builtins/csv-lookup/index.ts similarity index 99% rename from src/builtins/csv-lookup/index.ts rename to src/if-run/builtins/csv-lookup/index.ts index 8243a6391..91e1739b3 100644 --- a/src/builtins/csv-lookup/index.ts +++ b/src/if-run/builtins/csv-lookup/index.ts @@ -7,7 +7,7 @@ import {parse} from 'csv-parse/sync'; import {ERRORS} from '@grnsft/if-core/utils'; import {ExecutePlugin, PluginParams} from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/divide/README.md b/src/if-run/builtins/divide/README.md similarity index 100% rename from src/builtins/divide/README.md rename to src/if-run/builtins/divide/README.md diff --git a/src/builtins/divide/index.ts b/src/if-run/builtins/divide/index.ts similarity index 97% rename from src/builtins/divide/index.ts rename to src/if-run/builtins/divide/index.ts index 453a50921..328c4ccff 100644 --- a/src/builtins/divide/index.ts +++ b/src/if-run/builtins/divide/index.ts @@ -2,7 +2,7 @@ import {z} from 'zod'; import {ERRORS} from '@grnsft/if-core/utils'; import {ExecutePlugin, PluginParams, ConfigParams} from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/exponent/README.md b/src/if-run/builtins/exponent/README.md similarity index 100% rename from src/builtins/exponent/README.md rename to src/if-run/builtins/exponent/README.md diff --git a/src/builtins/exponent/index.ts b/src/if-run/builtins/exponent/index.ts similarity index 96% rename from src/builtins/exponent/index.ts rename to src/if-run/builtins/exponent/index.ts index ce4e959ef..800b2295e 100644 --- a/src/builtins/exponent/index.ts +++ b/src/if-run/builtins/exponent/index.ts @@ -5,7 +5,7 @@ import { ExponentConfig, } from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; export const Exponent = (globalConfig: ExponentConfig): ExecutePlugin => { const metadata = { diff --git a/src/builtins/export-csv-raw.ts b/src/if-run/builtins/export-csv-raw.ts similarity index 98% rename from src/builtins/export-csv-raw.ts rename to src/if-run/builtins/export-csv-raw.ts index 7afc21cb2..1dc637587 100644 --- a/src/builtins/export-csv-raw.ts +++ b/src/if-run/builtins/export-csv-raw.ts @@ -3,7 +3,7 @@ import {ERRORS} from '@grnsft/if-core/utils'; import {STRINGS} from '../config'; -import {Context} from '../types/manifest'; +import {Context} from '../../common/types/manifest'; const {ExhaustOutputArgError, WriteFileError} = ERRORS; const {OUTPUT_REQUIRED, WRITE_CSV_ERROR, EXPORTING_RAW_CSV_FILE} = STRINGS; diff --git a/src/builtins/export-csv.ts b/src/if-run/builtins/export-csv.ts similarity index 98% rename from src/builtins/export-csv.ts rename to src/if-run/builtins/export-csv.ts index 53a105733..112061e31 100644 --- a/src/builtins/export-csv.ts +++ b/src/if-run/builtins/export-csv.ts @@ -6,7 +6,7 @@ import {PluginParams} from '@grnsft/if-core/types'; import {STRINGS} from '../config'; -import {Context} from '../types/manifest'; +import {Context} from '../../common/types/manifest'; const {ExhaustOutputArgError} = ERRORS; const {CSV_EXPORT, OUTPUT_REQUIRED, EXPORTING_TO_CSV_FILE} = STRINGS; diff --git a/src/builtins/export-log.ts b/src/if-run/builtins/export-log.ts similarity index 86% rename from src/builtins/export-log.ts rename to src/if-run/builtins/export-log.ts index fee267dc2..0f1dad33a 100644 --- a/src/builtins/export-log.ts +++ b/src/if-run/builtins/export-log.ts @@ -1,6 +1,6 @@ import * as YAML from 'js-yaml'; -import {Context} from '../types/manifest'; +import {Context} from '../../common/types/manifest'; export const ExportLog = () => { /** diff --git a/src/builtins/export-yaml.ts b/src/if-run/builtins/export-yaml.ts similarity index 87% rename from src/builtins/export-yaml.ts rename to src/if-run/builtins/export-yaml.ts index 624e1298f..456ca6aa6 100644 --- a/src/builtins/export-yaml.ts +++ b/src/if-run/builtins/export-yaml.ts @@ -1,10 +1,10 @@ import {ERRORS} from '@grnsft/if-core/utils'; -import {saveYamlFileAs} from '../util/yaml'; +import {saveYamlFileAs} from '../../common/util/yaml'; import {STRINGS} from '../config'; -import {Context} from '../types/manifest'; +import {Context} from '../../common/types/manifest'; const {ExhaustOutputArgError} = ERRORS; const {OUTPUT_REQUIRED, EXPORTING_TO_YAML_FILE} = STRINGS; diff --git a/src/builtins/group-by.ts b/src/if-run/builtins/group-by.ts similarity index 97% rename from src/builtins/group-by.ts rename to src/if-run/builtins/group-by.ts index 63c4cd5c5..c8694d4cc 100644 --- a/src/builtins/group-by.ts +++ b/src/if-run/builtins/group-by.ts @@ -8,7 +8,7 @@ import { import {STRINGS} from '../config'; -import {validate} from '../util/validations'; +import {validate} from '../../common/util/validations'; const {InvalidGroupingError, GlobalConfigError} = ERRORS; diff --git a/src/builtins/index.ts b/src/if-run/builtins/index.ts similarity index 100% rename from src/builtins/index.ts rename to src/if-run/builtins/index.ts diff --git a/src/builtins/interpolation/README.md b/src/if-run/builtins/interpolation/README.md similarity index 100% rename from src/builtins/interpolation/README.md rename to src/if-run/builtins/interpolation/README.md diff --git a/src/builtins/interpolation/index.ts b/src/if-run/builtins/interpolation/index.ts similarity index 98% rename from src/builtins/interpolation/index.ts rename to src/if-run/builtins/interpolation/index.ts index 98a84ad8a..03239a004 100644 --- a/src/builtins/interpolation/index.ts +++ b/src/if-run/builtins/interpolation/index.ts @@ -8,7 +8,7 @@ import { Method, } from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/mock-observations/README.md b/src/if-run/builtins/mock-observations/README.md similarity index 100% rename from src/builtins/mock-observations/README.md rename to src/if-run/builtins/mock-observations/README.md diff --git a/src/builtins/mock-observations/helpers/common-generator.ts b/src/if-run/builtins/mock-observations/helpers/common-generator.ts similarity index 100% rename from src/builtins/mock-observations/helpers/common-generator.ts rename to src/if-run/builtins/mock-observations/helpers/common-generator.ts diff --git a/src/builtins/mock-observations/helpers/rand-int-generator.ts b/src/if-run/builtins/mock-observations/helpers/rand-int-generator.ts similarity index 100% rename from src/builtins/mock-observations/helpers/rand-int-generator.ts rename to src/if-run/builtins/mock-observations/helpers/rand-int-generator.ts diff --git a/src/builtins/mock-observations/index.ts b/src/if-run/builtins/mock-observations/index.ts similarity index 98% rename from src/builtins/mock-observations/index.ts rename to src/if-run/builtins/mock-observations/index.ts index c7488de0f..d947e7d7c 100644 --- a/src/builtins/mock-observations/index.ts +++ b/src/if-run/builtins/mock-observations/index.ts @@ -7,7 +7,7 @@ import { ObservationParams, } from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {CommonGenerator} from './helpers/common-generator'; import {RandIntGenerator} from './helpers/rand-int-generator'; diff --git a/src/builtins/mock-observations/interfaces/index.ts b/src/if-run/builtins/mock-observations/interfaces/index.ts similarity index 100% rename from src/builtins/mock-observations/interfaces/index.ts rename to src/if-run/builtins/mock-observations/interfaces/index.ts diff --git a/src/builtins/multiply/README.md b/src/if-run/builtins/multiply/README.md similarity index 100% rename from src/builtins/multiply/README.md rename to src/if-run/builtins/multiply/README.md diff --git a/src/builtins/multiply/index.ts b/src/if-run/builtins/multiply/index.ts similarity index 96% rename from src/builtins/multiply/index.ts rename to src/if-run/builtins/multiply/index.ts index cd2a74837..706e190ec 100644 --- a/src/builtins/multiply/index.ts +++ b/src/if-run/builtins/multiply/index.ts @@ -5,7 +5,7 @@ import { MultiplyConfig, } from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; export const Multiply = (globalConfig: MultiplyConfig): ExecutePlugin => { const metadata = { diff --git a/src/builtins/regex/README.md b/src/if-run/builtins/regex/README.md similarity index 100% rename from src/builtins/regex/README.md rename to src/if-run/builtins/regex/README.md diff --git a/src/builtins/regex/index.ts b/src/if-run/builtins/regex/index.ts similarity index 97% rename from src/builtins/regex/index.ts rename to src/if-run/builtins/regex/index.ts index 8d382ec74..45c65d6f6 100644 --- a/src/builtins/regex/index.ts +++ b/src/if-run/builtins/regex/index.ts @@ -2,7 +2,7 @@ import {z} from 'zod'; import {ERRORS} from '@grnsft/if-core/utils'; import {ExecutePlugin, PluginParams, ConfigParams} from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/sci-embodied/README.md b/src/if-run/builtins/sci-embodied/README.md similarity index 100% rename from src/builtins/sci-embodied/README.md rename to src/if-run/builtins/sci-embodied/README.md diff --git a/src/builtins/sci-embodied/index.ts b/src/if-run/builtins/sci-embodied/index.ts similarity index 97% rename from src/builtins/sci-embodied/index.ts rename to src/if-run/builtins/sci-embodied/index.ts index a88017e9f..2fffc09ed 100644 --- a/src/builtins/sci-embodied/index.ts +++ b/src/if-run/builtins/sci-embodied/index.ts @@ -1,7 +1,7 @@ import {z} from 'zod'; import {ExecutePlugin, PluginParams} from '@grnsft/if-core/types'; -import {validate, allDefined} from '../../util/validations'; +import {validate, allDefined} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/sci/README.md b/src/if-run/builtins/sci/README.md similarity index 100% rename from src/builtins/sci/README.md rename to src/if-run/builtins/sci/README.md diff --git a/src/builtins/sci/config.ts b/src/if-run/builtins/sci/config.ts similarity index 100% rename from src/builtins/sci/config.ts rename to src/if-run/builtins/sci/config.ts diff --git a/src/builtins/sci/index.ts b/src/if-run/builtins/sci/index.ts similarity index 96% rename from src/builtins/sci/index.ts rename to src/if-run/builtins/sci/index.ts index 47d495351..f11f6c2b8 100644 --- a/src/builtins/sci/index.ts +++ b/src/if-run/builtins/sci/index.ts @@ -2,7 +2,7 @@ import {z} from 'zod'; import {ERRORS} from '@grnsft/if-core/utils'; import {ExecutePlugin, PluginParams, ConfigParams} from '@grnsft/if-core/types'; -import {validate, allDefined} from '../../util/validations'; +import {validate, allDefined} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/shell/README.md b/src/if-run/builtins/shell/README.md similarity index 100% rename from src/builtins/shell/README.md rename to src/if-run/builtins/shell/README.md diff --git a/src/builtins/shell/index.ts b/src/if-run/builtins/shell/index.ts similarity index 96% rename from src/builtins/shell/index.ts rename to src/if-run/builtins/shell/index.ts index 352ee6bcd..5fbea5f28 100644 --- a/src/builtins/shell/index.ts +++ b/src/if-run/builtins/shell/index.ts @@ -5,7 +5,7 @@ import {z} from 'zod'; import {ERRORS} from '@grnsft/if-core/utils'; import {ExecutePlugin, PluginParams, ConfigParams} from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; const {ProcessExecutionError} = ERRORS; diff --git a/src/builtins/subtract/README.md b/src/if-run/builtins/subtract/README.md similarity index 100% rename from src/builtins/subtract/README.md rename to src/if-run/builtins/subtract/README.md diff --git a/src/builtins/subtract/index.ts b/src/if-run/builtins/subtract/index.ts similarity index 97% rename from src/builtins/subtract/index.ts rename to src/if-run/builtins/subtract/index.ts index 797b259c1..bd5af0603 100644 --- a/src/builtins/subtract/index.ts +++ b/src/if-run/builtins/subtract/index.ts @@ -5,7 +5,7 @@ import { SubtractConfig, } from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; export const Subtract = (globalConfig: SubtractConfig): ExecutePlugin => { const metadata = { diff --git a/src/builtins/sum/README.md b/src/if-run/builtins/sum/README.md similarity index 100% rename from src/builtins/sum/README.md rename to src/if-run/builtins/sum/README.md diff --git a/src/builtins/sum/index.ts b/src/if-run/builtins/sum/index.ts similarity index 97% rename from src/builtins/sum/index.ts rename to src/if-run/builtins/sum/index.ts index 57437ba31..62ba479e6 100644 --- a/src/builtins/sum/index.ts +++ b/src/if-run/builtins/sum/index.ts @@ -2,7 +2,7 @@ import {z} from 'zod'; import {ERRORS} from '@grnsft/if-core/utils'; import {ExecutePlugin, PluginParams, SumConfig} from '@grnsft/if-core/types'; -import {validate} from '../../util/validations'; +import {validate} from '../../../common/util/validations'; import {STRINGS} from '../../config'; diff --git a/src/builtins/time-sync.ts b/src/if-run/builtins/time-sync.ts similarity index 99% rename from src/builtins/time-sync.ts rename to src/if-run/builtins/time-sync.ts index 8b37241b8..78b293e75 100644 --- a/src/builtins/time-sync.ts +++ b/src/if-run/builtins/time-sync.ts @@ -13,7 +13,7 @@ import { import {parameterize} from '../lib/parameterize'; -import {validate} from '../util/validations'; +import {validate} from '../../common/util/validations'; import {STRINGS} from '../config'; diff --git a/src/if-run/config/config.ts b/src/if-run/config/config.ts new file mode 100644 index 000000000..d3b0b7dfe --- /dev/null +++ b/src/if-run/config/config.ts @@ -0,0 +1,62 @@ +import {ArgumentConfig, ParseOptions} from 'ts-command-line-args'; + +import {STRINGS} from '../../common/config'; + +import {IfRunArgs} from '../types/process-args'; + +const {DISCLAIMER_MESSAGE} = STRINGS; + +export const CONFIG = { + IF_RUN: { + ARGS: { + manifest: { + type: String, + optional: true, + alias: 'm', + description: '[path to the input file]', + }, + output: { + type: String, + optional: true, + alias: 'o', + description: '[path to the output file]', + }, + 'override-params': { + type: String, + optional: true, + alias: 'p', + description: '[path to a parameter file that overrides our defaults]', + }, + stdout: { + type: Boolean, + optional: true, + alias: 's', + description: '[prints out to the console]', + }, + help: { + type: Boolean, + optional: true, + alias: 'h', + description: '[prints out the above help instruction]', + }, + debug: { + type: Boolean, + optional: true, + alias: 'd', + description: '[prints out debug logs to the console]', + }, + } as ArgumentConfig, + HELP: { + helpArg: 'help', + headerContentSections: [ + {header: 'Impact Framework', content: 'Helpful keywords:'}, + ], + footerContentSections: [ + {header: 'Green Software Foundation', content: DISCLAIMER_MESSAGE}, + ], + } as ParseOptions, + }, + GITHUB_PATH: 'https://github.com', + NATIVE_PLUGIN: 'if-plugins', + AGGREGATION_ADDITIONAL_PARAMS: ['timestamp', 'duration'], +}; diff --git a/src/config/index.ts b/src/if-run/config/index.ts similarity index 100% rename from src/config/index.ts rename to src/if-run/config/index.ts index 03ec3e587..6aa6e0a98 100644 --- a/src/config/index.ts +++ b/src/if-run/config/index.ts @@ -1,3 +1,3 @@ export {CONFIG} from './config'; -export {STRINGS} from './strings'; export {PARAMETERS} from './params'; +export {STRINGS} from './strings'; diff --git a/src/config/params.ts b/src/if-run/config/params.ts similarity index 100% rename from src/config/params.ts rename to src/if-run/config/params.ts diff --git a/src/config/strings.ts b/src/if-run/config/strings.ts similarity index 74% rename from src/config/strings.ts rename to src/if-run/config/strings.ts index b6f3410b4..186b6fb80 100644 --- a/src/config/strings.ts +++ b/src/if-run/config/strings.ts @@ -1,21 +1,15 @@ -import {ManifestParameter} from '../types/manifest'; +import {ManifestParameter} from '../../common/types/manifest'; export const STRINGS = { - MANIFEST_IS_MISSING: 'Manifest is missing.', MISSING_METHOD: "Initalization param 'method' is missing.", MISSING_PATH: "Initalization param 'path' is missing.", UNSUPPORTED_PLUGIN: "Plugin interface doesn't implement 'execute' or 'metadata' methods.", OVERRIDE_WARNING: '\n**WARNING**: You are overriding the IF default parameters file. Please be extremely careful of unintended side-effects in your plugin pipeline!\n', - DISCLAIMER_MESSAGE: ` -Incubation Project - -This project is an incubation project being run inside the Green Software Foundation; as such, we DON’T recommend using it in any critical use case. -Incubation projects are experimental, offer no support guarantee, have minimal governance and process, and may be retired at any moment. This project may one day graduate, in which case this disclaimer will be removed.`, NOT_NATIVE_PLUGIN: (path: string) => ` -You are using plugin ${path} which is not part of the Impact Framework standard library. You should do your own research to ensure the plugins are up to date and accurate. They may not be actively maintained.`, + You are using plugin ${path} which is not part of the Impact Framework standard library. You should do your own research to ensure the plugins are up to date and accurate. They may not be actively maintained.`, INVALID_MODULE_PATH: (path: string, error?: any) => `Provided module \`${path}\` is invalid or not found. ${error ?? ''} `, @@ -47,68 +41,8 @@ You have not selected an output method. To see your output data, you can choose --stdout: this will print your output data to the console --output : this will save your output data to the given filepath (do not provide file extension) Note that for the '--output' option you also need to define the output type in your manifest file. See https://if.greensoftware.foundation/major-concepts/manifest-file#initialize`, - SOURCE_IS_NOT_YAML: 'Given source file is not in yaml format.', - TARGET_IS_NOT_YAML: 'Given target file is not in yaml format.', - INVALID_TARGET: 'Target is invalid.', - INVALID_SOURCE: 'Source is invalid.', UNSUPPORTED_ERROR: (errorName: string) => `UnsupportedErrorClass: plugin threw error class: ${errorName} that is not recognized by Impact Framework`, - /** Plugin messages */ - MISSING_GLOBAL_CONFIG: 'Global config is not provided.', - MISSING_INPUT_DATA: (param: string) => - `${param} is missing from the input array, or has nullish value.`, - MANIFEST_NOT_FOUND: 'Manifest file not found.', - INITIALIZING_PACKAGE_JSON: 'Initializing package.json.', - INSTALLING_NPM_PACKAGES: 'Installing npm packages...', - NOT_NUMERIC_VALUE: (str: any) => `${str} is not numberic.`, - MISSING_FUNCTIONAL_UNIT_CONFIG: - '`functional-unit` should be provided in your global config', - MISSING_FUNCTIONAL_UNIT_INPUT: - '`functional-unit` value is missing from input data or it is not a positive integer', - REGEX_MISMATCH: (input: any, match: string) => - `\`${input}\` does not match the ${match} regex expression`, - SCI_EMBODIED_ERROR: (unit: string) => - `invalid number. please provide it as \`${unit}\` to input`, - MISSING_MIN_MAX: 'Config is missing min or max value', - INVALID_MIN_MAX: (name: string) => - `Min value should not be greater than or equal to max value of ${name}`, - FILE_FETCH_FAILED: ( - filepath: string, - message: string - ) => `Failed fetching the file: ${filepath}. -${message}`, - FILE_READ_FAILED: ( - filepath: string, - error: string - ) => `Failed reading the file: ${filepath}. -${error}`, - MISSING_CSV_COLUMN: (columnName: string) => - `There is no column with the name: ${columnName}.`, - NO_QUERY_DATA: - 'One or more of the given query parameters are not found in the target CSV file column headers.', - INVALID_DATE_TYPE: (date: any) => - `Unexpected date datatype: ${typeof date}: ${date}`, - INVALID_OBSERVATION_OVERLAP: - 'Observation timestamps overlap, please check inputs.', - SCI_MISSING_FN_UNIT: (functionalUnit: string) => - `'carbon' and ${functionalUnit} should be present in your input data.`, - /** Exhaust messages */ - OUTPUT_REQUIRED: - 'Output path is required, please make sure output is configured properly.', - CSV_EXPORT: - 'CSV export criteria is not found in output path. Please append it after --output #.', - WRITE_CSV_ERROR: (outputPath: string, error: any) => - `Failed to write CSV file to ${outputPath}: ${error}`, - INVALID_NAME: - '`name` global config parameter is empty or contains all spaces', - START_LOWER_END: '`start-time` should be lower than `end-time`', - TIMESTAMP_REQUIRED: (index: number) => `required in input[${index}]`, - INVALID_DATETIME: (index: number) => `invalid datetime in input[${index}]`, - X_Y_EQUAL: 'The length of `x` and `y` should be equal', - ARRAY_LENGTH_NON_EMPTY: - 'the length of the input arrays must be greater than 1', - WITHIN_THE_RANGE: - 'The target x value must be within the range of the given x values', /** Debugging logs */ STARTING_IF: 'Starting IF', EXITING_IF: 'Exiting IF', @@ -134,23 +68,58 @@ ${error}`, `Exporting to csv file: ${savepath}`, EXPORTING_RAW_CSV_FILE: (savepath: string) => `Exporting raw csv file: ${savepath}`, - CHECKING: 'Checking...', - IF_CHECK_FLAGS_MISSING: - 'Either the `--manifest` or `--directory` command should be provided with a path', - DIRECTORY_NOT_FOUND: 'Directory not found.', - DIRECTORY_YAML_FILES_NOT_FOUND: - 'The directory does not contain any YAML/YML files.\n', - IF_CHECK_EXECUTING: (filename: string) => `Executing \`${filename}\``, - IF_CHECK_VERIFICATION_FAILURES: - '---------\nif-check verification failures:\n', - IF_CHECK_FAILED: (filename: string) => - `✖ if-check could not verify ${filename}. The re-executed file does not match the original.\n`, - IF_CHECK_VERIFIED: (filename: string) => - `✔ if-check successfully verified ${filename}\n`, - IF_CHECK_SUMMARY_ERROR_MESSAGE: (filename: string, message: string) => - `Executing \`${filename}\`\n✖ ${message}`, - IF_CHECK_SUMMARY_LOG: (passedCount: number, totalCount: number) => - `---------\nCheck summary:\n${passedCount} of ${totalCount} files are passed.\n`, + /** Exhaust messages */ + OUTPUT_REQUIRED: + 'Output path is required, please make sure output is configured properly.', + CSV_EXPORT: + 'CSV export criteria is not found in output path. Please append it after --output #.', + WRITE_CSV_ERROR: (outputPath: string, error: any) => + `Failed to write CSV file to ${outputPath}: ${error}`, + /** Plugins messages */ + INVALID_NAME: + '`name` global config parameter is empty or contains all spaces', + START_LOWER_END: '`start-time` should be lower than `end-time`', + TIMESTAMP_REQUIRED: (index: number) => `required in input[${index}]`, + INVALID_DATETIME: (index: number) => `invalid datetime in input[${index}]`, + X_Y_EQUAL: 'The length of `x` and `y` should be equal', + ARRAY_LENGTH_NON_EMPTY: + 'the length of the input arrays must be greater than 1', + WITHIN_THE_RANGE: + 'The target x value must be within the range of the given x values', + MISSING_CSV_COLUMN: (columnName: string) => + `There is no column with the name: ${columnName}.`, + NO_QUERY_DATA: + 'One or more of the given query parameters are not found in the target CSV file column headers.', + INVALID_DATE_TYPE: (date: any) => + `Unexpected date datatype: ${typeof date}: ${date}`, + INVALID_OBSERVATION_OVERLAP: + 'Observation timestamps overlap, please check inputs.', + SCI_MISSING_FN_UNIT: (functionalUnit: string) => + `'carbon' and ${functionalUnit} should be present in your input data.`, + MISSING_FUNCTIONAL_UNIT_CONFIG: + '`functional-unit` should be provided in your global config', + MISSING_FUNCTIONAL_UNIT_INPUT: + '`functional-unit` value is missing from input data or it is not a positive integer', + REGEX_MISMATCH: (input: any, match: string) => + `\`${input}\` does not match the ${match} regex expression`, + SCI_EMBODIED_ERROR: (unit: string) => + `invalid number. please provide it as \`${unit}\` to input`, + MISSING_MIN_MAX: 'Config is missing min or max value', + INVALID_MIN_MAX: (name: string) => + `Min value should not be greater than or equal to max value of ${name}`, + FILE_FETCH_FAILED: ( + filepath: string, + message: string + ) => `Failed fetching the file: ${filepath}. +${message}`, + FILE_READ_FAILED: ( + filepath: string, + error: string + ) => `Failed reading the file: ${filepath}. +${error}`, ZERO_DIVISION: (moduleName: string, index: number) => `-- SKIPPING -- DivisionByZero: you are attempting to divide by zero in ${moduleName} plugin : inputs[${index}]\n`, + MISSING_GLOBAL_CONFIG: 'Global config is not provided.', + MISSING_INPUT_DATA: (param: string) => + `${param} is missing from the input array, or has nullish value.`, }; diff --git a/src/index.ts b/src/if-run/index.ts similarity index 77% rename from src/index.ts rename to src/if-run/index.ts index e0d87009c..fa5d17fa2 100644 --- a/src/index.ts +++ b/src/if-run/index.ts @@ -4,21 +4,23 @@ import {compute} from './lib/compute'; import {injectEnvironment} from './lib/environment'; import {exhaust} from './lib/exhaust'; import {initialize} from './lib/initialize'; -import {load} from './lib/load'; import {parameterize} from './lib/parameterize'; +import {load} from '../common/lib/load'; -import {debugLogger} from './util/debug-logger'; -import {parseIEProcessArgs} from './util/args'; +import {parseIfRunProcessArgs} from './util/args'; import {andHandle} from './util/helpers'; -import {logger} from './util/logger'; -import {validateManifest} from './util/validations'; +import {logger} from '../common/util/logger'; +import {validateManifest} from '../common/util/validations'; +import {debugLogger} from '../common/util/debug-logger'; import {STRINGS} from './config'; +import {STRINGS as COMMON_STRINGS} from '../common/config'; -const {DISCLAIMER_MESSAGE, EXITING_IF, STARTING_IF} = STRINGS; +const {EXITING_IF, STARTING_IF} = STRINGS; +const {DISCLAIMER_MESSAGE} = COMMON_STRINGS; const impactEngine = async () => { - const options = parseIEProcessArgs(); + const options = parseIfRunProcessArgs(); const {inputPath, paramPath, outputOptions, debug} = options; debugLogger.overrideConsoleMethods(!!debug); diff --git a/src/lib/aggregate.ts b/src/if-run/lib/aggregate.ts similarity index 97% rename from src/lib/aggregate.ts rename to src/if-run/lib/aggregate.ts index c6eef11cc..4c16a0d0d 100644 --- a/src/lib/aggregate.ts +++ b/src/if-run/lib/aggregate.ts @@ -3,8 +3,10 @@ import {PluginParams} from '@grnsft/if-core/types'; import {aggregateInputsIntoOne} from '../util/aggregation-helper'; import {STRINGS} from '../config/strings'; - -import {AggregationParams, AggregationParamsSure} from '../types/manifest'; +import { + AggregationParams, + AggregationParamsSure, +} from '../../common/types/manifest'; const {AGGREGATING_NODE, AGGREGATING_OUTPUTS} = STRINGS; diff --git a/src/lib/compute.ts b/src/if-run/lib/compute.ts similarity index 98% rename from src/lib/compute.ts rename to src/if-run/lib/compute.ts index b83a22d2e..dde49d3f7 100644 --- a/src/lib/compute.ts +++ b/src/if-run/lib/compute.ts @@ -1,13 +1,13 @@ import {PluginParams, GroupByConfig} from '@grnsft/if-core/types'; -import {debugLogger} from '../util/debug-logger'; import {mergeObjects} from '../util/helpers'; - -import {ComputeParams, Node, Params} from '../types/compute'; -import {isExecute, isGroupBy} from '../types/interface'; +import {debugLogger} from '../../common/util/debug-logger'; import {STRINGS} from '../config/strings'; +import {isExecute, isGroupBy} from '../types/interface'; +import {ComputeParams, Node, Params} from '../types/compute'; + const {MERGING_DEFAULTS_WITH_INPUT_DATA, COMPUTING_PIPELINE_FOR_NODE} = STRINGS; /** diff --git a/src/lib/environment.ts b/src/if-run/lib/environment.ts similarity index 94% rename from src/lib/environment.ts rename to src/if-run/lib/environment.ts index 52e2b0c9c..21808cc20 100644 --- a/src/lib/environment.ts +++ b/src/if-run/lib/environment.ts @@ -1,14 +1,13 @@ import {DateTime} from 'luxon'; -import {execPromise} from '../util/helpers'; - -import {Manifest} from '../types/manifest'; -import {NpmListResponse, PackageDependency} from '../types/environment'; import {osInfo} from '../util/os-checker'; +import {execPromise} from '../../common/util/helpers'; +import {Manifest} from '../../common/types/manifest'; import {STRINGS} from '../config/strings'; +import {NpmListResponse, PackageDependency} from '../types/environment'; -const packageJson = require('../../package.json'); +const packageJson = require('../../../package.json'); const {CAPTURING_RUNTIME_ENVIRONMENT_DATA} = STRINGS; diff --git a/src/lib/exhaust.ts b/src/if-run/lib/exhaust.ts similarity index 97% rename from src/lib/exhaust.ts rename to src/if-run/lib/exhaust.ts index 1b65ca898..1ca6d9f1c 100644 --- a/src/lib/exhaust.ts +++ b/src/if-run/lib/exhaust.ts @@ -11,8 +11,8 @@ import {ExportYaml} from '../builtins/export-yaml'; import {STRINGS} from '../config'; import {ExhaustPluginInterface} from '../types/exhaust-plugin-interface'; -import {Context} from '../types/manifest'; import {Options} from '../types/process-args'; +import {Context} from '../../common/types/manifest'; const {InvalidExhaustPluginError} = ERRORS; const {INVALID_EXHAUST_PLUGIN, PREPARING_OUTPUT_DATA} = STRINGS; diff --git a/src/lib/initialize.ts b/src/if-run/lib/initialize.ts similarity index 95% rename from src/lib/initialize.ts rename to src/if-run/lib/initialize.ts index 212d98e37..5833787ba 100644 --- a/src/lib/initialize.ts +++ b/src/if-run/lib/initialize.ts @@ -2,14 +2,14 @@ import * as path from 'node:path'; import {ERRORS} from '@grnsft/if-core/utils'; -import {logger} from '../util/logger'; +import {logger} from '../../common/util/logger'; import {memoizedLog} from '../util/log-memoize'; import {pluginStorage} from '../util/plugin-storage'; import {CONFIG, STRINGS} from '../config'; import {PluginInterface} from '../types/interface'; -import {GlobalPlugins, PluginOptions} from '../types/manifest'; +import {GlobalPlugins, PluginOptions} from '../../common/types/manifest'; import {PluginStorageInterface} from '../types/plugin-storage'; const { diff --git a/src/lib/parameterize.ts b/src/if-run/lib/parameterize.ts similarity index 87% rename from src/lib/parameterize.ts rename to src/if-run/lib/parameterize.ts index 591ef781e..1034e1ce2 100644 --- a/src/lib/parameterize.ts +++ b/src/if-run/lib/parameterize.ts @@ -1,11 +1,11 @@ -import {logger} from '../util/logger'; +import {debugLogger} from '../../common/util/debug-logger'; +import {logger} from '../../common/util/logger'; import {memoizedLog} from '../util/log-memoize'; -import {debugLogger} from '../util/debug-logger'; import {STRINGS, PARAMETERS} from '../config'; -import {ManifestParameter} from '../types/manifest'; import {Parameters} from '../types/parameters'; +import {ManifestParameter} from '../../common/types/manifest'; const { REJECTING_OVERRIDE, @@ -57,6 +57,8 @@ const Parameterize = () => { const {description, unit, aggregation, name} = param; + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore parameters[name] = { description, unit, diff --git a/src/types/aggregation.ts b/src/if-run/types/aggregation.ts similarity index 100% rename from src/types/aggregation.ts rename to src/if-run/types/aggregation.ts diff --git a/src/types/compute.ts b/src/if-run/types/compute.ts similarity index 91% rename from src/types/compute.ts rename to src/if-run/types/compute.ts index 2de3e6e33..bfe294241 100644 --- a/src/types/compute.ts +++ b/src/if-run/types/compute.ts @@ -1,7 +1,7 @@ import {PluginParams} from '@grnsft/if-core/types'; -import {Context} from './manifest'; import {PluginStorageInterface} from './plugin-storage'; +import {Context} from '../../common/types/manifest'; export type NodeConfig = { [key: string]: Record; diff --git a/src/types/environment.ts b/src/if-run/types/environment.ts similarity index 100% rename from src/types/environment.ts rename to src/if-run/types/environment.ts diff --git a/src/types/exhaust-plugin-interface.ts b/src/if-run/types/exhaust-plugin-interface.ts similarity index 80% rename from src/types/exhaust-plugin-interface.ts rename to src/if-run/types/exhaust-plugin-interface.ts index 3dc00182a..de6183842 100644 --- a/src/types/exhaust-plugin-interface.ts +++ b/src/if-run/types/exhaust-plugin-interface.ts @@ -1,4 +1,4 @@ -import {Context} from './manifest'; +import {Context} from '../../common/types/manifest'; export interface ExhaustPluginInterface { /** diff --git a/src/types/interface.ts b/src/if-run/types/interface.ts similarity index 100% rename from src/types/interface.ts rename to src/if-run/types/interface.ts diff --git a/src/types/parameters.ts b/src/if-run/types/parameters.ts similarity index 74% rename from src/types/parameters.ts rename to src/if-run/types/parameters.ts index 7434777ce..939ac2ba6 100644 --- a/src/types/parameters.ts +++ b/src/if-run/types/parameters.ts @@ -1,4 +1,4 @@ -import {ManifestParameter} from './manifest'; +import {ManifestParameter} from '../../common/types/manifest'; export const AGGREGATION_TYPES = ['sum', 'none', 'avg'] as const; diff --git a/src/types/plugin-storage.ts b/src/if-run/types/plugin-storage.ts similarity index 100% rename from src/types/plugin-storage.ts rename to src/if-run/types/plugin-storage.ts diff --git a/src/types/process-args.ts b/src/if-run/types/process-args.ts similarity index 57% rename from src/types/process-args.ts rename to src/if-run/types/process-args.ts index 0f3799f5a..8f7903112 100644 --- a/src/types/process-args.ts +++ b/src/if-run/types/process-args.ts @@ -1,4 +1,4 @@ -export interface IEArgs { +export interface IfRunArgs { manifest?: string; output?: string; 'override-params'?: string; @@ -6,27 +6,6 @@ export interface IEArgs { debug?: boolean; } -export interface IFDiffArgs { - source?: string; - target: string; -} - -export interface IFEnvArgs { - manifest?: string; - install?: boolean; - cwd?: boolean; -} - -export interface IFCheckArgs { - manifest?: string; - directory?: string; -} - -export interface Options { - outputPath?: string; - stdout?: boolean; -} - export interface ProcessArgsOutputs { inputPath: string; outputOptions: { @@ -36,3 +15,8 @@ export interface ProcessArgsOutputs { paramPath?: string; debug?: boolean; } + +export interface Options { + outputPath?: string; + stdout?: boolean; +} diff --git a/src/types/time-sync.ts b/src/if-run/types/time-sync.ts similarity index 100% rename from src/types/time-sync.ts rename to src/if-run/types/time-sync.ts diff --git a/src/util/aggregation-helper.ts b/src/if-run/util/aggregation-helper.ts similarity index 100% rename from src/util/aggregation-helper.ts rename to src/if-run/util/aggregation-helper.ts diff --git a/src/if-run/util/args.ts b/src/if-run/util/args.ts new file mode 100644 index 000000000..78f125bb1 --- /dev/null +++ b/src/if-run/util/args.ts @@ -0,0 +1,73 @@ +import {parse} from 'ts-command-line-args'; +import {ERRORS} from '@grnsft/if-core/utils'; + +import {checkIfFileIsYaml} from '../../common/util/yaml'; +import {prependFullFilePath} from '../../common/util/helpers'; + +import {logger} from '../../common/util/logger'; + +import {CONFIG, STRINGS} from '../config'; +import {STRINGS as COMMON_STRINGS} from '../../common/config'; + +import {IfRunArgs, ProcessArgsOutputs} from '../types/process-args'; + +const {ParseCliParamsError, CliSourceFileError} = ERRORS; + +const {IF_RUN} = CONFIG; +const {NO_OUTPUT} = STRINGS; +const {SOURCE_IS_NOT_YAML, MANIFEST_IS_MISSING} = COMMON_STRINGS; + +/** + * Validates `ie` process arguments. + */ +const validateAndParseProcessArgs = () => { + try { + return parse(IF_RUN.ARGS, IF_RUN.HELP); + } catch (error) { + if (error instanceof Error) { + throw new ParseCliParamsError(error.message); + } + + throw error; + } +}; + +/** + * 1. Parses process arguments like `manifest`, `output`, `override-params`, `help` and `debug`. + * 2. Checks if `help` param is provided, then logs help message and exits. + * 3. If output params are missing, warns user about it. + * 3. Otherwise checks if `manifest` param is there, then processes with checking if it's a yaml file. + * If it is, then returns object containing full path. + * 4. If params are missing or invalid, then rejects with `ParseCliParamsError`. + */ +export const parseIfRunProcessArgs = (): ProcessArgsOutputs => { + const { + manifest, + output, + 'override-params': overrideParams, + stdout, + debug, + } = validateAndParseProcessArgs(); + + if (!output && !stdout) { + logger.warn(NO_OUTPUT); + } + + if (manifest) { + if (checkIfFileIsYaml(manifest)) { + return { + inputPath: prependFullFilePath(manifest), + outputOptions: { + ...(output && {outputPath: prependFullFilePath(output)}), + ...(stdout && {stdout}), + }, + ...(overrideParams && {paramPath: overrideParams}), + debug, + }; + } + + throw new CliSourceFileError(SOURCE_IS_NOT_YAML); + } + + throw new CliSourceFileError(MANIFEST_IS_MISSING); +}; diff --git a/src/if-run/util/helpers.ts b/src/if-run/util/helpers.ts new file mode 100644 index 000000000..978ef128f --- /dev/null +++ b/src/if-run/util/helpers.ts @@ -0,0 +1,41 @@ +import {ERRORS} from '@grnsft/if-core/utils'; + +import {STRINGS} from '../config'; + +import {logger} from '../../common/util/logger'; + +const {UNSUPPORTED_ERROR} = STRINGS; + +/** + * Impact engine error handler. Logs errors and appends issue template if error is unknown. + */ +export const andHandle = (error: Error) => { + const knownErrors = Object.keys(ERRORS); + + logger.error(error); + + if (!knownErrors.includes(error.name)) { + logger.error(UNSUPPORTED_ERROR(error.name)); + // eslint-disable-next-line no-process-exit + process.exit(2); + } +}; + +/** + * Append entries from defaults which are missing from inputs. + */ +export const mergeObjects = (defaults: any, input: any) => { + const merged: Record = structuredClone(input); + + for (const key in defaults) { + if (!(key in input)) { + merged[key] = defaults[key]; + } + + if (merged[key] === undefined || merged[key] === null) { + merged[key] = defaults[key]; + } + } + + return merged; +}; diff --git a/src/util/json.ts b/src/if-run/util/json.ts similarity index 100% rename from src/util/json.ts rename to src/if-run/util/json.ts diff --git a/src/util/log-memoize.ts b/src/if-run/util/log-memoize.ts similarity index 100% rename from src/util/log-memoize.ts rename to src/if-run/util/log-memoize.ts diff --git a/src/util/os-checker.ts b/src/if-run/util/os-checker.ts similarity index 98% rename from src/util/os-checker.ts rename to src/if-run/util/os-checker.ts index c795ecc61..1614d5a4b 100644 --- a/src/util/os-checker.ts +++ b/src/if-run/util/os-checker.ts @@ -1,6 +1,6 @@ import {release, platform} from 'os'; -import {execPromise} from './helpers'; +import {execPromise} from '../../common/util/helpers'; /** * Executes `lsb_release -a` command in terminal. diff --git a/src/util/plugin-storage.ts b/src/if-run/util/plugin-storage.ts similarity index 100% rename from src/util/plugin-storage.ts rename to src/if-run/util/plugin-storage.ts index 1f9711943..ca2907fcc 100644 --- a/src/util/plugin-storage.ts +++ b/src/if-run/util/plugin-storage.ts @@ -2,8 +2,8 @@ import {ERRORS} from '@grnsft/if-core/utils'; import {STRINGS} from '../config'; -import {PluginInterface} from '../types/interface'; import {PluginStorage} from '../types/plugin-storage'; +import {PluginInterface} from '../types/interface'; const {PluginInitializationError} = ERRORS; const {NOT_INITALIZED_PLUGIN} = STRINGS; diff --git a/src/lib/load.ts b/src/lib/load.ts deleted file mode 100644 index 61f2b6cf5..000000000 --- a/src/lib/load.ts +++ /dev/null @@ -1,61 +0,0 @@ -import * as YAML from 'js-yaml'; -import {ERRORS} from '@grnsft/if-core/utils'; - -import {openYamlFileAsObject} from '../util/yaml'; -import {readAndParseJson} from '../util/json'; - -import {PARAMETERS} from '../config'; -import {STRINGS} from '../config'; - -import {Parameters} from '../types/parameters'; -import {LoadDiffParams} from '../types/util/args'; -import {Manifest} from '../types/manifest'; - -const {CliSourceFileError} = ERRORS; - -const {INVALID_SOURCE, LOADING_MANIFEST} = STRINGS; - -/** - * Parses manifest file as an object. Checks if parameter file is passed via CLI, then loads it too. - * Returns context, tree and parameters (either the default one, or from CLI). - */ -export const load = async (inputPath: string, paramPath?: string) => { - console.debug(LOADING_MANIFEST); - - const rawManifest = await openYamlFileAsObject(inputPath); - const parametersFromCli = - paramPath && - (await readAndParseJson(paramPath)); /** @todo validate json */ - const parameters = - parametersFromCli || - PARAMETERS; /** @todo PARAMETERS should be specified in parameterize only */ - - return { - rawManifest, - parameters, - }; -}; - -/** - * Loads files to compare. As a source file checks if data is piped and then decides which one to take. - */ -export const loadIfDiffFiles = async (params: LoadDiffParams) => { - const {sourcePath, targetPath, pipedSourceManifest} = params; - - if (!sourcePath && !pipedSourceManifest) { - throw new CliSourceFileError(INVALID_SOURCE); - } - - const loadFromSource = - sourcePath && (await openYamlFileAsObject(sourcePath!)); - const loadFromSTDIN = - pipedSourceManifest && (await YAML.load(pipedSourceManifest!)); - - const rawSourceManifest = loadFromSource || loadFromSTDIN; - const rawTargetManifest = await openYamlFileAsObject(targetPath); - - return { - rawSourceManifest, - rawTargetManifest, - }; -}; diff --git a/src/util/args.ts b/src/util/args.ts deleted file mode 100644 index 31da90461..000000000 --- a/src/util/args.ts +++ /dev/null @@ -1,248 +0,0 @@ -import * as path from 'path'; - -import {parse} from 'ts-command-line-args'; -import {ERRORS} from '@grnsft/if-core/utils'; - -import {checkIfFileIsYaml} from './yaml'; - -import {isDirectoryExists, isFileExists} from './fs'; - -import {logger} from './logger'; - -import {CONFIG, STRINGS} from '../config'; - -import { - IFDiffArgs, - IEArgs, - ProcessArgsOutputs, - IFEnvArgs, - IFCheckArgs, -} from '../types/process-args'; -import {LoadDiffParams} from '../types/util/args'; - -const { - ParseCliParamsError, - CliTargetFileError, - CliSourceFileError, - InvalidDirectoryError, - MissingCliFlagsError, -} = ERRORS; - -const {IE, IF_DIFF, IF_ENV, IF_CHECK} = CONFIG; - -const { - MANIFEST_IS_MISSING, - MANIFEST_NOT_FOUND, - NO_OUTPUT, - SOURCE_IS_NOT_YAML, - TARGET_IS_NOT_YAML, - INVALID_TARGET, - IF_CHECK_FLAGS_MISSING, - DIRECTORY_NOT_FOUND, -} = STRINGS; - -/** - * Validates `ie` process arguments. - */ -const validateAndParseProcessArgs = () => { - try { - return parse(IE.ARGS, IE.HELP); - } catch (error) { - if (error instanceof Error) { - throw new ParseCliParamsError(error.message); - } - - throw error; - } -}; - -/** - * Prepends process path to given `filePath`. - */ -const prependFullFilePath = (filePath: string) => { - const processRunningPath = process.cwd(); - - if (path.isAbsolute(filePath)) { - return filePath; - } - - return path.normalize(`${processRunningPath}/${filePath}`); -}; - -/** - * 1. Parses process arguments like `manifest`, `output`, `override-params`, `help` and `debug`. - * 2. Checks if `help` param is provided, then logs help message and exits. - * 3. If output params are missing, warns user about it. - * 3. Otherwise checks if `manifest` param is there, then processes with checking if it's a yaml file. - * If it is, then returns object containing full path. - * 4. If params are missing or invalid, then rejects with `ParseCliParamsError`. - */ -export const parseIEProcessArgs = (): ProcessArgsOutputs => { - const { - manifest, - output, - 'override-params': overrideParams, - stdout, - debug, - } = validateAndParseProcessArgs(); - - if (!output && !stdout) { - logger.warn(NO_OUTPUT); - } - - if (manifest) { - if (checkIfFileIsYaml(manifest)) { - return { - inputPath: prependFullFilePath(manifest), - outputOptions: { - ...(output && {outputPath: prependFullFilePath(output)}), - ...(stdout && {stdout}), - }, - ...(overrideParams && {paramPath: overrideParams}), - debug, - }; - } - - throw new CliSourceFileError(SOURCE_IS_NOT_YAML); - } - - throw new CliSourceFileError(MANIFEST_IS_MISSING); -}; - -/** -- IF Diff -- */ - -/** - * Parses `if-diff` process arguments. - */ -const validateAndParseIfDiffArgs = () => { - try { - return parse(IF_DIFF.ARGS, IF_DIFF.HELP); - } catch (error) { - if (error instanceof Error) { - throw new ParseCliParamsError(error.message); - } - - throw error; - } -}; - -/** - * Checks for `source` and `target` flags to be present. - */ -export const parseIfDiffArgs = () => { - const {source, target} = validateAndParseIfDiffArgs(); - - if (target) { - if (source && !checkIfFileIsYaml(source)) { - throw new CliSourceFileError(SOURCE_IS_NOT_YAML); - } - - if (checkIfFileIsYaml(target)) { - const response: LoadDiffParams = { - targetPath: prependFullFilePath(target), - }; - - if (source && checkIfFileIsYaml(source)) { - response.sourcePath = prependFullFilePath(source); - } - - return response; - } - - throw new CliTargetFileError(TARGET_IS_NOT_YAML); - } - - throw new ParseCliParamsError(INVALID_TARGET); -}; - -/** -- IF Env -- */ - -/** - * Parses `if-env` process arguments. - */ -const validateAndParseIfEnvArgs = () => { - try { - return parse(IF_ENV.ARGS, IF_ENV.HELP); - } catch (error) { - if (error instanceof Error) { - throw new ParseCliParamsError(error.message); - } - - throw error; - } -}; - -/** - * Checks if the `manifest` command is provided and it is valid manifest file. - */ -export const parseIfEnvArgs = async () => { - const {manifest, install, cwd} = validateAndParseIfEnvArgs(); - - if (manifest) { - const response = prependFullFilePath(manifest); - const isManifestFileExists = await isFileExists(response); - - if (!isManifestFileExists) { - throw new ParseCliParamsError(MANIFEST_NOT_FOUND); - } - - if (checkIfFileIsYaml(manifest)) { - return {manifest: response, install, cwd}; - } - - throw new CliSourceFileError(SOURCE_IS_NOT_YAML); - } - - return {install, cwd}; -}; - -/** -- IF Check -- */ - -/** - * Parses `if-check` process arguments. - */ -const validateAndParseIfCheckArgs = () => { - try { - return parse(IF_CHECK.ARGS, IF_CHECK.HELP); - } catch (error) { - if (error instanceof Error) { - throw new ParseCliParamsError(error.message); - } - - throw error; - } -}; - -/** - * Checks if either `manifest` or `directory` command is provided. - */ -export const parseIfCheckArgs = async () => { - const {manifest, directory} = validateAndParseIfCheckArgs(); - - if (manifest) { - const response = prependFullFilePath(manifest); - const isManifestFileExists = await isFileExists(response); - - if (!isManifestFileExists) { - throw new ParseCliParamsError(MANIFEST_NOT_FOUND); - } - - if (checkIfFileIsYaml(manifest)) { - return {manifest}; - } - - throw new CliSourceFileError(SOURCE_IS_NOT_YAML); - } else if (directory) { - const isDirExists = await isDirectoryExists(directory); - - if (!isDirExists) { - throw new InvalidDirectoryError(DIRECTORY_NOT_FOUND); - } - - const response = prependFullFilePath(directory); - - return {directory: response}; - } - - throw new MissingCliFlagsError(IF_CHECK_FLAGS_MISSING); -}; diff --git a/src/util/helpers.ts b/src/util/helpers.ts deleted file mode 100644 index c55b43724..000000000 --- a/src/util/helpers.ts +++ /dev/null @@ -1,285 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable no-process-exit */ -import {createInterface} from 'node:readline/promises'; -import {exec} from 'node:child_process'; -import {promisify} from 'node:util'; - -import * as fs from 'fs/promises'; -import * as path from 'path'; - -import {ERRORS} from '@grnsft/if-core/utils'; - -import {STRINGS, CONFIG} from '../config'; - -import {Difference} from '../types/lib/compare'; - -import {load} from '../lib/load'; - -import { - installDependencies, - initPackageJsonIfNotExists, - updatePackageJsonDependencies, - extractPathsWithVersion, - updatePackageJsonProperties, -} from './npm'; - -import {logger} from './logger'; -import {EnvironmentOptions} from '../types/if-env'; - -const {IF_ENV} = CONFIG; -const { - FAILURE_MESSAGE, - FAILURE_MESSAGE_TEMPLATE, - FAILURE_MESSAGE_DEPENDENCIES, -} = IF_ENV; - -const {UNSUPPORTED_ERROR, IF_CHECK_FAILED, IF_CHECK_SUMMARY_ERROR_MESSAGE} = - STRINGS; -const {MissingPluginDependenciesError} = ERRORS; - -/** - * Impact engine error handler. Logs errors and appends issue template if error is unknown. - */ -export const andHandle = (error: Error) => { - const knownErrors = Object.keys(ERRORS); - - logger.error(error); - - if (!knownErrors.includes(error.name)) { - logger.error(UNSUPPORTED_ERROR(error.name)); - // eslint-disable-next-line no-process-exit - process.exit(2); - } -}; - -/** - * Append entries from defaults which are missing from inputs. - */ -export const mergeObjects = (defaults: any, input: any) => { - const merged: Record = structuredClone(input); - - for (const key in defaults) { - if (!(key in input)) { - merged[key] = defaults[key]; - } - - if (merged[key] === undefined || merged[key] === null) { - merged[key] = defaults[key]; - } - } - - return merged; -}; - -/** - * Promise version of Node's `exec` from `child-process`. - */ -export const execPromise = promisify(exec); - -/** - * `If-diff` equality checker. - */ -export const checkIfEqual = (source: any, target: any) => { - if (source === target) { - return true; - } - - if (source === '*' || target === '*') { - return true; - } - - return false; -}; - -/** - * Converts given `value` to either `1` or `0`. - */ -const convertToXorable = (value: any) => { - if (typeof value === 'number') { - return value !== 0 ? 1 : 0; - } - - if (typeof value === 'boolean') { - return value ? 1 : 0; - } - - if (typeof value === 'string') { - return value.length > 0 ? 1 : 0; - } - - if (typeof value === 'object') { - return 1; - } - - return 0; -}; - -/** - * If one of the `valuesToCheck` values is undefined, then set `missing`, otherwise `exists`. - */ -const setValuesIfMissing = (response: Difference) => { - const source = convertToXorable(response.source); - const target = convertToXorable(response.target); - - if (source ^ target) { - ['source', 'target'].forEach(value => { - response[value] = response[value] ? 'exists' : 'missing'; - }); - - return response; - } - - return response; -}; - -/** - * Checks if objects are primitive types. - */ -export const oneIsPrimitive = (source: any, target: any) => { - // eslint-disable-next-line eqeqeq - if (source == null || target == null) { - return true; - } - - return source !== Object(source) && target !== Object(target); -}; - -/** - * Format not matching message for CLI logging. - */ -export const formatNotMatchingLog = (message: Difference) => { - const flattenMessage = setValuesIfMissing(message); - - Object.keys(flattenMessage).forEach(key => { - if (key === 'message' || key === 'path') { - console.log(message[key]); - } else { - console.log(`${key}: ${message[key]}`); - } - }); -}; - -/** - * Checks if there is data piped, then collects it. - * Otherwise returns empty string. - */ -const collectPipedData = async () => { - if (process.stdin.isTTY) { - return ''; - } - - const readline = createInterface({ - input: process.stdin, - }); - - const data: string[] = []; - - for await (const line of readline) { - data.push(line); - } - - return data.join('\n'); -}; - -/** - * Checks if there is piped data, tries to parse yaml from it. - * Returns empty string if haven't found anything. - */ -export const parseManifestFromStdin = async () => { - const pipedSourceManifest = await collectPipedData(); - - if (!pipedSourceManifest) { - return ''; - } - - const regex = /# start((?:.*\n)+?)# end/; - const match = regex.exec(pipedSourceManifest); - - if (!match) { - return ''; - } - - return match![1]; -}; - -/** - * Gets the folder path of the manifest file, dependencies from manifest file and install argument from the given arguments. - */ -export const getOptionsFromArgs = async (commandArgs: { - manifest: string; - install: boolean | undefined; -}) => { - const {manifest, install} = commandArgs; - const folderPath = path.dirname(manifest); - const loadedManifest = await load(manifest); - const rawManifest = loadedManifest.rawManifest; - const plugins = rawManifest?.initialize?.plugins || {}; - const dependencies = rawManifest?.execution?.environment.dependencies || []; - - if (!dependencies.length) { - throw new MissingPluginDependenciesError(FAILURE_MESSAGE_DEPENDENCIES); - } - - const pathsWithVersion = extractPathsWithVersion(plugins, dependencies); - - return { - folderPath, - dependencies: pathsWithVersion, - install, - }; -}; - -/** - * Creates folder if not exists, installs dependencies if required, update depenedencies. - */ -export const initializeAndInstallLibs = async (options: EnvironmentOptions) => { - try { - const {folderPath, install, cwd, dependencies} = options; - const packageJsonPath = await initPackageJsonIfNotExists(folderPath); - - await updatePackageJsonProperties(packageJsonPath, cwd); - - if (install) { - await installDependencies(folderPath, dependencies); - } else { - await updatePackageJsonDependencies(packageJsonPath, dependencies, cwd); - } - } catch (error) { - console.log(FAILURE_MESSAGE); - process.exit(2); - } -}; - -/** - * Adds a manifest template to the folder where the if-env CLI command runs. - */ -export const addTemplateManifest = async (destinationDir: string) => { - try { - const templateManifest = path.resolve( - __dirname, - '../config/env-template.yml' - ); - - const destinationPath = path.resolve(destinationDir, 'manifest.yml'); - const data = await fs.readFile(templateManifest, 'utf-8'); - - await fs.writeFile(destinationPath, data, 'utf-8'); - } catch (error) { - console.log(FAILURE_MESSAGE_TEMPLATE); - process.exit(1); - } -}; - -/** - * Logs the failure message from the stdout of an error. - */ -export const logStdoutFailMessage = (error: any, fileName: string) => { - console.log(IF_CHECK_FAILED(fileName)); - - const stdout = error.stdout; - const logs = stdout.split('\n\n'); - const failMessage = logs[logs.length - 1]; - - console.log(failMessage); - return IF_CHECK_SUMMARY_ERROR_MESSAGE(fileName, failMessage); -};