diff --git a/addons/api/addon/adapters/application.js b/addons/api/addon/adapters/application.js index 04a9ebe6f..1c4149511 100644 --- a/addons/api/addon/adapters/application.js +++ b/addons/api/addon/adapters/application.js @@ -263,14 +263,36 @@ export default class ApplicationAdapter extends RESTAdapter.extend( source: { pointer: '/data' }, }; // Normalize field-specific errors, if any. - const errors = fieldErrors.map((error) => ({ - detail: error.description, - // Ember Data cannot handle errors on nested attributes, so these are - // flattened (e.g. `attributes.foobar` becomes `attributes_foobar`). - // To capture these errors, add read-only fields on the model - // corresponding to the underscored field name. - source: { pointer: `/data/attributes/${error.name.replace('.', '_')}` }, - })); + const errors = fieldErrors.flatMap((error) => { + const newValue = [ + { + detail: error.description, + // Ember Data cannot handle errors on nested attributes, so these are + // flattened (e.g. `attributes.foobar` becomes `attributes_foobar`). + // To capture these errors, add read-only fields on the model + // corresponding to the underscored field name. + source: { + pointer: `/data/attributes/${error.name.replace('.', '_')}`, + }, + }, + ]; + if (error.name.includes('attributes.')) { + newValue.push({ + detail: error.description, + // Going forward, nested attributes will be hoisted. Thus "attributes." + // must be stripped from the error name key. Until all models are + // transitioned away from model fragments, however, both this and the + // prior error key mappings are necessary. + source: { + pointer: `/data/attributes/${error.name.replace( + 'attributes.', + '' + )}`, + }, + }); + } + return newValue; + }); // Return a list of JSON API errors rooted under the `errors` key. return { errors: [baseError, ...errors], diff --git a/addons/api/addon/generated/models/host-catalog.js b/addons/api/addon/generated/models/host-catalog.js index 5f4569e6a..f1f78db34 100644 --- a/addons/api/addon/generated/models/host-catalog.js +++ b/addons/api/addon/generated/models/host-catalog.js @@ -48,4 +48,10 @@ export default class GeneratedHostCatalogModel extends BaseModel { description: 'Current version number of this resource.', }) version; + + @attr({ + description: 'Plugin information for this resource.', + readOnly: true, + }) + plugin; } diff --git a/addons/api/addon/models/fragment-target-attributes.js b/addons/api/addon/models/fragment-target-attributes.js deleted file mode 100644 index 44f1a94aa..000000000 --- a/addons/api/addon/models/fragment-target-attributes.js +++ /dev/null @@ -1,9 +0,0 @@ -import Fragment from 'ember-data-model-fragments/fragment'; -import { attr } from '@ember-data/model'; - -export default class FragmentTargetAttributesModel extends Fragment { - @attr('number', { - description: 'The default port a target should use if present.', - }) - default_port; -} diff --git a/addons/api/addon/models/host-catalog.js b/addons/api/addon/models/host-catalog.js index f9ae96493..8a0a64bd5 100644 --- a/addons/api/addon/models/host-catalog.js +++ b/addons/api/addon/models/host-catalog.js @@ -2,9 +2,54 @@ import GeneratedHostCatalogModel from '../generated/models/host-catalog'; export default class HostCatalogModel extends GeneratedHostCatalogModel { /** + * Returns if the host-catalog is static type or not. * @type {boolean} */ get isStatic() { return this.type === 'static'; } + + /** + * Returns if the host-catalog is a plugin or not. + * @type {boolean} + */ + get isPlugin() { + return this.type === 'plugin'; + } + + /** + * Returns if host-catalog plugin is AWS or not + * @type {boolean} + */ + get isAWS() { + return this.compositeType === 'aws'; + } + + /** + * Return if host-catalog plugin is Azure or not. + * @type {boolean} + */ + get isAzure() { + return this.compositeType === 'azure'; + } + + /** If host-catalog is a plugins returns its name, + * otherwise returns the host-catalog type + * @type {string} + */ + get compositeType() { + return this.isPlugin ? this.plugin.name : this.type; + } + + /** + * Sets type, if type is different than static, sets plugin name to type + */ + set compositeType(type) { + if (type === 'static') { + this.type = 'static'; + } else { + this.type = 'plugin'; + this.plugin = { name: type }; + } + } } diff --git a/addons/api/addon/models/target.js b/addons/api/addon/models/target.js index 9b36a5749..65baaffea 100644 --- a/addons/api/addon/models/target.js +++ b/addons/api/addon/models/target.js @@ -1,15 +1,16 @@ import GeneratedTargetModel from '../generated/models/target'; -import { fragment, fragmentArray } from 'ember-data-model-fragments/attributes'; +import { attr } from '@ember-data/model'; +import { fragmentArray } from 'ember-data-model-fragments/attributes'; import { computed } from '@ember/object'; export default class TargetModel extends GeneratedTargetModel { // =attributes - /** - * Attributes of this resource, if any, represented as a JSON fragment. - * @type {FragmentTargetAttributesModel} - */ - @fragment('fragment-target-attributes', { defaultValue: {} }) attributes; + @attr('number', { + isNestedAttribute: true, + description: 'The default port a target should use if present.', + }) + default_port; /** * @type {[FragmentHostSourceModel]} diff --git a/addons/api/addon/serializers/application.js b/addons/api/addon/serializers/application.js index 08b59eebc..cb61f1a34 100644 --- a/addons/api/addon/serializers/application.js +++ b/addons/api/addon/serializers/application.js @@ -50,6 +50,12 @@ export default class ApplicationSerializer extends RESTSerializer { // Do not serialize `disabled` fields. // TODO: disabled is temporarily disabled if (key === 'disabled') delete json[key]; + // Push nested attributes down into the attributes key + if (options.isNestedAttribute && json[key] !== undefined) { + if (!json.attributes) json.attributes = {}; + json.attributes[key] = json[key]; + delete json[key]; + } return value; } @@ -195,9 +201,37 @@ export default class ApplicationSerializer extends RESTSerializer { * @return {Object} */ normalize(typeClass, hash) { - const normalizedHash = copy(hash, true); + let normalizedHash = copy(hash, true); const scopeID = get(normalizedHash, 'scope.id'); if (scopeID) normalizedHash.scope.scope_id = scopeID; + normalizedHash = this.normalizeNestedAttributes(typeClass, normalizedHash); return super.normalize(typeClass, normalizedHash); } + + /** + * Certain typed resources in the API have type-specific fields nested under + * an `attributes` key in the payload + * (e.g. `attributes: { fieldName: '123' }`). Since Ember Data lacks + * first-class support for nested attributes, these fields must be hoisted up + * into the main body of the payload. + * + * In order to annotate a field as being a nested attribute, add the key name + * to the `nestedAttributes` field on the serializer: + * `@attr('string', { isNestedAttribute: true }) fieldName;` + * + * @param {Model} typeClass + * @param {Object} hash + * @return {Object} + */ + normalizeNestedAttributes(typeClass, hash) { + const normalizedHash = copy(hash, true); + typeClass.attributes.forEach((attribute) => { + const { isNestedAttribute } = attribute.options; + const attributeValue = normalizedHash.attributes?.[attribute.name]; + if (isNestedAttribute && attributeValue) { + normalizedHash[attribute.name] = attributeValue; + } + }); + return normalizedHash; + } } diff --git a/addons/api/app/models/fragment-target-attributes.js b/addons/api/app/models/fragment-target-attributes.js deleted file mode 100644 index 41eb7d88f..000000000 --- a/addons/api/app/models/fragment-target-attributes.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from 'api/models/fragment-target-attributes'; diff --git a/addons/api/mirage/generated/factories/host-catalog.js b/addons/api/mirage/generated/factories/host-catalog.js index 1b2bb8af6..fa8135226 100644 --- a/addons/api/mirage/generated/factories/host-catalog.js +++ b/addons/api/mirage/generated/factories/host-catalog.js @@ -11,4 +11,13 @@ export default Factory.extend({ created_time: () => date.recent(), updated_time: () => date.recent(), disabled: () => datatype.boolean(), + plugin: function (i) { + if (this.type == 'plugin') { + return { + id: `pl_${datatype.hexaDecimal(5)}`, + name: i % 2 ? 'aws' : 'azure', + description: random.words(), + }; + } + }, }); diff --git a/addons/api/tests/unit/models/host-catalog-test.js b/addons/api/tests/unit/models/host-catalog-test.js index 48b21051d..7a8a77147 100644 --- a/addons/api/tests/unit/models/host-catalog-test.js +++ b/addons/api/tests/unit/models/host-catalog-test.js @@ -19,4 +19,74 @@ module('Unit | Model | host catalog', function (hooks) { assert.true(modelA.isStatic); assert.false(modelB.isStatic); }); + + test('it has isPlugin function and returns the expected values', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const modelPlugin = store.createRecord('host-catalog', { type: 'plugin' }); + const modelStatic = store.createRecord('host-catalog', { type: 'static' }); + assert.equal(typeof modelPlugin.isPlugin, 'boolean'); + assert.true(modelPlugin.isPlugin); + assert.false(modelStatic.isPlugin); + }); + + test('it has isAWS function and returns the expected values', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const modelAws = store.createRecord('host-catalog', { + type: 'plugin', + plugin: { name: 'aws' }, + }); + const modelRandom = store.createRecord('host-catalog', { + plugin: { name: 'random' }, + }); + assert.equal(typeof modelAws.isAWS, 'boolean'); + assert.true(modelAws.isAWS); + assert.false(modelRandom.isAWS); + }); + + test('it has isAzure function and return the expected values', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const modelAzure = store.createRecord('host-catalog', { + type: 'plugin', + plugin: { name: 'azure' }, + }); + const modelRandom = store.createRecord('host-catalog', { + plugin: { name: 'random' }, + }); + assert.equal(typeof modelAzure.isAzure, 'boolean'); + assert.true(modelAzure.isAzure); + assert.false(modelRandom.isAzure); + }); + + test('get compositeType returns expected values', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const modelPlugin = store.createRecord('host-catalog', { + type: 'plugin', + plugin: { name: 'Test name' }, + }); + const modelStatic = store.createRecord('host-catalog', { + type: 'static', + }); + assert.equal(typeof modelPlugin.compositeType, 'string'); + assert.equal(modelPlugin.compositeType, 'Test name'); + assert.equal(modelStatic.compositeType, 'static'); + }); + + test('set compositeType sets expected values', async function (assert) { + assert.expect(3); + const store = this.owner.lookup('service:store'); + const modelPlugin = store.createRecord('host-catalog', { + compositeType: 'aws', + }); + const modelStatic = store.createRecord('host-catalog', { + compositeType: 'static', + }); + + assert.equal(modelPlugin.type, 'plugin'); + assert.equal(modelPlugin.plugin.name, 'aws'); + assert.equal(modelStatic.type, 'static'); + }); }); diff --git a/addons/api/tests/unit/serializers/target-test.js b/addons/api/tests/unit/serializers/target-test.js index 0c1bd089c..d384e2cee 100644 --- a/addons/api/tests/unit/serializers/target-test.js +++ b/addons/api/tests/unit/serializers/target-test.js @@ -20,6 +20,7 @@ module('Unit | Serializer | target', function (hooks) { scope_id: 'org_1', type: 'org', }, + default_port: 1234, version: 1, type: 'tcp', }); @@ -35,7 +36,7 @@ module('Unit | Serializer | target', function (hooks) { session_max_seconds: 28800, session_connection_limit: 1, attributes: { - default_port: null, + default_port: 1234, }, }); }); @@ -123,6 +124,40 @@ module('Unit | Serializer | target', function (hooks) { }); }); + test('it normalizes records with nested attributes', function (assert) { + assert.expect(1); + const store = this.owner.lookup('service:store'); + const serializer = store.serializerFor('target'); + const targetModelClass = store.createRecord('target').constructor; + const payload = { + id: '1', + name: 'Target 1', + attributes: { + default_port: 1234, + }, + }; + const normalized = serializer.normalizeSingleResponse( + store, + targetModelClass, + payload + ); + assert.deepEqual(normalized, { + included: [], + data: { + id: '1', + type: 'target', + attributes: { + application_credential_source_ids: [], + authorized_actions: [], + host_sources: [], + name: 'Target 1', + default_port: 1234, + }, + relationships: {}, + }, + }); + }); + test('it normalizes missing host_sources and application_credential_source_ids to empty array', function (assert) { assert.expect(1); const store = this.owner.lookup('service:store'); diff --git a/addons/rose/addon/components/rose/form/radio/radio/index.hbs b/addons/rose/addon/components/rose/form/radio/radio/index.hbs index d38bc3680..edecc3ba8 100644 --- a/addons/rose/addon/components/rose/form/radio/radio/index.hbs +++ b/addons/rose/addon/components/rose/form/radio/radio/index.hbs @@ -1,4 +1,4 @@ -
+
+ {{@helperText}} + + {{/if}} {{yield}} diff --git a/addons/rose/addon/components/rose/form/radio/radio/index.js b/addons/rose/addon/components/rose/form/radio/radio/index.js index 0487f6fe1..70392d1ea 100644 --- a/addons/rose/addon/components/rose/form/radio/radio/index.js +++ b/addons/rose/addon/components/rose/form/radio/radio/index.js @@ -3,6 +3,6 @@ import { generateComponentID } from '../../../../../utilities/component-auto-id' export default class RoseFormRadioRadioComponent extends Component { // =attributes - + className = 'rose-form-radio'; id = generateComponentID(); } diff --git a/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx b/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx index 63e873862..613952aff 100644 --- a/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx +++ b/addons/rose/addon/components/rose/form/radio/radio/index.stories.mdx @@ -29,12 +29,14 @@ Renders a form radiofield. @selectedValue={{selectedValue}} @disabled={{disabled}} @icon={{icon}} + @helperText={{helperText}} /> `, context: { label: text('text', 'Radiofield'), error: boolean('error', false), value: text('value', 'value and selectedValue must match to select radiofield'), + helperText: text('helperText', 'Helper text'), selectedValue: text( 'selectedValue', 'value and selectedValue must match to select radiofield' @@ -63,6 +65,7 @@ Renders a form radiofield. @disabled={{false}} @changed={{fn}} @inline={{true}} + @helperText={{helperText}} /> ``` @@ -77,6 +80,7 @@ Renders a form radiofield. @disabled={{disabled}} @icon={{icon}} @inline={{inline}} + @helperText={{helperText}} /> `, context: { @@ -104,6 +110,7 @@ Renders a form radiofield. 'selectedValue', 'value and selectedValue must match to select radiofield' ), + helperText: text('helperText', 'Helper text'), disabled: boolean('disabled', false), icon: select( 'icon', diff --git a/addons/rose/addon/styles/rose/components/form/_radio.scss b/addons/rose/addon/styles/rose/components/form/_radio.scss index 3c94ac816..32be9a8c5 100644 --- a/addons/rose/addon/styles/rose/components/form/_radio.scss +++ b/addons/rose/addon/styles/rose/components/form/_radio.scss @@ -1,5 +1,6 @@ @use '../../variables/sizing'; @use '../../utilities/type'; +@use '../../variables/typography'; $radio-svg: ( 'default': @@ -34,9 +35,18 @@ $data-svg-prefix: 'data:image/svg+xml;utf-8,'; color: var(--ui-gray); .rose-form-radio-label { + display: flex; + align-items: center; + .rose-form-helper-text { + font-weight: normal; + margin-left: sizing.rems(xxs); + margin-bottom: 0; + } + @include type.type(s, semibold); color: currentColor; padding-left: sizing.rems(l); + padding-top: sizing.rems(xxxs); .rose-icon { margin-right: sizing.rems(xxs); diff --git a/addons/rose/package.json b/addons/rose/package.json index 1814040ea..f6ce53dfa 100644 --- a/addons/rose/package.json +++ b/addons/rose/package.json @@ -31,12 +31,12 @@ "dependencies": { "ember-cli-babel": "^7.26.8", "ember-cli-htmlbars": "^5.7.1", + "ember-composable-helpers": "^4.5.0", "ember-focus-trap": "^0.7.0", "ember-named-blocks-polyfill": "^0.2.4", "ember-radio-button": "^2.0.1", "ember-uuid": "^2.1.0", - "emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774", - "ember-composable-helpers": "^4.5.0" + "emberx-select": "github:adopted-ember-addons/emberx-select#bc5f774" }, "devDependencies": { "@babel/core": "^7.16.5", @@ -44,7 +44,7 @@ "@ember/test-helpers": "^2.4.2", "@glimmer/component": "^1.0.4", "@glimmer/tracking": "^1.0.4", - "@hashicorp/flight-icons": "^1.0.0", + "@hashicorp/flight-icons": "^2.0.1", "@storybook/addon-a11y": "^6.3.12", "@storybook/addon-actions": "^6.3.10", "@storybook/addon-docs": "^6.3.8", @@ -53,8 +53,10 @@ "@storybook/api": "^6.3.12", "@storybook/cli": "^6.3.10", "@storybook/components": "^6.3.12", + "@storybook/core-events": "^6.3.0", "@storybook/ember": "^6.3.8", "@storybook/ember-cli-storybook": "^0.4.0", + "@storybook/theming": "^6.3.0", "babel-eslint": "^10.1.0", "babel-loader": "^8.2.2", "broccoli-asset-rev": "^3.0.0", diff --git a/ui/admin/app/components/form/target/index.hbs b/ui/admin/app/components/form/target/index.hbs index efdda831f..fc8757041 100644 --- a/ui/admin/app/components/form/target/index.hbs +++ b/ui/admin/app/components/form/target/index.hbs @@ -94,7 +94,7 @@ {{#each @model as |hostCatalog|}} - {{#if hostCatalog.isStatic}} - - - {{#if (can 'read model' hostCatalog)}} - - {{hostCatalog.displayName}} - - {{else}} + + + {{#if (can 'read model' hostCatalog)}} + {{hostCatalog.displayName}} - {{/if}} -

{{hostCatalog.description}}

-
- + + {{else}} + {{hostCatalog.displayName}} + {{/if}} +

{{hostCatalog.description}}

+
+ + {{#if hostCatalog.isStatic}} {{t (concat 'resources.host-catalog.types.' hostCatalog.type) }} - - - - {{hostCatalog.id}} - - -
- {{/if}} + {{hostCatalog.compositeType}} + + {{/if}} + + + + {{hostCatalog.id}} + + + {{/each}}
diff --git a/yarn.lock b/yarn.lock index 2fdf55c4c..6618670af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3449,10 +3449,10 @@ resolved "https://registry.yarnpkg.com/@handlebars/parser/-/parser-1.1.0.tgz#d6dbc7574774b238114582410e8fee0dc3532bdf" integrity sha512-rR7tJoSwJ2eooOpYGxGGW95sLq6GXUaS1UtWvN7pei6n2/okYvCGld9vsUTvkl2migxbkszsycwtMf/GEc1k1A== -"@hashicorp/flight-icons@^1.0.0": - version "1.0.0" - resolved "https://registry.yarnpkg.com/@hashicorp/flight-icons/-/flight-icons-1.0.0.tgz#488ec6c3aaa8702d7878274abb8a5352eae1f6a2" - integrity sha512-LehZZOxbHZmVLzF6fy2w1LqJ5jQv+QXCfGlb7oToBc3u6YPxlIb3S1+bKQJIXzn7TELFRjRndnCtuYYXFO90Ng== +"@hashicorp/flight-icons@^2.0.1": + version "2.0.1" + resolved "https://registry.yarnpkg.com/@hashicorp/flight-icons/-/flight-icons-2.0.1.tgz#7e7370f1d45207409dd20ebdce7285370e7d9532" + integrity sha512-445WEmL0ysOeVQDe2khCqGbmNm+/Rv5B35Q8FXvc+RuE+r9mNWL0ufe2cVa8Sqqt/VRg0lyTEhArNX9gYCX1Yg== "@humanwhocodes/config-array@^0.5.0": version "0.5.0" @@ -4324,6 +4324,14 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/client-logger@6.4.13": + version "6.4.13" + resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.13.tgz#2467ee13c7f85e9f6c0d9cd64c11ee7a109b060a" + integrity sha512-VPrrgJRURztXAKTeHOpzKMAHnNupkGApUDNlPIs0Qxyn5gaSiy806q4XPoROno3mVgEe+7Chf86hRiL8pJnlCA== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/codemod@6.3.10": version "6.3.10" resolved "https://registry.yarnpkg.com/@storybook/codemod/-/codemod-6.3.10.tgz#aad0ee5ca8ef59b4d134ff45e213e198d64c9974" @@ -4538,6 +4546,13 @@ dependencies: core-js "^3.8.2" +"@storybook/core-events@^6.3.0": + version "6.4.13" + resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.13.tgz#caf1adafd743f8d53a151bc402449f841896cb9c" + integrity sha512-zNlzNv7qVXjLf7yfvY9KfLvDY8nVskxrjmz0+21rIqUefS9+7SWBrtJJURpCaoPf/BmACqh/6c1RnuOY7TESnw== + dependencies: + core-js "^3.8.2" + "@storybook/core-server@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.3.8.tgz#0d141e6f315b735a442aebc0f424f911456c769c" @@ -4893,6 +4908,24 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@storybook/theming@^6.3.0": + version "6.4.13" + resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.13.tgz#1a8aadeddb6c3a115f739aa1a4bbd7e65d4a3830" + integrity sha512-oWRoNnvO4QnRnplZ74DVdU4k91eqw8y0Xqn6lzZBeC8hq6mYWldgfj1LZ24gJhVtEIa7ZKoyujGUygHaH8WXHw== + dependencies: + "@emotion/core" "^10.1.1" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.27" + "@storybook/client-logger" "6.4.13" + core-js "^3.8.2" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.27" + global "^4.4.0" + memoizerific "^1.11.3" + polished "^4.0.5" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/ui@6.3.8": version "6.3.8" resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.3.8.tgz#9fb71befbe76f06c478925610ce998c23260a3f3" @@ -8141,9 +8174,9 @@ caniuse-api@^3.0.0: lodash.uniq "^4.5.0" caniuse-lite@^1.0.0, caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001157, caniuse-lite@^1.0.30001286: - version "1.0.30001295" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001295.tgz#68a60f8f0664f342b2835c5d8898b4faea7b3d51" - integrity sha512-lSP16vcyC0FEy0R4ECc9duSPoKoZy+YkpGkue9G4D81OfPnliopaZrU10+qtPdT8PbGXad/PNx43TIQrOmJZSQ== + version "1.0.30001300" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001300.tgz#11ab6c57d3eb6f964cba950401fd00a146786468" + integrity sha512-cVjiJHWGcNlJi8TZVKNMnvMid3Z3TTdDHmLDzlOdIiZq138Exvo0G+G0wTdVYolxKb4AYwC+38pxodiInVtJSA== capture-exit@^2.0.0: version "2.0.0" @@ -13628,11 +13661,16 @@ got@^9.6.0: to-readable-stream "^1.0.0" url-parse-lax "^3.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.2, graceful-fs@^4.2.3, graceful-fs@^4.2.4: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.3, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0, graceful-fs@^4.2.3, graceful-fs@^4.2.4: version "4.2.8" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a" integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg== +graceful-fs@^4.2.2: + version "4.2.9" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" + integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -14276,9 +14314,9 @@ ignore@^5.1.1, ignore@^5.1.4, ignore@^5.1.8: integrity sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw== immer@8.0.1, immer@^9.0.6: - version "9.0.7" - resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.7.tgz#b6156bd7db55db7abc73fd2fdadf4e579a701075" - integrity sha512-KGllzpbamZDvOIxnmJ0jI840g7Oikx58lBPWV0hUh7dtAyZpFqqrBZdKka5GlTwMTZ1Tjc/bKKW4VSFAt6BqMA== + version "9.0.12" + resolved "https://registry.yarnpkg.com/immer/-/immer-9.0.12.tgz#2d33ddf3ee1d247deab9d707ca472c8c942a0f20" + integrity sha512-lk7UNmSbAukB5B6dh9fnh5D0bJTOFKxVg2cyJWTYrWRfhLrLMBquONcUs3aFq507hNoIZEDDh8lb8UtOizSMhA== import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" @@ -19133,9 +19171,9 @@ printf@^0.6.1: integrity sha512-is0ctgGdPJ5951KulgfzvHGwJtZ5ck8l042vRkV6jrkpBzTmb/lueTqguWHy2JfVA+RY6gFVlaZgUS0j7S/dsw== prismjs@^1.21.0, prismjs@^1.25.0, prismjs@~1.24.0: - version "1.25.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.25.0.tgz#6f822df1bdad965734b310b315a23315cf999756" - integrity sha512-WCjJHl1KEWbnkQom1+SzftbtXMKQoezOCYs5rECqMN+jP+apI7ftoflyqigqzopSO3hMhTEb0mFClA8lkolgEg== + version "1.26.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.26.0.tgz#16881b594828bb6b45296083a8cbab46b0accd47" + integrity sha512-HUoH9C5Z3jKkl3UunCyiD5jwk0+Hz0fIgQ2nbwU2Oo/ceuTAQAg+pPVnfdt2TJWRVLcxKh9iuoYDUSc8clb5UQ== private@^0.1.8: version "0.1.8"