diff --git a/.pnp.cjs b/.pnp.cjs index 1c999ddfc..ad133e11f 100755 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -6195,7 +6195,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["mssql", "npm:6.3.1"], ["mysql", "npm:2.18.1"], ["openid-client", "npm:4.6.0"], - ["oracledb", "npm:5.1.0"], ["pg", "virtual:dddca670fd0b7758fb2e1b1a3e18ac7ebd1ecd06ecdd7acec2b78bccf1d35802cb22904bfbb233b16515a81f5cb819421786d20887823d98022b367036c1ad51#npm:8.5.1"], ["saslprep", "npm:1.0.3"], ["sqlite3", "virtual:dddca670fd0b7758fb2e1b1a3e18ac7ebd1ecd06ecdd7acec2b78bccf1d35802cb22904bfbb233b16515a81f5cb819421786d20887823d98022b367036c1ad51#npm:5.0.2"], @@ -20738,7 +20737,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ["mysql", "npm:2.18.1"], ["opener", "npm:1.5.2"], ["ora", "npm:5.3.0"], - ["oracledb", "npm:5.1.0"], ["pg", "virtual:dddca670fd0b7758fb2e1b1a3e18ac7ebd1ecd06ecdd7acec2b78bccf1d35802cb22904bfbb233b16515a81f5cb819421786d20887823d98022b367036c1ad51#npm:8.5.1"], ["react", "npm:17.0.2"], ["react-dom", "virtual:22157ea722f8d6428f1fcf0a6f7f6c7d6b902d9c785256c60a65fe6cd0db76ebccc7c1457ee047df0ba6909ff018e300c4f4957a60f5b670089810dfc417af9b#npm:17.0.2"], @@ -22908,15 +22906,6 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { "linkType": "HARD", }] ]], - ["oracledb", [ - ["npm:5.1.0", { - "packageLocation": "./.yarn/unplugged/oracledb-npm-5.1.0-5af4450ffa/node_modules/oracledb/", - "packageDependencies": [ - ["oracledb", "npm:5.1.0"] - ], - "linkType": "HARD", - }] - ]], ["original", [ ["npm:1.0.2", { "packageLocation": "./.yarn/cache/original-npm-1.0.2-2250635ba0-6918b9d454.zip/node_modules/original/", diff --git a/.yarn/cache/oracledb-npm-5.1.0-5af4450ffa-c4dd2c6ac0.zip b/.yarn/cache/oracledb-npm-5.1.0-5af4450ffa-c4dd2c6ac0.zip deleted file mode 100644 index 523c6bd0b..000000000 Binary files a/.yarn/cache/oracledb-npm-5.1.0-5af4450ffa-c4dd2c6ac0.zip and /dev/null differ diff --git a/packages/blocks/blocksAntd/demo/examples/PageHeaderMenu.yaml b/packages/blocks/blocksAntd/demo/examples/PageHeaderMenu.yaml index e8e2f0bf6..b3dd9293a 100644 --- a/packages/blocks/blocksAntd/demo/examples/PageHeaderMenu.yaml +++ b/packages/blocks/blocksAntd/demo/examples/PageHeaderMenu.yaml @@ -1,6 +1,6 @@ - id: default type: PageHeaderMenu -- id: "properties.menu" +- id: 'properties.menu' type: PageHeaderMenu properties: menu: @@ -17,11 +17,11 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.logo.src" +- id: 'properties.logo.src' type: PageHeaderMenu properties: logo: - src: "https://lowdefy.com/logos/name_250.png" + src: 'https://lowdefy.com/logos/name_250.png' menu: links: - id: Introduction @@ -36,11 +36,11 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.logo.alt" +- id: 'properties.logo.alt' type: PageHeaderMenu properties: logo: - alt: "Header logo alt text" + alt: 'Header logo alt text' menu: links: - id: Introduction @@ -55,17 +55,55 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.logo.style" +- id: 'properties.logo.style' type: PageHeaderMenu properties: logo: - style: + style: border: 5px solid blue -- id: "properties.header.color" +- id: 'properties.logo.srcset' + type: PageHeaderMenu + properties: + logo: + srcset: 'https://lowdefy.com/logos/name_250.png 250w, https://lowdefy.com/logos/name_450.png 450w' + menu: + links: + - id: Introduction + type: MenuLink + pageId: introduction + properties: + icon: RocketOutlined + title: Introduction + - id: alert + type: MenuLink + pageId: Alert + properties: + icon: ExclamationCircleOutlined + title: Alert +- id: 'properties.logo.size' + type: PageHeaderMenu + properties: + logo: + srcset: '(max-width: 767px) 40px, 768px' + menu: + links: + - id: Introduction + type: MenuLink + pageId: introduction + properties: + icon: RocketOutlined + title: Introduction + - id: alert + type: MenuLink + pageId: Alert + properties: + icon: ExclamationCircleOutlined + title: Alert +- id: 'properties.header.color' type: PageHeaderMenu properties: header: - color: "#3f51b5" + color: '#3f51b5' menu: links: - id: Introduction @@ -80,7 +118,7 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.header.theme: light" +- id: 'properties.header.theme: light' type: PageHeaderMenu properties: header: @@ -99,42 +137,42 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.breadcrumb.list" +- id: 'properties.breadcrumb.list' type: PageHeaderMenu properties: breadcrumb: - list: + list: - Link one - Link two -- id: "properties.footer.style" +- id: 'properties.footer.style' type: PageHeaderMenu properties: footer: - style: + style: border: 5px solid blue areas: footer: blocks: [] -- id: "properties.content.style" +- id: 'properties.content.style' type: PageHeaderMenu properties: content: - style: + style: border: 5px solid blue areas: content: blocks: [] -- id: "properties.header.style" +- id: 'properties.header.style' type: PageHeaderMenu properties: header: - style: + style: border: 5px solid blue areas: header: blocks: [] -- id: "properties.style" +- id: 'properties.style' type: PageHeaderMenu properties: - style: - border: 5px solid blue \ No newline at end of file + style: + border: 5px solid blue diff --git a/packages/blocks/blocksAntd/demo/examples/PageSiderMenu.yaml b/packages/blocks/blocksAntd/demo/examples/PageSiderMenu.yaml index e0e1c9f31..2af669be2 100644 --- a/packages/blocks/blocksAntd/demo/examples/PageSiderMenu.yaml +++ b/packages/blocks/blocksAntd/demo/examples/PageSiderMenu.yaml @@ -1,6 +1,6 @@ - id: default type: PageSiderMenu -- id: "properties.menu" +- id: 'properties.menu' type: PageSiderMenu properties: menu: @@ -17,11 +17,11 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.logo.src" +- id: 'properties.logo.src' type: PageHeaderMenu properties: logo: - src: "https://lowdefy.com/logos/name_250.png" + src: 'https://lowdefy.com/logos/name_250.png' menu: links: - id: Introduction @@ -36,11 +36,11 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.logo.alt" +- id: 'properties.logo.alt' type: PageHeaderMenu properties: logo: - alt: "Header logo alt text" + alt: 'Header logo alt text' menu: links: - id: Introduction @@ -55,17 +55,55 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.logo.style" +- id: 'properties.logo.style' type: PageHeaderMenu properties: logo: - style: + style: border: 5px solid blue -- id: "properties.header.color" +- id: 'properties.logo.srcset' + type: PageHeaderMenu + properties: + logo: + srcset: 'https://lowdefy.com/logos/name_250.png 250w, https://lowdefy.com/logos/name_450.png 450w' + menu: + links: + - id: Introduction + type: MenuLink + pageId: introduction + properties: + icon: RocketOutlined + title: Introduction + - id: alert + type: MenuLink + pageId: Alert + properties: + icon: ExclamationCircleOutlined + title: Alert +- id: 'properties.logo.size' + type: PageHeaderMenu + properties: + logo: + srcset: '(max-width: 767px) 40px, 768px' + menu: + links: + - id: Introduction + type: MenuLink + pageId: introduction + properties: + icon: RocketOutlined + title: Introduction + - id: alert + type: MenuLink + pageId: Alert + properties: + icon: ExclamationCircleOutlined + title: Alert +- id: 'properties.header.color' type: PageSiderMenu properties: header: - color: "#3f51b5" + color: '#3f51b5' menu: links: - id: Introduction @@ -80,11 +118,11 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.sider.color" +- id: 'properties.sider.color' type: PageSiderMenu properties: sider: - color: "#faf7a5" + color: '#faf7a5' menu: links: - id: Introduction @@ -99,13 +137,13 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.menu.selectedColor" +- id: 'properties.menu.selectedColor' type: PageSiderMenu properties: sider: - color: "#faf7a5" + color: '#faf7a5' menu: - selectedColor: "#a30331" + selectedColor: '#a30331' links: - id: Introduction type: MenuLink @@ -119,7 +157,7 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.header.theme: light" +- id: 'properties.header.theme: light' type: PageSiderMenu properties: header: @@ -138,41 +176,41 @@ properties: icon: ExclamationCircleOutlined title: Alert -- id: "properties.breadcrumb.list" +- id: 'properties.breadcrumb.list' type: PageHeaderMenu properties: breadcrumb: - list: + list: - Link one - Link two -- id: "properties.footer.style" +- id: 'properties.footer.style' type: PageHeaderMenu properties: footer: - style: + style: border: 5px solid blue areas: footer: blocks: [] -- id: "properties.content.style" +- id: 'properties.content.style' type: PageHeaderMenu properties: content: - style: + style: border: 5px solid blue areas: content: blocks: [] -- id: "properties.sider.style" +- id: 'properties.sider.style' type: PageHeaderMenu properties: sider: - style: + style: border: 5px solid blue areas: sider: blocks: [] -- id: "properties.sider.initialCollapsed" +- id: 'properties.sider.initialCollapsed' type: PageHeaderMenu properties: sider: @@ -180,12 +218,12 @@ areas: sider: blocks: [] -- id: "properties.sider.theme: dark" +- id: 'properties.sider.theme: dark' type: PageHeaderMenu properties: sider: theme: dark -- id: "properties.toggleSiderButton.type" +- id: 'properties.toggleSiderButton.type' type: PageHeaderMenu properties: toggleSiderButton: @@ -193,7 +231,7 @@ areas: sider: blocks: [] -- id: "properties.toggleSiderButton.hide" +- id: 'properties.toggleSiderButton.hide' type: PageHeaderMenu properties: toggleSiderButton: @@ -201,17 +239,17 @@ areas: sider: blocks: [] -- id: "properties.header.style" +- id: 'properties.header.style' type: PageHeaderMenu properties: header: - style: + style: border: 5px solid blue areas: header: blocks: [] -- id: "properties.style" +- id: 'properties.style' type: PageHeaderMenu properties: - style: - border: 5px solid blue \ No newline at end of file + style: + border: 5px solid blue diff --git a/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.js b/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.js index 05aa927d0..0bccbe047 100644 --- a/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.js +++ b/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.js @@ -56,12 +56,10 @@ const PageHeaderMenu = ({ justifyContent: 'flex-end', }, logo: { - width: 130, margin: '0px 30px', flex: '0 1 auto', sm: { margin: '0 10px' }, md: { margin: '0 15px' }, - lg: { margin: '0 30px' }, }, lgMenu: { flex: '1 1 auto', @@ -124,8 +122,28 @@ const PageHeaderMenu = ({ ? '/public/logo-light-theme.png' : '/public/logo-dark-theme.png') } + srcset={ + (properties.logo && (properties.logo.srcset || properties.logo.src)) || + (get(properties, 'header.theme') === 'light' + ? '/public/logo-square-light-theme.png 40w, /public/logo-light-theme.png 768w' + : '/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w') + } + sizes={ + (properties.logo && properties.logo.sizes) || + '(max-width: 767px) 40px, 768px' + } alt={(properties.logo && properties.logo.alt) || 'Lowdefy'} className={methods.makeCssClass([ + { + width: 130, + sm: { + width: + properties.logo && properties.logo.src && !properties.logo.srcset + ? 130 + : 40, + }, + md: { width: 130 }, + }, styles.logo, properties.logo && properties.logo.style, ])} diff --git a/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.json b/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.json index 136253261..9b7781ac2 100644 --- a/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.json +++ b/packages/blocks/blocksAntd/src/blocks/PageHeaderMenu/PageHeaderMenu.json @@ -34,6 +34,14 @@ "type": "string", "description": "Header logo source url." }, + "srcset": { + "type": "string", + "description": "Header logo srcset for logo img element." + }, + "size": { + "type": "string", + "description": "Header logo size for logo img element." + }, "alt": { "type": "string", "default": "Lowdefy", diff --git a/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.js b/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.js index 171161b80..5c359296e 100644 --- a/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.js +++ b/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.js @@ -71,7 +71,6 @@ const PageSiderMenu = ({ justifyContent: 'flex-end', }, logo: { - width: 130, margin: '0 30px 0 0', flex: '0 1 auto', sm: { margin: '0 10px 0 0' }, @@ -169,8 +168,28 @@ const PageSiderMenu = ({ ? '/public/logo-light-theme.png' : '/public/logo-dark-theme.png') } + srcset={ + (properties.logo && (properties.logo.srcset || properties.logo.src)) || + (get(properties, 'header.theme') === 'light' + ? '/public/logo-square-light-theme.png 40w, /public/logo-light-theme.png 768w' + : '/public/logo-square-dark-theme.png 40w, /public/logo-dark-theme.png 768w') + } + sizes={ + (properties.logo && properties.logo.sizes) || + '(max-width: 767px) 40px, 768px' + } alt={(properties.logo && properties.logo.alt) || 'Lowdefy'} className={methods.makeCssClass([ + { + width: 130, + sm: { + width: + properties.logo && properties.logo.src && !properties.logo.srcset + ? 130 + : 40, + }, + md: { width: 130 }, + }, styles.logo, properties.logo && properties.logo.style, ])} diff --git a/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.json b/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.json index 0872bdad1..6d3fa8e17 100644 --- a/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.json +++ b/packages/blocks/blocksAntd/src/blocks/PageSiderMenu/PageSiderMenu.json @@ -44,6 +44,14 @@ "type": "string", "description": "Header logo source url." }, + "srcset": { + "type": "string", + "description": "Header logo srcset for logo img element." + }, + "size": { + "type": "string", + "description": "Header logo size for logo img element." + }, "alt": { "type": "string", "default": "Lowdefy", diff --git a/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.mock.test.js.snap b/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.mock.test.js.snap index 74ccffaf7..9d41e0cba 100644 --- a/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.mock.test.js.snap +++ b/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.mock.test.js.snap @@ -871,6 +871,110 @@ Array [ ] `; +exports[`Mock render - properties.logo.size - value[0] - default 1`] = ` +Array [ + Array [ + Object { + "children": + + + , + "className": "css-vooagt", + "id": "properties.logo.size", + }, + Object {}, + ], +] +`; + exports[`Mock render - properties.logo.src - value[0] - default 1`] = ` Array [ Array [ @@ -975,6 +1079,110 @@ Array [ ] `; +exports[`Mock render - properties.logo.srcset - value[0] - default 1`] = ` +Array [ + Array [ + Object { + "children": + + + , + "className": "css-vooagt", + "id": "properties.logo.srcset", + }, + Object {}, + ], +] +`; + exports[`Mock render - properties.logo.style - value[0] - default 1`] = ` Array [ Array [ diff --git a/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.test.js.snap b/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.test.js.snap index 49ab7884c..e2d969a3b 100644 --- a/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.test.js.snap +++ b/packages/blocks/blocksAntd/tests/__snapshots__/PageHeaderMenu.test.js.snap @@ -32,10 +32,18 @@ exports[`Test Schema properties.logo.alt 1`] = `true`; exports[`Test Schema properties.logo.alt 2`] = `null`; +exports[`Test Schema properties.logo.size 1`] = `true`; + +exports[`Test Schema properties.logo.size 2`] = `null`; + exports[`Test Schema properties.logo.src 1`] = `true`; exports[`Test Schema properties.logo.src 2`] = `null`; +exports[`Test Schema properties.logo.srcset 1`] = `true`; + +exports[`Test Schema properties.logo.srcset 2`] = `null`; + exports[`Test Schema properties.logo.style 1`] = `true`; exports[`Test Schema properties.logo.style 2`] = `null`; diff --git a/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.mock.test.js.snap b/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.mock.test.js.snap index 19f69071c..893dc130d 100644 --- a/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.mock.test.js.snap +++ b/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.mock.test.js.snap @@ -723,6 +723,96 @@ Array [ ] `; +exports[`Mock render - properties.logo.size - value[0] - default 1`] = ` +Array [ + Array [ + Object { + "children": + + + , + "className": "css-vooagt", + "id": "properties.logo.size", + }, + Object {}, + ], +] +`; + exports[`Mock render - properties.logo.src - value[0] - default 1`] = ` Array [ Array [ @@ -813,6 +903,96 @@ Array [ ] `; +exports[`Mock render - properties.logo.srcset - value[0] - default 1`] = ` +Array [ + Array [ + Object { + "children": + + + , + "className": "css-vooagt", + "id": "properties.logo.srcset", + }, + Object {}, + ], +] +`; + exports[`Mock render - properties.logo.style - value[0] - default 1`] = ` Array [ Array [ diff --git a/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.test.js.snap b/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.test.js.snap index bf1c6bd54..2bc09c58b 100644 --- a/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.test.js.snap +++ b/packages/blocks/blocksAntd/tests/__snapshots__/PageSiderMenu.test.js.snap @@ -55,8 +55,10 @@ exports[`Render default - value[0] 1`] = ` > Lowdefy @@ -228,8 +230,10 @@ exports[`Render properties.breadcrumb.list - value[0] 1`] = ` > Lowdefy @@ -435,8 +439,10 @@ exports[`Render properties.content.style - value[0] 1`] = ` > Lowdefy @@ -608,8 +614,10 @@ exports[`Render properties.footer.style - value[0] 1`] = ` > Lowdefy @@ -796,8 +804,10 @@ exports[`Render properties.header.color - value[0] 1`] = ` > Lowdefy @@ -1066,8 +1076,10 @@ exports[`Render properties.header.style - value[0] 1`] = ` > Lowdefy @@ -1239,8 +1251,10 @@ exports[`Render properties.header.theme: light - value[0] 1`] = ` > Lowdefy @@ -1499,8 +1513,10 @@ exports[`Render properties.logo.alt - value[0] 1`] = ` > Header logo alt text @@ -1704,6 +1720,268 @@ exports[`Render properties.logo.alt - value[0] 1`] = ` `; +exports[`Render properties.logo.size - value[0] 1`] = ` +
+
+
+
+
+ +
+
+
+ + Lowdefy + +
+
+ +
+
+
+ content +
+
+
+
+`; + exports[`Render properties.logo.src - value[0] 1`] = `
Lowdefy @@ -1964,6 +2244,268 @@ exports[`Render properties.logo.src - value[0] 1`] = `
`; +exports[`Render properties.logo.srcset - value[0] 1`] = ` +
+
+
+
+
+ +
+
+
+ + Lowdefy + +
+
+ +
+
+
+ content +
+
+
+
+`; + exports[`Render properties.logo.style - value[0] 1`] = `
Lowdefy @@ -2192,8 +2736,10 @@ exports[`Render properties.menu - value[0] 1`] = ` > Lowdefy @@ -2452,8 +2998,10 @@ exports[`Render properties.menu.selectedColor - value[0] 1`] = ` > Lowdefy @@ -2712,8 +3260,10 @@ exports[`Render properties.sider.color - value[0] 1`] = ` > Lowdefy @@ -2972,8 +3522,10 @@ exports[`Render properties.sider.initialCollapsed - value[0] 1`] = ` > Lowdefy @@ -3156,8 +3708,10 @@ exports[`Render properties.sider.style - value[0] 1`] = ` > Lowdefy @@ -3340,8 +3894,10 @@ exports[`Render properties.sider.theme: dark - value[0] 1`] = ` > Lowdefy @@ -3513,8 +4069,10 @@ exports[`Render properties.style - value[0] 1`] = ` > Lowdefy @@ -3686,8 +4244,10 @@ exports[`Render properties.toggleSiderButton.hide - value[0] 1`] = ` > Lowdefy @@ -3870,8 +4430,10 @@ exports[`Render properties.toggleSiderButton.type - value[0] 1`] = ` > Lowdefy @@ -4031,10 +4593,18 @@ exports[`Test Schema properties.logo.alt 1`] = `true`; exports[`Test Schema properties.logo.alt 2`] = `null`; +exports[`Test Schema properties.logo.size 1`] = `true`; + +exports[`Test Schema properties.logo.size 2`] = `null`; + exports[`Test Schema properties.logo.src 1`] = `true`; exports[`Test Schema properties.logo.src 2`] = `null`; +exports[`Test Schema properties.logo.srcset 1`] = `true`; + +exports[`Test Schema properties.logo.srcset 2`] = `null`; + exports[`Test Schema properties.logo.style 1`] = `true`; exports[`Test Schema properties.logo.style 2`] = `null`; diff --git a/packages/build/src/lowdefySchema.json b/packages/build/src/lowdefySchema.json index 9f2f0237d..010a48d33 100644 --- a/packages/build/src/lowdefySchema.json +++ b/packages/build/src/lowdefySchema.json @@ -54,17 +54,9 @@ "type": "App \"config.auth.openId.rolesField\" should be a string." } }, - "logoutFromProvider": { - "type": "boolean", - "default": false, - "description": "If true, the user will be directed to OpenID Connect provider's logout URL. This will log the user out of the provider.", - "errorMessage": { - "type": "App \"config.auth.openId.logoutFromProvider\" should be a boolean." - } - }, "logoutRedirectUri": { "type": "string", - "description": "The URI to redirect the user to after logout.", + "description": "The URI to redirect the user to after logout. Can be a Nunjucks template string with client_id, host, id_token_hint, and openid_domain as template data.", "errorMessage": { "type": "App \"config.auth.openId.logoutRedirectUri\" should be a string." } diff --git a/packages/cli/package.json b/packages/cli/package.json index 8d8f320a5..44bc5f95f 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -59,7 +59,6 @@ "mysql": "2.18.1", "opener": "1.5.2", "ora": "5.3.0", - "oracledb": "5.1.0", "pg": "8.5.1", "reload": "3.1.1", "saslprep": "1.0.3", diff --git a/packages/docs/menus.yaml b/packages/docs/menus.yaml index ccbc80f75..3c49fb67d 100644 --- a/packages/docs/menus.yaml +++ b/packages/docs/menus.yaml @@ -419,11 +419,11 @@ - id: MySQL type: MenuLink pageId: MySQL - - id: OracleDB - type: MenuLink - pageId: OracleDB - properties: - title: Oracle Database + # - id: OracleDB + # type: MenuLink + # pageId: OracleDB + # properties: + # title: Oracle Database - id: PostgreSQL type: MenuLink pageId: PostgreSQL diff --git a/packages/docs/pages.yaml b/packages/docs/pages.yaml index 730007ea0..b0f98e527 100644 --- a/packages/docs/pages.yaml +++ b/packages/docs/pages.yaml @@ -115,7 +115,7 @@ - _ref: connections/MongoDB.yaml - _ref: connections/MSSQL.yaml - _ref: connections/MySQL.yaml -- _ref: connections/OracleDB.yaml +# - _ref: connections/OracleDB.yaml - _ref: connections/PostgreSQL.yaml - _ref: connections/SendGridMail.yaml - _ref: connections/SQLite.yaml diff --git a/packages/graphql/package.json b/packages/graphql/package.json index d4d4548a3..aca1bae7b 100644 --- a/packages/graphql/package.json +++ b/packages/graphql/package.json @@ -62,7 +62,6 @@ "mssql": "6.3.1", "mysql": "2.18.1", "openid-client": "4.6.0", - "oracledb": "5.1.0", "pg": "8.5.1", "saslprep": "1.0.3", "sqlite3": "5.0.2" diff --git a/packages/graphql/src/controllers/openIdController.js b/packages/graphql/src/controllers/openIdController.js index 948313bc6..e8b3b2595 100644 --- a/packages/graphql/src/controllers/openIdController.js +++ b/packages/graphql/src/controllers/openIdController.js @@ -14,7 +14,8 @@ limitations under the License. */ -import { get } from '@lowdefy/helpers'; +import { get, type } from '@lowdefy/helpers'; +import { nunjucksFunction } from '@lowdefy/nunjucks'; import { Issuer } from 'openid-client'; import cookie from 'cookie'; @@ -22,13 +23,12 @@ import { AuthenticationError, ConfigurationError } from '../context/errors'; class OpenIdController { constructor({ development, getController, getLoader, getSecrets, gqlUri, host, setHeader }) { - const httpPrefix = development ? 'http' : 'https'; - + this.httpPrefix = development ? 'http' : 'https'; this.development = development; this.componentLoader = getLoader('component'); this.getSecrets = getSecrets; this.host = host; - this.redirectUri = `${httpPrefix}://${host}/auth/openid-callback`; + this.redirectUri = `${this.httpPrefix}://${this.host}/auth/openid-callback`; this.gqlUri = gqlUri || '/api/graphql'; this.setHeader = setHeader; this.tokenController = getController('token'); @@ -63,7 +63,7 @@ class OpenIdController { }); } - async authorizationUrl({ input, pageId, urlQuery }) { + async authorizationUrl({ authUrlQueryParams, input, pageId, urlQuery }) { try { const config = await this.getOpenIdConfig(); if (!config) return null; @@ -73,15 +73,16 @@ class OpenIdController { pageId, urlQuery, }); - return this.getAuthorizationUrl({ config, state }); + return this.getAuthorizationUrl({ authUrlQueryParams, config, state }); } catch (error) { throw new ConfigurationError(error); } } - async getAuthorizationUrl({ config, state }) { + async getAuthorizationUrl({ authUrlQueryParams, config, state }) { const client = await this.getClient({ config }); const url = client.authorizationUrl({ + ...authUrlQueryParams, redirect_uri: this.redirectUri, response_type: 'code', scope: config.scope || 'openid profile email', @@ -137,6 +138,17 @@ class OpenIdController { }; } + parseLogoutUrlNunjucks({ config, idToken }) { + const template = nunjucksFunction(config.logoutRedirectUri); + const templateData = { + id_token_hint: idToken, + client_id: config.clientId, + openid_domain: config.domain, + host: encodeURIComponent(`${this.httpPrefix}://${this.host}`), + }; + return template(templateData); + } + async logoutUrl({ idToken }) { try { const setCookieHeader = cookie.serialize('authorization', '', { @@ -149,18 +161,8 @@ class OpenIdController { this.setHeader('Set-Cookie', setCookieHeader); const config = await this.getOpenIdConfig(); - if (!config) return null; - - if (config.logoutFromProvider !== true) { - return config.logoutRedirectUri || null; - } - - const client = await this.getClient({ config }); - - return client.endSessionUrl({ - id_token_hint: idToken, - post_logout_redirect_uri: config.logoutRedirectUri, - }); + if (!config || !type.isString(config.logoutRedirectUri)) return null; + return this.parseLogoutUrlNunjucks({ config, idToken }); } catch (error) { throw new AuthenticationError(error); } diff --git a/packages/graphql/src/controllers/openIdController.test.js b/packages/graphql/src/controllers/openIdController.test.js index df60e37a4..18c23a8f6 100644 --- a/packages/graphql/src/controllers/openIdController.test.js +++ b/packages/graphql/src/controllers/openIdController.test.js @@ -24,8 +24,8 @@ jest.mock('openid-client'); const mockOpenIdAuthorizationUrl = jest.fn( // eslint-disable-next-line camelcase - ({ redirect_uri, response_type, scope, state }) => - `${redirect_uri}:${response_type}:${scope}:${state}` + ({ redirect_uri, response_type, scope, state, ...additional }) => + `${redirect_uri}:${response_type}:${scope}:${state}:${JSON.stringify(additional)}` ); const mockOpenIdCallback = jest.fn(() => ({ @@ -33,14 +33,9 @@ const mockOpenIdCallback = jest.fn(() => ({ id_token: 'id_token', })); -const mockEndSessionUrl = jest.fn( - ({ id_token_hint, post_logout_redirect_uri }) => `${id_token_hint}:${post_logout_redirect_uri}` -); - const mockClient = jest.fn(() => ({ authorizationUrl: mockOpenIdAuthorizationUrl, callback: mockOpenIdCallback, - endSessionUrl: mockEndSessionUrl, })); // eslint-disable-next-line no-undef @@ -189,7 +184,7 @@ describe('authorizationUrl', () => { ], ]); expect(url).toEqual( - 'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI' + 'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI:{}' ); }); @@ -205,7 +200,7 @@ describe('authorizationUrl', () => { const openIdController = createOpenIdController(context); const url = await openIdController.authorizationUrl(authorizationUrlInput); expect(url).toEqual( - 'https://host/auth/openid-callback:code:custom scope:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI' + 'https://host/auth/openid-callback:code:custom scope:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpbnB1dCI6eyJpIjp0cnVlfSwibG93ZGVmeV9vcGVuaWRfc3RhdGVfdG9rZW4iOnRydWUsInBhZ2VJZCI6InBhZ2VJZCIsInVybFF1ZXJ5Ijp7InUiOnRydWV9LCJpYXQiOjEsImV4cCI6MzAxLCJhdWQiOiJob3N0IiwiaXNzIjoiaG9zdCJ9.-GLdtCspyagMhdx9z1VootZXXbIdLY3cbzpn5UK8eGI:{}' ); }); @@ -230,6 +225,51 @@ describe('authorizationUrl', () => { ConfigurationError ); }); + + test('authorizationUrl, additional query params', async () => { + getSecrets.mockImplementation(() => secrets); + const openIdController = createOpenIdController(context); + const url = await openIdController.authorizationUrl({ + authUrlQueryParams: { screen_hint: 'sign-up' }, + }); + expect(mockClient.mock.calls).toEqual([ + [ + { + client_id: 'OPENID_CLIENT_ID', + client_secret: 'OPENID_CLIENT_SECRET', + redirect_uris: ['https://host/auth/openid-callback'], + }, + ], + ]); + expect(url).toEqual( + 'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb3dkZWZ5X29wZW5pZF9zdGF0ZV90b2tlbiI6dHJ1ZSwiaWF0IjoxLCJleHAiOjMwMSwiYXVkIjoiaG9zdCIsImlzcyI6Imhvc3QifQ.-UtxdTFvQW6pFFFHTO0EtmubPbkDl8EJQwBQA2pp_M4:{"screen_hint":"sign-up"}' + ); + }); + + test('authorizationUrl, additional query do not overwrite fiexed params', async () => { + getSecrets.mockImplementation(() => secrets); + const openIdController = createOpenIdController(context); + const url = await openIdController.authorizationUrl({ + authUrlQueryParams: { + redirect_uri: 'overwritten', + response_type: 'overwritten', + scope: 'overwritten', + state: 'overwritten', + }, + }); + expect(mockClient.mock.calls).toEqual([ + [ + { + client_id: 'OPENID_CLIENT_ID', + client_secret: 'OPENID_CLIENT_SECRET', + redirect_uris: ['https://host/auth/openid-callback'], + }, + ], + ]); + expect(url).toEqual( + 'https://host/auth/openid-callback:code:openid profile email:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJsb3dkZWZ5X29wZW5pZF9zdGF0ZV90b2tlbiI6dHJ1ZSwiaWF0IjoxLCJleHAiOjMwMSwiYXVkIjoiaG9zdCIsImlzcyI6Imhvc3QifQ.-UtxdTFvQW6pFFFHTO0EtmubPbkDl8EJQwBQA2pp_M4:{}' + ); + }); }); describe('callback', () => { @@ -344,7 +384,7 @@ describe('callback', () => { }); describe('logout', () => { - test('callback, no openId config', async () => { + test('logout, no openId config', async () => { getSecrets.mockImplementation(() => ({})); const openIdController = createOpenIdController(context); const url = await openIdController.logoutUrl(logoutUrlInput); @@ -357,144 +397,48 @@ describe('logout', () => { ]); }); - test('callback, logoutFromProvider !== true, no logoutRedirectUri', async () => { - getSecrets.mockImplementation(() => secrets); - const openIdController = createOpenIdController(context); - const url = await openIdController.logoutUrl(logoutUrlInput); - expect(url).toEqual(null); - expect(mockEndSessionUrl.mock.calls).toEqual([]); - expect(setHeader.mock.calls).toEqual([ - [ - 'Set-Cookie', - 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax', - ], - ]); - }); - - test('callback, logoutFromProvider !== true, with logoutRedirectUri', async () => { + test('logout with logoutRedirectUri', async () => { getSecrets.mockImplementation(() => secrets); mockLoadComponent.mockImplementation(() => ({ auth: { openId: { - logoutRedirectUri: 'logoutRedirectUri', + logoutRedirectUri: + '{{ openid_domain }}/logout/?id_token_hint={{ id_token_hint }}&client_id={{ client_id }}&return_to={{ host }}%2Flogged-out', }, }, })); const openIdController = createOpenIdController(context); const url = await openIdController.logoutUrl(logoutUrlInput); - expect(url).toEqual('logoutRedirectUri'); - expect(mockEndSessionUrl.mock.calls).toEqual([]); - expect(setHeader.mock.calls).toEqual([ - [ - 'Set-Cookie', - 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax', - ], - ]); - }); - - test('callback, logoutFromProvider, no logoutRedirectUri', async () => { - getSecrets.mockImplementation(() => secrets); - mockLoadComponent.mockImplementation(() => ({ - auth: { - openId: { - logoutFromProvider: true, - }, - }, - })); - const openIdController = createOpenIdController(context); - const url = await openIdController.logoutUrl(logoutUrlInput); - expect(mockClient.mock.calls).toEqual([ - [ - { - client_id: 'OPENID_CLIENT_ID', - client_secret: 'OPENID_CLIENT_SECRET', - redirect_uris: ['https://host/auth/openid-callback'], - }, - ], - ]); - expect(url).toEqual('idToken:undefined'); - expect(mockEndSessionUrl.mock.calls).toEqual([ - [ - { - id_token_hint: 'idToken', - }, - ], - ]); - expect(setHeader.mock.calls).toEqual([ - [ - 'Set-Cookie', - 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax', - ], - ]); - }); - - test('callback, logoutFromProvider, with logoutRedirectUri', async () => { - getSecrets.mockImplementation(() => secrets); - mockLoadComponent.mockImplementation(() => ({ - auth: { - openId: { - logoutFromProvider: true, - logoutRedirectUri: 'logoutRedirectUri', - }, - }, - })); - const openIdController = createOpenIdController(context); - const url = await openIdController.logoutUrl(logoutUrlInput); - expect(mockClient.mock.calls).toEqual([ - [ - { - client_id: 'OPENID_CLIENT_ID', - client_secret: 'OPENID_CLIENT_SECRET', - redirect_uris: ['https://host/auth/openid-callback'], - }, - ], - ]); - expect(url).toEqual('idToken:logoutRedirectUri'); - expect(mockEndSessionUrl.mock.calls).toEqual([ - [ - { - id_token_hint: 'idToken', - post_logout_redirect_uri: 'logoutRedirectUri', - }, - ], - ]); - expect(setHeader.mock.calls).toEqual([ - [ - 'Set-Cookie', - 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax', - ], - ]); - }); - - test('callback, logoutFromProvider, error', async () => { - getSecrets.mockImplementation(() => secrets); - mockLoadComponent.mockImplementation(() => ({ - auth: { - openId: { - logoutFromProvider: true, - }, - }, - })); - const openIdController = createOpenIdController(context); - mockEndSessionUrl.mockImplementationOnce(() => { - throw new Error('OpenId End Session Error'); - }); - await expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow(AuthenticationError); - mockEndSessionUrl.mockImplementationOnce(() => { - throw new Error('OpenId End Session Error'); - }); - await expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow( - 'Error: OpenId End Session Error' + expect(url).toEqual( + 'OPENID_DOMAIN/logout/?id_token_hint=idToken&client_id=OPENID_CLIENT_ID&return_to=https%3A%2F%2Fhost%2Flogged-out' ); expect(setHeader.mock.calls).toEqual([ [ 'Set-Cookie', 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax', ], - [ - 'Set-Cookie', - 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax', - ], + ]); + }); + + test('logout with logoutRedirectUri, development true', async () => { + getSecrets.mockImplementation(() => secrets); + mockLoadComponent.mockImplementation(() => ({ + auth: { + openId: { + logoutRedirectUri: + '{{ openid_domain }}/logout/?id_token_hint={{ id_token_hint }}&client_id={{ client_id }}&return_to={{ host }}%2Flogged-out', + }, + }, + })); + const devOpenIdController = createOpenIdController( + testBootstrapContext({ getSecrets, host: 'host', loaders, development: true, setHeader }) + ); + const url = await devOpenIdController.logoutUrl(logoutUrlInput); + expect(url).toEqual( + 'OPENID_DOMAIN/logout/?id_token_hint=idToken&client_id=OPENID_CLIENT_ID&return_to=http%3A%2F%2Fhost%2Flogged-out' + ); + expect(setHeader.mock.calls).toEqual([ + ['Set-Cookie', 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; SameSite=Lax'], ]); }); @@ -516,4 +460,18 @@ describe('logout', () => { ], ]); }); + + test('logout with logoutRedirectUri, invalid template', async () => { + getSecrets.mockImplementation(() => secrets); + mockLoadComponent.mockImplementation(() => ({ + auth: { + openId: { + logoutRedirectUri: '{{ openid_domain ', + }, + }, + })); + const openIdController = createOpenIdController(context); + expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow(AuthenticationError); + expect(openIdController.logoutUrl(logoutUrlInput)).rejects.toThrow('Template render error'); + }); }); diff --git a/packages/graphql/src/resolvers/queries/auth/openIdLogoutUrl.test.js b/packages/graphql/src/resolvers/queries/auth/openIdLogoutUrl.test.js index f1a0296dc..f5a927443 100644 --- a/packages/graphql/src/resolvers/queries/auth/openIdLogoutUrl.test.js +++ b/packages/graphql/src/resolvers/queries/auth/openIdLogoutUrl.test.js @@ -17,7 +17,6 @@ // eslint-disable-next-line no-unused-vars import { gql } from 'apollo-server'; import runTestQuery from '../../../test/runTestQuery'; -import { Issuer } from 'openid-client'; import openIdLogoutUrl from './openIdLogoutUrl'; @@ -33,19 +32,6 @@ const getController = jest.fn((name) => { } }); -// OpenID mocks -const mockEndSessionUrl = jest.fn( - ({ id_token_hint, post_logout_redirect_uri }) => `${id_token_hint}:${post_logout_redirect_uri}` -); - -const mockClient = jest.fn(() => ({ - endSessionUrl: mockEndSessionUrl, -})); - -Issuer.discover = jest.fn(() => ({ - Client: mockClient, -})); - const secrets = { OPENID_CLIENT_ID: 'OPENID_CLIENT_ID', OPENID_CLIENT_SECRET: 'OPENID_CLIENT_SECRET', @@ -56,8 +42,8 @@ const secrets = { const mockLoadComponent = jest.fn(() => ({ auth: { openId: { - logoutFromProvider: true, - logoutRedirectUri: 'logoutRedirectUri', + logoutRedirectUri: + '{{ openid_domain }}/logout/?id_token_hint={{ id_token_hint }}&client_id={{ client_id }}&return_to={{ host }}%2Flogged-out', }, }, })); @@ -97,7 +83,8 @@ test('openIdLogoutUrl graphql', async () => { }); expect(res.errors).toBe(undefined); expect(res.data).toEqual({ - openIdLogoutUrl: 'idToken:logoutRedirectUri', + openIdLogoutUrl: + 'OPENID_DOMAIN/logout/?id_token_hint=idToken&client_id=OPENID_CLIENT_ID&return_to=https%3A%2F%2Fhost%2Flogged-out', }); expect(setHeader.mock.calls).toEqual([ ['Set-Cookie', 'authorization=; Max-Age=0; Path=/api/graphql; HttpOnly; Secure; SameSite=Lax'], diff --git a/packages/graphql/src/schema.js b/packages/graphql/src/schema.js index 7ea707b8c..80ee73ad8 100644 --- a/packages/graphql/src/schema.js +++ b/packages/graphql/src/schema.js @@ -71,6 +71,7 @@ const typeDefs = gql` } input OpenIdAuthorizationUrlInput { + authUrlQueryParams: JSON input: JSON pageId: String urlQuery: JSON diff --git a/packages/renderer/src/utils/auth/createLogin.js b/packages/renderer/src/utils/auth/createLogin.js index 1f556e163..cb8299783 100644 --- a/packages/renderer/src/utils/auth/createLogin.js +++ b/packages/renderer/src/utils/auth/createLogin.js @@ -24,13 +24,14 @@ const GET_LOGIN = gql` `; function createLogin({ client, window }) { - async function login({ input, pageId, urlQuery } = {}) { + async function login({ authUrlQueryParams, input, pageId, urlQuery } = {}) { try { const { data } = await client.query({ query: GET_LOGIN, fetchPolicy: 'network-only', variables: { openIdAuthorizationUrlInput: { + authUrlQueryParams, input, pageId, urlQuery, diff --git a/packages/renderer/src/utils/auth/createLogout.js b/packages/renderer/src/utils/auth/createLogout.js index f9bece3af..1ab1f9f65 100644 --- a/packages/renderer/src/utils/auth/createLogout.js +++ b/packages/renderer/src/utils/auth/createLogout.js @@ -26,17 +26,17 @@ function createLogout(lowdefy) { async function logout() { try { lowdefy.user = {}; + const idToken = lowdefy.localStorage.getItem('idToken'); lowdefy.localStorage.setItem(`idToken`, ''); const { data } = await lowdefy.client.query({ query: GET_LOGOUT, fetchPolicy: 'network-only', variables: { openIdLogoutUrlInput: { - idToken: '', + idToken, }, }, }); - // TODO: should we call link?? lowdefy.window.location.href = data.openIdLogoutUrl || lowdefy.window.location.origin; } catch (error) { throw new Error(error); diff --git a/yarn.lock b/yarn.lock index fda8df87f..aa1ca57b4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3395,7 +3395,6 @@ __metadata: mssql: 6.3.1 mysql: 2.18.1 openid-client: 4.6.0 - oracledb: 5.1.0 pg: 8.5.1 saslprep: 1.0.3 sqlite3: 5.0.2 @@ -14188,7 +14187,6 @@ fsevents@^1.2.7: mysql: 2.18.1 opener: 1.5.2 ora: 5.3.0 - oracledb: 5.1.0 pg: 8.5.1 react: 17.0.2 react-dom: 17.0.2 @@ -16180,13 +16178,6 @@ fsevents@^1.2.7: languageName: node linkType: hard -"oracledb@npm:5.1.0": - version: 5.1.0 - resolution: "oracledb@npm:5.1.0" - checksum: c4dd2c6ac05b2c71f81ed61a17c71b694e9821f1ad2ca6b14ccc5681dd928e26fb0ba043fb0daf306b055329492186162b1aceddabd048eedd027645ed3afdaf - languageName: node - linkType: hard - "original@npm:^1.0.0": version: 1.0.2 resolution: "original@npm:1.0.2"