Merge branch 'main' into refactor-render-template-auth-methods

This commit is contained in:
Randall Morey authored and GitHub committed 2022-01-20 10:37:55 -05:00
commit 2595ea7f2e
18 files changed
+356 -73

No files matched your search

+30 -8
View File
@@ -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],
@@ -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;
}
@@ -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;
}
+45
View File
@@ -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 };
}
}
}
+7 -6
View File
@@ -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]}
+35 -1
View File
@@ -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;
}
}
@@ -1 +0,0 @@
export { default } from 'api/models/fragment-target-attributes';
@@ -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(),
};
}
},
});
@@ -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');
});
});
@@ -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');
@@ -1,4 +1,4 @@
<div class="rose-form-radio{{if @error " error"}} {{if @inline "inline"}}">
<div class="{{this.className}} {{if @error " error"}} {{if @inline "inline"}}">
<RadioButton
...attributes
@@ -22,7 +22,11 @@
{{/if}}
{{@label}}
{{#if @helperText}}
<Rose::Form::HelperText @id="helper-text-{{this.id}}" @error={{@error}} @link={{@link}} @linkText={{@linkText}}>
{{@helperText}}
</Rose::Form::HelperText>
{{/if}}
</label>
{{yield}}
@@ -3,6 +3,6 @@ import { generateComponentID } from '../../../../../utilities/component-auto-id'
export default class RoseFormRadioRadioComponent extends Component {
// =attributes
className = 'rose-form-radio';
id = generateComponentID();
}
@@ -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}}
/>
<Rose::Form::Radio::Radio
@label='Radiofield2'
@@ -86,6 +90,7 @@ Renders a form radiofield.
@disabled={{disabled}}
@icon={{icon}}
@inline={{inline}}
@helperText={{helperText}}
/>
<Rose::Form::Radio::Radio
@label='Radiofield3'
@@ -95,6 +100,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',
@@ -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);
+5 -3
View File
@@ -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",
@@ -94,7 +94,7 @@
<form.input
@name='default_port'
@type='number'
@value={{@model.attributes.default_port}}
@value={{@model.default_port}}
@label={{t 'form.default_port.label'}}
@error={{@model.errors.default_port}}
@helperText={{t 'form.default_port.help'}}
@@ -41,39 +41,49 @@
</table.header>
<table.body as |body|>
{{#each @model as |hostCatalog|}}
{{#if hostCatalog.isStatic}}
<body.row as |row|>
<row.headerCell>
{{#if (can 'read model' hostCatalog)}}
<LinkTo
@route='scopes.scope.host-catalogs.host-catalog'
@model={{hostCatalog.id}}
>
{{hostCatalog.displayName}}
</LinkTo>
{{else}}
<body.row as |row|>
<row.headerCell>
{{#if (can 'read model' hostCatalog)}}
<LinkTo
@route='scopes.scope.host-catalogs.host-catalog'
@model={{hostCatalog.id}}
>
{{hostCatalog.displayName}}
{{/if}}
<p>{{hostCatalog.description}}</p>
</row.headerCell>
<row.cell>
</LinkTo>
{{else}}
{{hostCatalog.displayName}}
{{/if}}
<p>{{hostCatalog.description}}</p>
</row.headerCell>
<row.cell>
{{#if hostCatalog.isStatic}}
<Rose::Badge>
{{t
(concat 'resources.host-catalog.types.' hostCatalog.type)
}}
</Rose::Badge>
</row.cell>
<row.cell>
<Copyable
@text={{hostCatalog.id}}
@buttonText={{t 'actions.copy-to-clipboard'}}
@acknowledgeText={{t 'states.copied'}}
{{else}}
<Rose::Badge
@icon={{concat
'flight-icons/svg/'
hostCatalog.compositeType
'-color-16'
}}
>
<code>{{hostCatalog.id}}</code>
</Copyable>
</row.cell>
</body.row>
{{/if}}
{{hostCatalog.compositeType}}
</Rose::Badge>
{{/if}}
</row.cell>
<row.cell>
<Copyable
@text={{hostCatalog.id}}
@buttonText={{t 'actions.copy-to-clipboard'}}
@acknowledgeText={{t 'states.copied'}}
>
<code>{{hostCatalog.id}}</code>
</Copyable>
</row.cell>
</body.row>
{{/each}}
</table.body>
</Rose::Table>
+52 -14
View File
@@ -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"