diff --git a/packages/core/expressions/cypress.config.ts b/packages/core/expressions/cypress.config.ts
new file mode 100644
index 0000000000..6aad897723
--- /dev/null
+++ b/packages/core/expressions/cypress.config.ts
@@ -0,0 +1,10 @@
+import { defineConfig } from 'cypress'
+
+export default defineConfig({
+ component: {
+ devServer: {
+ framework: 'vue',
+ bundler: 'vite',
+ },
+ },
+})
diff --git a/packages/core/expressions/cypress/fixtures/example.json b/packages/core/expressions/cypress/fixtures/example.json
new file mode 100644
index 0000000000..02e4254378
--- /dev/null
+++ b/packages/core/expressions/cypress/fixtures/example.json
@@ -0,0 +1,5 @@
+{
+ "name": "Using fixtures to represent data",
+ "email": "hello@cypress.io",
+ "body": "Fixtures are a great way to mock data for responses to routes"
+}
diff --git a/packages/core/expressions/cypress/support/commands.ts b/packages/core/expressions/cypress/support/commands.ts
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/packages/core/expressions/cypress/support/component-index.html b/packages/core/expressions/cypress/support/component-index.html
new file mode 100644
index 0000000000..ac6e79fd83
--- /dev/null
+++ b/packages/core/expressions/cypress/support/component-index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+ Components App
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/core/expressions/cypress/support/component.ts b/packages/core/expressions/cypress/support/component.ts
new file mode 100644
index 0000000000..3e26a2696c
--- /dev/null
+++ b/packages/core/expressions/cypress/support/component.ts
@@ -0,0 +1,32 @@
+// ***********************************************************
+// This example support/component.ts is processed and
+// loaded automatically before your test files.
+//
+// This is a great place to put global configuration and
+// behavior that modifies Cypress.
+//
+// You can change the location of this file or turn off
+// automatically serving support files with the
+// 'supportFile' configuration option.
+//
+// You can read more here:
+// https://on.cypress.io/configuration
+// ***********************************************************
+
+// Import commands.js using ES2015 syntax:
+import './commands'
+
+// Alternatively you can use CommonJS syntax:
+// require('./commands')
+
+import { mount } from 'cypress/vue'
+
+// Augment the Cypress namespace to include type definitions for
+// your custom command.
+// Alternatively, can be defined in cypress/support/component.d.ts
+// with a at the top of your spec.
+
+Cypress.Commands.add('mount', mount)
+
+// Example use:
+// cy.mount(MyComponent)
diff --git a/packages/core/expressions/cypress/support/index.d.ts b/packages/core/expressions/cypress/support/index.d.ts
new file mode 100644
index 0000000000..e7698a3a20
--- /dev/null
+++ b/packages/core/expressions/cypress/support/index.d.ts
@@ -0,0 +1,27 @@
+declare namespace Cypress {
+ interface Chainable {
+ /**
+ * @description Custom alias command for cy.get() to select DOM element by data-testid attribute.
+ * @param {string} dataTestId
+ * @example cy.dataTestId('kong-auth-login-submit')
+ */
+ getTestId(dataTestId: string): Chainable
+
+ /**
+ * @description Custom alias command for cy.find() to select DOM element by data-testid attribute.
+ * @param {string} dataTestId
+ * @example cy.findTestId('kong-auth-login-submit')
+ */
+ findTestId(dataTestId: string): Chainable
+
+ /**
+ * @description Custom command to mount a Vue component inside Cypress browser.
+ * @example cy.mount(component, optionsOrProps)
+ * @param {any} component target component
+ * @param {any} options Options or props
+ */
+ mount(component: any, options?: any): Chainable
+
+ assertValueCopiedToClipboard(value: string): Chainable
+ }
+}
diff --git a/packages/core/expressions/cypress/support/index.ts b/packages/core/expressions/cypress/support/index.ts
new file mode 100644
index 0000000000..a626f89055
--- /dev/null
+++ b/packages/core/expressions/cypress/support/index.ts
@@ -0,0 +1,2 @@
+// Import custom Cypress commands
+import './commands'
diff --git a/packages/core/expressions/package.json b/packages/core/expressions/package.json
index 99374645d4..93326ddb30 100644
--- a/packages/core/expressions/package.json
+++ b/packages/core/expressions/package.json
@@ -31,8 +31,8 @@
"stylelint": "stylelint --allow-empty-input './src/**/*.{css,scss,sass,less,styl,vue}'",
"stylelint:fix": "stylelint --allow-empty-input './src/**/*.{css,scss,sass,less,styl,vue}' --fix",
"typecheck": "vue-tsc -p './tsconfig.build.json' --noEmit",
- "test:component": "BABEL_ENV=cypress cross-env FORCE_COLOR=1 cypress run --component -b chrome --spec './src/**/*.cy.ts' --project '../../../.'",
- "test:component:open": "BABEL_ENV=cypress cross-env FORCE_COLOR=1 cypress open --component -b chrome --project '../../../.'",
+ "test:component": "BABEL_ENV=cypress cross-env FORCE_COLOR=1 cypress run --component -b chrome --spec './src/**/*.cy.ts' --project './'",
+ "test:component:open": "BABEL_ENV=cypress cross-env FORCE_COLOR=1 cypress open --component -b chrome --project './'",
"test:unit": "cross-env FORCE_COLOR=1 vitest run",
"test:unit:open": "cross-env FORCE_COLOR=1 vitest --ui"
},
@@ -70,6 +70,9 @@
"vue": "^3.4.31"
},
"dependencies": {
- "@kong-ui-public/core": "workspace:^"
+ "@kong-ui-public/core": "workspace:^",
+ "@kong-ui-public/forms": "workspace:^",
+ "@kong/icons": "^1.14.2",
+ "uuid": "^9.0.1"
}
}
diff --git a/packages/core/expressions/sandbox/App.vue b/packages/core/expressions/sandbox/App.vue
index 6ded682b41..815f8788fd 100644
--- a/packages/core/expressions/sandbox/App.vue
+++ b/packages/core/expressions/sandbox/App.vue
@@ -29,6 +29,24 @@
@parse-result-update="onParseResultUpdate"
/>
+ Test with Router Playground
+
+
+
+ A playground where you can test out the Kong router Expressions.
+
+
+
ParseResult:
{{ parseResult }}
@@ -41,6 +59,7 @@
import { ref, watch } from 'vue'
import type { SchemaDefinition } from '../src'
import { ExpressionsEditor, HTTP_SCHEMA_DEFINITION, STREAM_SCHEMA_DEFINITION } from '../src'
+import RouterPlaygroundModal from '../src/components/RouterPlaygroundModal.vue'
type NamedSchemaDefinition = { name: string; definition: SchemaDefinition }
@@ -68,21 +87,18 @@ const btoa = (s: string) => window.btoa(s)
const expression = ref(expressionPresets[0])
const schemaDefinition = ref
(schemaPresets[0])
const parseResult = ref('')
+const isVisible = ref(false)
const onParseResultUpdate = (result: any) => {
parseResult.value = JSON.stringify(result, null, 2)
}
+const handleCommit = (exp: string) => {
+ expression.value = exp
+ isVisible.value = false
+}
+
watch(schemaDefinition, (newSchemaDefinition) => {
schemaDefinition.value = newSchemaDefinition
})
-
-
diff --git a/packages/core/expressions/sandbox/index.ts b/packages/core/expressions/sandbox/index.ts
index 52668a0a54..70c15aa2a0 100644
--- a/packages/core/expressions/sandbox/index.ts
+++ b/packages/core/expressions/sandbox/index.ts
@@ -1,6 +1,9 @@
+import '@kong/kongponents/dist/style.css'
import { createApp } from 'vue'
import App from './App.vue'
+import Kongponents from '@kong/kongponents'
const app = createApp(App)
+app.use(Kongponents)
app.mount('#app')
diff --git a/packages/core/expressions/src/components/ExpressionsEditor.vue b/packages/core/expressions/src/components/ExpressionsEditor.vue
index 5ffb5d0dfd..142c31e148 100644
--- a/packages/core/expressions/src/components/ExpressionsEditor.vue
+++ b/packages/core/expressions/src/components/ExpressionsEditor.vue
@@ -2,6 +2,7 @@
@@ -26,8 +27,10 @@ const props = withDefaults(defineProps<{
schema: Schema,
parseDebounce?: number,
inactiveUntilFocused?: boolean,
+ testId?: string,
}>(), {
parseDebounce: 500,
+ testId: 'expressions-editor',
})
const expression = defineModel({ required: true })
diff --git a/packages/core/expressions/src/components/MonacoEditor.vue b/packages/core/expressions/src/components/MonacoEditor.vue
new file mode 100644
index 0000000000..27ff9d08cd
--- /dev/null
+++ b/packages/core/expressions/src/components/MonacoEditor.vue
@@ -0,0 +1,103 @@
+
+
+
+
+
diff --git a/packages/core/expressions/src/components/PageHeader.vue b/packages/core/expressions/src/components/PageHeader.vue
new file mode 100644
index 0000000000..30214f7835
--- /dev/null
+++ b/packages/core/expressions/src/components/PageHeader.vue
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+
diff --git a/packages/core/expressions/src/components/RequestCard.vue b/packages/core/expressions/src/components/RequestCard.vue
new file mode 100644
index 0000000000..8dd17e4634
--- /dev/null
+++ b/packages/core/expressions/src/components/RequestCard.vue
@@ -0,0 +1,252 @@
+
+
+
+
+
+ {{ protocol }}
+
+
+ {{ method?.toUpperCase() }}
+
+
+
+ {{ url }}
+
+
+
+ SNI
+
+
+ {{ sni }}
+
+
+
+
+
+
+
+
+
diff --git a/packages/core/expressions/src/components/RequestImportModal.vue b/packages/core/expressions/src/components/RequestImportModal.vue
new file mode 100644
index 0000000000..8125d1daa2
--- /dev/null
+++ b/packages/core/expressions/src/components/RequestImportModal.vue
@@ -0,0 +1,155 @@
+
+
+
+
+
+ Warning: All saved requests will be removed and replaced with the imported ones.
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/core/expressions/src/components/RequestModal.vue b/packages/core/expressions/src/components/RequestModal.vue
new file mode 100644
index 0000000000..b83b50351d
--- /dev/null
+++ b/packages/core/expressions/src/components/RequestModal.vue
@@ -0,0 +1,203 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/core/expressions/src/components/RouterPlayground.cy.ts b/packages/core/expressions/src/components/RouterPlayground.cy.ts
new file mode 100644
index 0000000000..329bf1cf78
--- /dev/null
+++ b/packages/core/expressions/src/components/RouterPlayground.cy.ts
@@ -0,0 +1,220 @@
+import RouterPlayground from './RouterPlayground.vue'
+
+import { mount } from 'cypress/vue'
+import type { App } from 'vue'
+import Kongponents from '@kong/kongponents'
+import '@kong/kongponents/dist/style.css'
+
+Cypress.Commands.add('getTestId', (dataTestId: string): any => {
+ return cy.get(`[data-testid="${dataTestId}"]`)
+})
+
+Cypress.Commands.add('mount', (component, options = {}) => {
+ // Setup options object
+ options.global = options.global || {}
+ options.global.stubs = options.global.stubs || {}
+ options.global.stubs.transition = false
+ options.global.components = options.global.components || {}
+ options.global.plugins = options.global.plugins || []
+
+ // Add plugins
+ options.global.plugins.push({
+ install(app: App) {
+ // Kongponents
+ app.use(Kongponents)
+ },
+ })
+
+ return mount(component, options)
+})
+
+Cypress.Commands.add('assertValueCopiedToClipboard', value => {
+ cy.window().then(win => {
+ win.navigator.clipboard.readText().then(text => {
+ expect(text).to.eq(value)
+ })
+ })
+})
+
+
+describe('', () => {
+ beforeEach(() => {
+ cy.viewport(800, 800)
+ })
+
+ it('should show router playground', () => {
+ cy.mount(RouterPlayground)
+
+ cy.getTestId('expression-header').should('be.visible')
+ cy.getTestId('expressions-editor').should('be.visible')
+ cy.getTestId('expressions-inspirations').should('be.visible')
+ cy.getTestId('requests-header').should('be.visible')
+ cy.getTestId('btn-commit').should('be.visible')
+ cy.getTestId('btn-import').should('be.visible')
+ cy.getTestId('empty-state-requests').should('be.visible')
+ })
+
+ it('page-header slot', () => {
+ cy.mount(RouterPlayground, {
+ slots: {
+ 'page-header': 'Custom Header
',
+ },
+ })
+
+ cy.getTestId('custom-header').should('be.visible')
+ })
+
+ it('initial expression', () => {
+ cy.mount(RouterPlayground, {
+ props: {
+ initialExpression: 'http.host == "localhost"',
+ },
+ })
+
+ cy.getTestId('expressions-editor').contains('http.host == "localhost"')
+ cy.getTestId('expressions-inspirations').should('not.exist')
+ })
+
+ it('inspirations', () => {
+ cy.mount(RouterPlayground)
+
+ cy.getTestId('btn-inspiration-0').click()
+ cy.getTestId('expressions-editor').contains('http.host == "localhost"')
+ cy.getTestId('btn-inspiration-0').should('not.exist')
+ })
+
+ it('expression changed', () => {
+ const onChangeSpy = cy.spy().as('onChangeSpy')
+ const onCommitSpy = cy.spy().as('onCommitSpy')
+ cy.mount(RouterPlayground, {
+ props: {
+ initialExpression: 'http.host == "localhost"',
+ onChange: onChangeSpy,
+ onCommit: onCommitSpy,
+ },
+ })
+ cy.get('.view-lines').type(' && http.method == "GET"')
+ cy.get('@onChangeSpy').should('have.been.calledWith', 'http.host == "localhost" && http.method == "GET"')
+
+ cy.getTestId('btn-commit').click()
+ cy.get('@onCommitSpy').should('have.been.calledWith', 'http.host == "localhost" && http.method == "GET"')
+ })
+
+ it('requests placeholder', () => {
+ cy.mount(RouterPlayground)
+
+ cy.getTestId('empty-state-requests').should('be.visible')
+ })
+
+ function addRequest({
+ url,
+ method,
+ headers,
+ }: {
+ url: string
+ method: string
+ headers?: Record[]
+ }) {
+ cy.getTestId('btn-add-request').click()
+ cy.getTestId('url-input').should('be.visible')
+ cy.getTestId('url-input').type(url)
+
+ cy.get('#method').select(method)
+
+ if (headers?.length) {
+ headers.forEach((header, index) => {
+ cy.getTestId('keyname-input').type(header.key)
+ cy.getTestId('add-key').click()
+ cy.get(`:nth-child(${index + 3}) > .form-control`).type(header.value)
+ })
+ }
+
+ cy.getTestId('modal-action-button').click()
+ }
+
+ it('add/remove requests', () => {
+ cy.mount(RouterPlayground)
+
+ addRequest({
+ url: 'http://localhost:8000',
+ method: 'GET',
+ headers: [
+ { key: 'Foo', value: 'bar' },
+ ],
+ })
+
+ cy.get('.request-card').should('have.length', 1)
+ cy.get('.request-card').first().contains('http://localhost:8000')
+ cy.get('.request-card').first().find('.close-btn').click({ force: true })
+ cy.get('.request-card').should('not.exist')
+ })
+
+ it('matching requests', () => {
+ cy.mount(RouterPlayground)
+ const requestIds: string[] = []
+
+ addRequest({
+ url: 'http://localhost:8000',
+ method: 'GET',
+ headers: [
+ { key: 'Foo', value: 'bar' },
+ ],
+ })
+
+ addRequest({
+ url: 'https://www.konghq.com',
+ method: 'GET',
+ })
+
+ // eslint-disable-next-line cypress/unsafe-to-chain-command
+ cy.get('.request-card').each($card => {
+ requestIds.push($card.data('testid'))
+ }).then(() => {
+ cy.get('.view-lines').type('http.host == "localhost"')
+ cy.getTestId(requestIds[0]).should('have.class', 'active')
+ cy.getTestId(requestIds[1]).should('not.have.class', 'active')
+ })
+ })
+
+ it('cache requests', () => {
+ cy.clearAllLocalStorage()
+
+ cy.mount(RouterPlayground)
+
+ addRequest({
+ url: 'http://localhost:8000',
+ method: 'GET',
+ })
+
+ cy.mount(RouterPlayground)
+ cy.get('.request-card').should('have.length', 1)
+
+ cy.getTestId('clear-requests-link').click()
+ cy.getTestId('modal-action-button').click()
+
+ cy.mount(RouterPlayground)
+ cy.get('.request-card').should('not.exist')
+ })
+
+ const REQUESTS_TEXT = '[{"method":"POST","headers":{},"protocol":"http","host":"localhost","port":8080,"path":"/","id":"42eafd0a-287b-441d-b88f-f6fe4b44da0d"},{"method":"GET","headers":{},"protocol":"https","host":"konghq.com","port":443,"path":"/abc","id":"da8f04a9-c8c5-495d-a26c-f7bc132a3a64"}]'
+
+ it('import/export requests', () => {
+ cy.mount(RouterPlayground)
+ cy.getTestId('btn-import').click()
+ cy.getTestId('import-requests-editor').should('be.visible')
+ cy.getTestId('import-requests-editor').find('.view-lines').type(REQUESTS_TEXT, {
+ parseSpecialCharSequences: false,
+ })
+ cy.getTestId('modal-action-button').click()
+ cy.get('.request-card').should('have.length', 2)
+ cy.get('.request-card').first().contains('http://localhost:8080/')
+ cy.get('.request-card:nth-child(2)').contains('https://konghq.com/abc')
+
+ cy.window().then(() => {
+ // eslint-disable-next-line cypress/unsafe-to-chain-command
+ cy.getTestId('btn-export').focus().click()
+ cy.assertValueCopiedToClipboard(REQUESTS_TEXT)
+ })
+ })
+
+})
diff --git a/packages/core/expressions/src/components/RouterPlayground.vue b/packages/core/expressions/src/components/RouterPlayground.vue
new file mode 100644
index 0000000000..114d017a4e
--- /dev/null
+++ b/packages/core/expressions/src/components/RouterPlayground.vue
@@ -0,0 +1,493 @@
+
+
+
+
+
+
+
+
+
+
+ Add to Route
+
+
+
+
+
+ auto_awesome
+ Inspiration for Quickstart
+
+
+
+ {{ exp }}
+
+
+
+
+
+
+
+
+ Import
+
+
+
+
+ Export
+
+
+
+ Add
+
+
+
+
+
+
+ No requests
+
+
+ Add requests to test out route expressions.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ All saved requests will be removed from the browser. This operation is permanent and cannot be undone. Would
+ you like to proceed?
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/core/expressions/src/components/RouterPlaygroundModal.vue b/packages/core/expressions/src/components/RouterPlaygroundModal.vue
new file mode 100644
index 0000000000..525b5675d3
--- /dev/null
+++ b/packages/core/expressions/src/components/RouterPlaygroundModal.vue
@@ -0,0 +1,72 @@
+
+
+
+
+ expression = exp"
+ >
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/core/expressions/src/definitions.ts b/packages/core/expressions/src/definitions.ts
new file mode 100644
index 0000000000..bf8a9dc4ea
--- /dev/null
+++ b/packages/core/expressions/src/definitions.ts
@@ -0,0 +1,25 @@
+export type Request = {
+ id: string;
+ protocol: string;
+ host: string;
+ port: number;
+ path: string;
+ method?: string;
+ headers?: { [k: string]: string | string[] };
+ sni?: string;
+}
+
+export const DEFAULT_PROTOCOL_PORTS = {
+ http: 80,
+ https: 443,
+ grpc: 80,
+ grpcs: 443,
+ ws: 80,
+ wss: 443,
+}
+
+export const HTTP_PROTOCOLS = new Set(['http', 'https'])
+
+export const SECURED_PROTOCOLS = new Set(['https', 'grpcs', 'wss'])
+
+export const SUPPORTED_PROTOCOLS = new Set(Object.keys(DEFAULT_PROTOCOL_PORTS))
diff --git a/packages/core/expressions/src/index.ts b/packages/core/expressions/src/index.ts
index ec9ebd21b3..488c03051c 100644
--- a/packages/core/expressions/src/index.ts
+++ b/packages/core/expressions/src/index.ts
@@ -1,8 +1,9 @@
import ExpressionsEditor from './components/ExpressionsEditor.vue'
+import RouterPlaygroudModal from './components/RouterPlaygroundModal.vue'
export * as Atc from '@kong/atc-router'
export * from './schema'
-export { ExpressionsEditor }
+export { ExpressionsEditor, RouterPlaygroudModal }
declare const asyncInit: Promise
export { asyncInit }
diff --git a/packages/core/expressions/src/utils.ts b/packages/core/expressions/src/utils.ts
new file mode 100644
index 0000000000..00b6dfc9ec
--- /dev/null
+++ b/packages/core/expressions/src/utils.ts
@@ -0,0 +1,57 @@
+import { v4 as uuidv4 } from 'uuid'
+import {
+ DEFAULT_PROTOCOL_PORTS, HTTP_PROTOCOLS, SECURED_PROTOCOLS, SUPPORTED_PROTOCOLS, type Request,
+} from './definitions'
+
+export const validateRequest = (request: Request) => {
+ if (!SUPPORTED_PROTOCOLS.has(request.protocol)) throw new Error(`Unsupported protocol: ${request.protocol}. (Supported protocols: ${Array.from(SUPPORTED_PROTOCOLS.values()).join(', ')})`)
+}
+
+/**
+ * Transforms and checks if the request is valid
+ * @param request
+ * @returns
+ */
+export const transformCheckRequest = (request: Partial): string | undefined => {
+ if (request.id === undefined) {
+ request.id = uuidv4()
+ }
+
+ if (!request.protocol) {
+ return 'Protocol is required'
+ }
+
+ if (!SUPPORTED_PROTOCOLS.has(request.protocol)) {
+ return `Protocol is unsupported (Supported protocols: ${Array.from(SUPPORTED_PROTOCOLS).join(', ')})`
+ }
+
+ if (!request.port) {
+ request.port = (DEFAULT_PROTOCOL_PORTS as any)[request.protocol]
+ }
+
+ if (!request.host) {
+ return 'Host is required'
+ }
+
+ if (!request.path) {
+ return 'Path is required'
+ }
+
+ if (!SECURED_PROTOCOLS.has(request.protocol) && request.sni) {
+ return `SNI is not available for "${request.protocol}" protocol`
+ }
+
+ if (HTTP_PROTOCOLS.has(request.protocol)) {
+ if (Array.isArray(request.method)) {
+ request.method = request.method[0]
+ }
+
+ if (!request.method) {
+ return `Method is required for "${request.protocol}" protocol`
+ }
+
+ if (!/^[A-Z]+$/g.test(request.method)) {
+ return 'Method should be all capitalized'
+ }
+ }
+}
diff --git a/packages/core/expressions/vite.config.ts b/packages/core/expressions/vite.config.ts
index ec1423e694..134b4944b0 100644
--- a/packages/core/expressions/vite.config.ts
+++ b/packages/core/expressions/vite.config.ts
@@ -29,11 +29,7 @@ const config = mergeConfig(sharedViteConfig, defineConfig({
topLevelAwait({
promiseExportName: 'asyncInit',
}),
- // We don't need this plugin to bundle the library. Only for sandbox previews.
- // See: https://github.com/vdesjs/vite-plugin-monaco-editor/issues/21
- ...process.env.USE_SANDBOX
- ? [((monacoEditorPlugin as any).default as typeof monacoEditorPlugin)({})]
- : [],
+ [((monacoEditorPlugin as any).default as typeof monacoEditorPlugin)({})],
],
}))
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4193aca321..4fac0d0d59 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -545,6 +545,15 @@ importers:
'@kong-ui-public/core':
specifier: workspace:^
version: link:../core
+ '@kong-ui-public/forms':
+ specifier: workspace:^
+ version: link:../forms
+ '@kong/icons':
+ specifier: ^1.14.2
+ version: 1.14.2(vue@3.4.31(typescript@5.3.3))
+ uuid:
+ specifier: ^9.0.1
+ version: 9.0.1
devDependencies:
'@kong/atc-router':
specifier: 1.6.0-rc.1
@@ -1736,7 +1745,6 @@ packages:
'@evilmartians/lefthook@1.7.1':
resolution: {integrity: sha512-Wp8DaTMHZM1tUV4Mow6nG+6zq+giruD5054zHmFIDLXlPQxqYxnZMqJg0aYxe16vYwqFmH6NIClEMRdtGucO0Q==}
- cpu: [x64, arm64, ia32]
os: [darwin, linux, win32]
hasBin: true