feat(Yaml Viewer): add parsing validation

Add parsing validations
Fix #540
This commit is contained in:
sharevb 2024-04-03 23:05:13 +02:00 committed by ShareVB
parent d3b32cc14e
commit fd0a723f06
6 changed files with 123 additions and 65 deletions

3
components.d.ts vendored
View file

@ -126,6 +126,7 @@ declare module '@vue/runtime-core' {
MenuLayout: typeof import('./src/components/MenuLayout.vue')['default'] MenuLayout: typeof import('./src/components/MenuLayout.vue')['default']
MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default'] MetaTagGenerator: typeof import('./src/tools/meta-tag-generator/meta-tag-generator.vue')['default']
MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default'] MimeTypes: typeof import('./src/tools/mime-types/mime-types.vue')['default']
NAlert: typeof import('naive-ui')['NAlert']
NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default'] NavbarButtons: typeof import('./src/components/NavbarButtons.vue')['default']
NCode: typeof import('naive-ui')['NCode'] NCode: typeof import('naive-ui')['NCode']
NCollapseTransition: typeof import('naive-ui')['NCollapseTransition'] NCollapseTransition: typeof import('naive-ui')['NCollapseTransition']
@ -145,6 +146,7 @@ declare module '@vue/runtime-core' {
NMenu: typeof import('naive-ui')['NMenu'] NMenu: typeof import('naive-ui')['NMenu']
NScrollbar: typeof import('naive-ui')['NScrollbar'] NScrollbar: typeof import('naive-ui')['NScrollbar']
NSpin: typeof import('naive-ui')['NSpin'] NSpin: typeof import('naive-ui')['NSpin']
NSwitch: typeof import('naive-ui')['NSwitch']
NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default'] NumeronymGenerator: typeof import('./src/tools/numeronym-generator/numeronym-generator.vue')['default']
OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default'] OtpCodeGeneratorAndValidator: typeof import('./src/tools/otp-code-generator-and-validator/otp-code-generator-and-validator.vue')['default']
PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default'] PasswordStrengthAnalyser: typeof import('./src/tools/password-strength-analyser/password-strength-analyser.vue')['default']
@ -159,6 +161,7 @@ declare module '@vue/runtime-core' {
RouterLink: typeof import('vue-router')['RouterLink'] RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView'] RouterView: typeof import('vue-router')['RouterView']
RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default'] RsaKeyPairGenerator: typeof import('./src/tools/rsa-key-pair-generator/rsa-key-pair-generator.vue')['default']
SafelinkDecoder: typeof import('./src/tools/safelink-decoder/safelink-decoder.vue')['default']
SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default'] SlugifyString: typeof import('./src/tools/slugify-string/slugify-string.vue')['default']
SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default'] SpanCopyable: typeof import('./src/components/SpanCopyable.vue')['default']
SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default'] SqlPrettify: typeof import('./src/tools/sql-prettify/sql-prettify.vue')['default']

View file

@ -51,6 +51,7 @@
"change-case": "^4.1.2", "change-case": "^4.1.2",
"colord": "^2.9.3", "colord": "^2.9.3",
"composerize-ts": "^0.6.2", "composerize-ts": "^0.6.2",
"composeverter": "^1.7.2",
"country-code-lookup": "^0.1.0", "country-code-lookup": "^0.1.0",
"cron-validator": "^1.3.1", "cron-validator": "^1.3.1",
"cronstrue": "^2.26.0", "cronstrue": "^2.26.0",

48
pnpm-lock.yaml generated
View file

@ -53,6 +53,9 @@ dependencies:
composerize-ts: composerize-ts:
specifier: ^0.6.2 specifier: ^0.6.2
version: 0.6.2 version: 0.6.2
composeverter:
specifier: ^1.7.2
version: 1.7.2
country-code-lookup: country-code-lookup:
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0 version: 0.1.0
@ -3351,7 +3354,7 @@ packages:
dependencies: dependencies:
'@unhead/dom': 0.5.1 '@unhead/dom': 0.5.1
'@unhead/schema': 0.5.1 '@unhead/schema': 0.5.1
'@vueuse/shared': 10.7.2(vue@3.3.4) '@vueuse/shared': 10.9.0(vue@3.3.4)
unhead: 0.5.1 unhead: 0.5.1
vue: 3.3.4 vue: 3.3.4
transitivePeerDependencies: transitivePeerDependencies:
@ -3993,10 +3996,10 @@ packages:
- vue - vue
dev: false dev: false
/@vueuse/shared@10.7.2(vue@3.3.4): /@vueuse/shared@10.9.0(vue@3.3.4):
resolution: {integrity: sha512-qFbXoxS44pi2FkgFjPvF4h7c9oMDutpyBdcJdMYIMg9XyXli2meFMuaKn+UMgsClo//Th6+beeCgqweT/79BVA==} resolution: {integrity: sha512-Uud2IWncmAfJvRaFYzv5OHDli+FbOzxiVEQdLCKQKLyhz94PIyFC3CHcH7EDMwIn8NPtD06+PNbC/PiO0LGLtw==}
dependencies: dependencies:
vue-demi: 0.14.6(vue@3.3.4) vue-demi: 0.14.7(vue@3.3.4)
transitivePeerDependencies: transitivePeerDependencies:
- '@vue/composition-api' - '@vue/composition-api'
- vue - vue
@ -4051,6 +4054,14 @@ packages:
- supports-color - supports-color
dev: true dev: true
/ajv-errors@3.0.0(ajv@8.12.0):
resolution: {integrity: sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==}
peerDependencies:
ajv: ^8.0.1
dependencies:
ajv: 8.12.0
dev: false
/ajv@6.12.6: /ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
dependencies: dependencies:
@ -4067,7 +4078,6 @@ packages:
json-schema-traverse: 1.0.0 json-schema-traverse: 1.0.0
require-from-string: 2.0.2 require-from-string: 2.0.2
uri-js: 4.4.1 uri-js: 4.4.1
dev: true
/ansi-colors@4.1.3: /ansi-colors@4.1.3:
resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==}
@ -4560,6 +4570,15 @@ packages:
yamljs: 0.3.0 yamljs: 0.3.0
dev: false dev: false
/composeverter@1.7.2:
resolution: {integrity: sha512-gRSCkSU8wJQvmTHQ23wBd2gCdUsns95k3lL7YszZBu5EyA3eac46mI7nVgvk1pZTM0mOmj/E5yurtdYkuVNZRA==}
dependencies:
ajv: 8.12.0
ajv-errors: 3.0.0(ajv@8.12.0)
core-js: 2.6.12
yaml: 1.10.2
dev: false
/concat-map@0.0.1: /concat-map@0.0.1:
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
@ -4613,6 +4632,12 @@ packages:
browserslist: 4.22.1 browserslist: 4.22.1
dev: true dev: true
/core-js@2.6.12:
resolution: {integrity: sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==}
deprecated: core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.
requiresBuild: true
dev: false
/country-code-lookup@0.1.0: /country-code-lookup@0.1.0:
resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==} resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==}
dev: false dev: false
@ -6571,7 +6596,6 @@ packages:
/json-schema-traverse@1.0.0: /json-schema-traverse@1.0.0:
resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
dev: true
/json-schema@0.4.0: /json-schema@0.4.0:
resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==} resolution: {integrity: sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==}
@ -7666,7 +7690,6 @@ packages:
/punycode@2.3.0: /punycode@2.3.0:
resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==} resolution: {integrity: sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==}
engines: {node: '>=6'} engines: {node: '>=6'}
dev: true
/punycode@2.3.1: /punycode@2.3.1:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
@ -7814,7 +7837,6 @@ packages:
/require-from-string@2.0.2: /require-from-string@2.0.2:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
dev: true
/require-main-filename@2.0.0: /require-main-filename@2.0.0:
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==} resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
@ -8913,7 +8935,6 @@ packages:
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
dependencies: dependencies:
punycode: 2.3.0 punycode: 2.3.0
dev: true
/url-parse@1.5.10: /url-parse@1.5.10:
resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==}
@ -9151,8 +9172,8 @@ packages:
vue: 3.3.4 vue: 3.3.4
dev: false dev: false
/vue-demi@0.14.6(vue@3.3.4): /vue-demi@0.14.7(vue@3.3.4):
resolution: {integrity: sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==} resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==}
engines: {node: '>=12'} engines: {node: '>=12'}
hasBin: true hasBin: true
requiresBuild: true requiresBuild: true
@ -9579,6 +9600,11 @@ packages:
yaml: 2.2.1 yaml: 2.2.1
dev: true dev: true
/yaml@1.10.2:
resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==}
engines: {node: '>= 6'}
dev: false
/yaml@2.2.1: /yaml@2.2.1:
resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==} resolution: {integrity: sha512-e0WHiYql7+9wr4cWMx3TVQrNwejKaEe7/rHNmQmqRjazfOP5W8PB6Jpebb5o6fIapbz9o9+2ipcaTM2ZwDI6lw==}
engines: {node: '>= 14'} engines: {node: '>= 14'}

View file

@ -0,0 +1,20 @@
declare module 'composeverter' {
interface Configuration {
expandVolumes?: boolean;
expandPorts?: boolean;
indent?: number;
}
interface DockerComposeValidatioError {
line?: number;
message: string;
helpLink?: string;
}
export function validateDockerComposeToCommonSpec(content: string): DockerComposeValidatioError[];
export function migrateFromV2xToV3x(content: string, configuration?: Configuration = null): string;
export function migrateFromV3xToV2x(content: string, configuration?: Configuration = null): string;
export function migrateFromV1ToV2x(content: string, configuration?: Configuration = null): string;
export function migrateToCommonSpec(content: string, configuration?: Configuration = null): string;
export function migrateFromV2xToV3x(content: string, configuration?: Configuration = null): string;
export function getDockerComposeSchemaWithoutFormats(): object;
export function yamlParse(content: string): object;
}

View file

@ -1,5 +1,5 @@
import { type MaybeRef, get } from '@vueuse/core'; import { type MaybeRef, get } from '@vueuse/core';
import { yamlParse } from 'composeverter';
import yaml from 'yaml'; import yaml from 'yaml';
export { formatYaml }; export { formatYaml };
@ -13,7 +13,7 @@ function formatYaml({
sortKeys?: MaybeRef<boolean> sortKeys?: MaybeRef<boolean>
indentSize?: MaybeRef<number> indentSize?: MaybeRef<number>
}) { }) {
const parsedYaml = yaml.parse(get(rawYaml)); const parsedYaml = yamlParse(get(rawYaml));
const formattedYAML = yaml.stringify(parsedYaml, { const formattedYAML = yaml.stringify(parsedYaml, {
sortMapEntries: get(sortKeys), sortMapEntries: get(sortKeys),

View file

@ -1,72 +1,80 @@
<script setup lang="ts"> <script setup lang="ts">
import yaml from 'yaml';
import { useStorage } from '@vueuse/core'; import { useStorage } from '@vueuse/core';
import { formatYaml } from './yaml-models'; import { formatYaml } from './yaml-models';
import { withDefaultOnError } from '@/utils/defaults';
import { useValidation } from '@/composable/validation';
import TextareaCopyable from '@/components/TextareaCopyable.vue'; import TextareaCopyable from '@/components/TextareaCopyable.vue';
const inputElement = ref<HTMLElement>();
const rawYaml = useStorage('yaml-prettify:raw-yaml', ''); const rawYaml = useStorage('yaml-prettify:raw-yaml', '');
const indentSize = useStorage('yaml-prettify:indent-size', 2); const indentSize = useStorage('yaml-prettify:indent-size', 2);
const sortKeys = useStorage('yaml-prettify:sort-keys', false); const sortKeys = useStorage('yaml-prettify:sort-keys', false);
const cleanYaml = computed(() => withDefaultOnError(() => formatYaml({ rawYaml, indentSize, sortKeys }), '')); const yamlFormattingResult = computed(() => {
try {
const rawYamlValidation = useValidation({ return { yaml: formatYaml({ rawYaml, indentSize, sortKeys }), errors: [] };
source: rawYaml, }
rules: [ catch (e: any) {
{ return { yaml: '#see error messages', errors: e.toString().split('\n') };
validator: v => v === '' || yaml.parse(v), }
message: 'Provided YAML is not valid.',
},
],
}); });
const errors = computed(() => yamlFormattingResult.value.errors);
const cleanYaml = computed(() => yamlFormattingResult.value.yaml);
const MONACO_EDITOR_OPTIONS = {
automaticLayout: true,
formatOnType: true,
formatOnPaste: true,
};
</script> </script>
<template> <template>
<div style="flex: 0 0 100%"> <div max-w-600>
<div style="margin: 0 auto; max-width: 600px" flex justify-center gap-3> <div style="flex: 0 0 100%">
<n-form-item label="Sort keys :" label-placement="left" label-width="100"> <div style="margin: 0 auto; max-width: 600px" flex justify-center gap-3>
<n-switch v-model:value="sortKeys" /> <n-form-item label="Sort keys :" label-placement="left" label-width="100">
</n-form-item> <n-switch v-model:value="sortKeys" />
<n-form-item label="Indent size :" label-placement="left" label-width="100" :show-feedback="false"> </n-form-item>
<n-input-number v-model:value="indentSize" min="1" max="10" style="width: 100px" /> <n-form-item label="Indent size :" label-placement="left" label-width="100" :show-feedback="false">
</n-form-item> <n-input-number v-model:value="indentSize" min="1" max="10" style="width: 100px" />
</n-form-item>
</div>
</div> </div>
</div>
<n-form-item <c-label label="Your raw YAML:">
label="Your raw YAML" <div relative w-full>
:feedback="rawYamlValidation.message" <c-monaco-editor
:validation-status="rawYamlValidation.status" v-model:value="rawYaml"
> theme="vs-dark"
<c-input-text language="yaml"
ref="inputElement" height="250px"
v-model:value="rawYaml" :options="MONACO_EDITOR_OPTIONS"
placeholder="Paste your raw YAML here..." />
rows="20" </div>
multiline </c-label>
autocomplete="off"
autocorrect="off" <div v-if="errors.length > 0">
autocapitalize="off" <n-alert title="The following errors occured" type="error" mt-5>
spellcheck="false" <ul>
monospace <li v-for="(message, index) of errors" :key="index">
/> {{ message }}
</n-form-item> </li>
<n-form-item label="Prettified version of your YAML"> </ul>
<TextareaCopyable :value="cleanYaml" language="yaml" :follow-height-of="inputElement" /> </n-alert>
</n-form-item> </div>
<n-divider />
<n-form-item label="Prettified version of your YAML">
<TextareaCopyable :value="cleanYaml" language="yaml" />
</n-form-item>
</div>
</template> </template>
<style lang="less" scoped> <style lang="less" scoped>
.result-card { .result-card {
position: relative; position: relative;
.copy-button { .copy-button {
position: absolute; position: absolute;
top: 10px; top: 10px;
right: 10px; right: 10px;
} }
} }
</style> </style>