From ee6f3e8f55e300df1a75c9be89b47f067bc08dee Mon Sep 17 00:00:00 2001 From: John Kaster Date: Tue, 23 Mar 2021 16:42:31 -0700 Subject: [PATCH] feat: Added --versions option to the code generator (#514) - Establish all generator parameters in prepGen - Moved spec list determination into sdk-codegen - Gracefully handling the absence of a LICENSE file - Updating codegen readmes - use `yarn test:ext` to test the extension SDK packages * v21.0.10 --- lerna.json | 2 +- package.json | 2 + packages/api-explorer/package.json | 12 +- packages/api-explorer/src/ApiExplorer.tsx | 14 +- .../src/StandaloneApiExplorer.tsx | 5 +- .../src/components/Header/ApiSpecSelector.tsx | 4 +- .../src/components/Header/Header.tsx | 4 +- packages/api-explorer/src/index.tsx | 12 +- packages/api-explorer/src/reducers/index.ts | 1 - .../api-explorer/src/reducers/spec/actions.ts | 5 +- .../api-explorer/src/reducers/spec/index.ts | 7 +- .../api-explorer/src/reducers/spec/reducer.ts | 5 +- .../src/reducers/spec/utils.spec.ts | 150 ++++-------- .../api-explorer/src/reducers/spec/utils.ts | 74 +----- .../api-explorer/src/routes/AppRouter.tsx | 5 +- .../src/scenes/DiffScene/DiffScene.tsx | 9 +- packages/api-explorer/src/test-data/specs.ts | 10 +- packages/extension-api-explorer/package.json | 14 +- .../src/ExtensionApiExplorer.tsx | 21 +- packages/extension-sdk-react/package.json | 8 +- packages/extension-sdk/package.json | 6 +- packages/hackathon/package.json | 12 +- packages/run-it/package.json | 10 +- packages/sdk-codegen-scripts/README.md | 4 +- .../sdk-codegen-scripts/config/development.js | 6 +- packages/sdk-codegen-scripts/package.json | 16 +- packages/sdk-codegen-scripts/scripts/utils.ts | 8 +- .../sdk-codegen-scripts/src/convert.spec.ts | 43 ---- packages/sdk-codegen-scripts/src/convert.ts | 26 +- .../sdk-codegen-scripts/src/fetchSpec.spec.ts | 26 +- packages/sdk-codegen-scripts/src/fetchSpec.ts | 165 +++++-------- .../src/legacyGenerator.ts | 10 +- packages/sdk-codegen-scripts/src/nodeUtils.ts | 25 ++ .../sdk-codegen-scripts/src/reformatter.ts | 12 +- packages/sdk-codegen-scripts/src/sdkGen.ts | 113 +++------ .../sdk-codegen-scripts/src/sdkGenerator.ts | 6 +- .../sdk-codegen-scripts/src/specConvert.ts | 5 +- .../sdk-codegen-scripts/src/utils.spec.ts | 203 ++++++++++++++++ packages/sdk-codegen-scripts/src/utils.ts | 223 ++++++++++++++++++ packages/sdk-codegen-utils/README.md | 7 +- packages/sdk-codegen-utils/package.json | 4 +- packages/sdk-codegen-utils/src/utils.ts | 13 - packages/sdk-codegen/README.md | 5 + packages/sdk-codegen/package.json | 11 +- packages/sdk-codegen/src/codeGen.ts | 28 ++- .../sdk-codegen/src/codeGenerators.spec.ts | 8 + packages/sdk-codegen/src/codeGenerators.ts | 5 +- packages/sdk-codegen/src/csharp.gen.ts | 5 +- packages/sdk-codegen/src/go.gen.ts | 3 +- packages/sdk-codegen/src/kotlin.gen.ts | 3 +- packages/sdk-codegen/src/pseudo.gen.spec.ts | 14 +- packages/sdk-codegen/src/sdkModels.ts | 4 +- .../sdk-codegen/src/specConverter.spec.ts | 100 +++++++- packages/sdk-codegen/src/specConverter.ts | 104 +++++++- packages/sdk-codegen/src/swift.gen.ts | 3 +- packages/sdk-codegen/src/typescript.gen.ts | 11 +- packages/sdk-node/package.json | 6 +- packages/sdk-node/src/nodeSession.ts | 2 +- packages/sdk-rtl/package.json | 2 +- packages/sdk-rtl/src/browserSession.ts | 15 +- packages/sdk-rtl/src/transport.ts | 3 + packages/sdk/package.json | 4 +- packages/wholly-sheet/package.json | 8 +- 63 files changed, 1020 insertions(+), 631 deletions(-) delete mode 100644 packages/sdk-codegen-scripts/src/convert.spec.ts create mode 100644 packages/sdk-codegen-scripts/src/utils.spec.ts create mode 100644 packages/sdk-codegen-scripts/src/utils.ts diff --git a/lerna.json b/lerna.json index 36f656cd6..ac89f65fa 100644 --- a/lerna.json +++ b/lerna.json @@ -4,5 +4,5 @@ ], "npmClient": "yarn", "useWorkspaces": true, - "version": "21.0.9" + "version": "21.0.10" } diff --git a/package.json b/package.json index 24ee4b953..980078f44 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,8 @@ "test:iphone": "xcodebuild test -project swift/looker/looker.xcodeproj -scheme looker-Package -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 11,OS=13.4.1' | xcpretty --test --color", "test:sdk": "yarn jest packages/sdk", "test:jest": "DOT_ENV_FILE=.env.test jest", + "test:ext": "yarn jest packages/extension-sdk packages/extension-sdk-react", + "bootstrap": "lerna clean -y && lerna bootstrap", "watch": "lerna run --parallel watch", "watch:cjs": "lerna run --parallel watch:cjs" }, diff --git a/packages/api-explorer/package.json b/packages/api-explorer/package.json index 7c4ef3080..d9cfdd974 100644 --- a/packages/api-explorer/package.json +++ b/packages/api-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@looker/api-explorer", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker API Explorer", "main": "lib/esm/index.js", "typings": "lib/index.d.ts", @@ -26,7 +26,7 @@ "devDependencies": { "@looker/components-test-utils": "^0.9.29", "@looker/sdk-codegen-scripts": "^21.0.9", - "@looker/sdk-node": "^21.0.9", + "@looker/sdk-node": "^21.0.10", "@testing-library/jest-dom": "^5.11.6", "@testing-library/react": "^11.2.2", "@testing-library/user-event": "^12.6.0", @@ -48,10 +48,10 @@ }, "dependencies": { "@looker/components": "^0.9.30", - "@looker/run-it": "^21.0.9", - "@looker/sdk": "^21.0.9", - "@looker/sdk-codegen": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/run-it": "^21.0.10", + "@looker/sdk": "^21.0.10", + "@looker/sdk-codegen": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "ace": "^1.3.0", "ace-builds": "^1.4.11", "history": "^4.10.1", diff --git a/packages/api-explorer/src/ApiExplorer.tsx b/packages/api-explorer/src/ApiExplorer.tsx index 4427ecdc0..26f527c2d 100644 --- a/packages/api-explorer/src/ApiExplorer.tsx +++ b/packages/api-explorer/src/ApiExplorer.tsx @@ -28,8 +28,8 @@ import React, { FC, useReducer, useState, useEffect } from 'react' import { useLocation } from 'react-router' import styled from 'styled-components' import { Aside, ComponentsProvider, Layout, Page } from '@looker/components' -import { ApiModel, KeyedCollection } from '@looker/sdk-codegen' import { Looker40SDK, Looker31SDK } from '@looker/sdk' +import { SpecList } from '@looker/sdk-codegen' import { SearchContext, LodeContext, defaultLodeContextValue } from './context' import { getLoded } from './utils' import { Header, SideNav } from './components' @@ -41,18 +41,8 @@ import { } from './reducers' import { AppRouter } from './routes' -export interface SpecItem { - status: string // 'current' | 'deprecated' | 'experimental' | 'stable' - isDefault?: boolean - api?: ApiModel - specURL?: string - specContent?: string -} - -export type SpecItems = KeyedCollection - export interface ApiExplorerProps { - specs: SpecItems + specs: SpecList sdk?: Looker31SDK | Looker40SDK lodeUrl?: string } diff --git a/packages/api-explorer/src/StandaloneApiExplorer.tsx b/packages/api-explorer/src/StandaloneApiExplorer.tsx index 414a808c5..167ce2f1b 100644 --- a/packages/api-explorer/src/StandaloneApiExplorer.tsx +++ b/packages/api-explorer/src/StandaloneApiExplorer.tsx @@ -32,10 +32,11 @@ import { initRunItSdk, } from '@looker/run-it' import { Looker40SDK } from '@looker/sdk' -import ApiExplorer, { SpecItems } from './ApiExplorer' +import { SpecList } from '@looker/sdk-codegen' +import ApiExplorer from './ApiExplorer' export interface StandloneApiExplorerProps { - specs: SpecItems + specs: SpecList } export const StandaloneApiExplorer: FC = ({ diff --git a/packages/api-explorer/src/components/Header/ApiSpecSelector.tsx b/packages/api-explorer/src/components/Header/ApiSpecSelector.tsx index de6dcfdc5..2db6baf65 100644 --- a/packages/api-explorer/src/components/Header/ApiSpecSelector.tsx +++ b/packages/api-explorer/src/components/Header/ApiSpecSelector.tsx @@ -28,11 +28,11 @@ import React, { FC, Dispatch } from 'react' import { Select } from '@looker/components' import { useHistory } from 'react-router-dom' -import { SpecItems } from '../../ApiExplorer' +import { SpecList } from '@looker/sdk-codegen' import { SpecAction, SpecState, selectSpec } from '../../reducers' interface ApiSpecSelectorProps { - specs: SpecItems + specs: SpecList spec: SpecState specDispatch: Dispatch } diff --git a/packages/api-explorer/src/components/Header/Header.tsx b/packages/api-explorer/src/components/Header/Header.tsx index 963cc90d4..92797100b 100644 --- a/packages/api-explorer/src/components/Header/Header.tsx +++ b/packages/api-explorer/src/components/Header/Header.tsx @@ -35,13 +35,13 @@ import { Header as SemanticHeader, } from '@looker/components' -import { SpecItems } from '../../ApiExplorer' +import { SpecList } from '@looker/sdk-codegen' import { SpecState, SpecAction } from '../../reducers' import { diffPath } from '../../utils' import { ApiSpecSelector } from './ApiSpecSelector' interface HeaderProps { - specs: SpecItems + specs: SpecList spec: SpecState specDispatch: Dispatch toggleNavigation: (target?: boolean) => void diff --git a/packages/api-explorer/src/index.tsx b/packages/api-explorer/src/index.tsx index 10ce9802b..1e0c209b4 100644 --- a/packages/api-explorer/src/index.tsx +++ b/packages/api-explorer/src/index.tsx @@ -28,21 +28,25 @@ import React from 'react' import { BrowserRouter as Router } from 'react-router-dom' import ReactDOM from 'react-dom' -import { ApiModel } from '@looker/sdk-codegen' -import { SpecItems } from './ApiExplorer' +import { ApiModel, SpecList } from '@looker/sdk-codegen' import { StandaloneApiExplorer } from './StandaloneApiExplorer' -export const specs: SpecItems = { +export const specs: SpecList = { '3.1': { + key: '3.1', status: 'current', + version: '3.1', specURL: 'https://self-signed.looker.com:19999/api/3.1/swagger.json', specContent: require('../../../spec/Looker.3.1.oas.json'), + isDefault: false, }, '4.0': { + key: '4.0', status: 'experimental', - isDefault: true, + version: '4.0', specURL: 'https://self-signed.looker.com:19999/api/4.0/swagger.json', specContent: require('../../../spec/Looker.4.0.oas.json'), + isDefault: true, }, } diff --git a/packages/api-explorer/src/reducers/index.ts b/packages/api-explorer/src/reducers/index.ts index 6c04ff33f..2419bc4ad 100644 --- a/packages/api-explorer/src/reducers/index.ts +++ b/packages/api-explorer/src/reducers/index.ts @@ -31,7 +31,6 @@ export { SpecState, SpecAction, getSpecKey, - getSpecsFromVersions, } from './spec' export { searchReducer, diff --git a/packages/api-explorer/src/reducers/spec/actions.ts b/packages/api-explorer/src/reducers/spec/actions.ts index 33785f904..4798c9269 100644 --- a/packages/api-explorer/src/reducers/spec/actions.ts +++ b/packages/api-explorer/src/reducers/spec/actions.ts @@ -23,9 +23,10 @@ SOFTWARE. */ -import { SpecItems } from '../../ApiExplorer' -export const selectSpec = (specs: SpecItems, specKey: string) => ({ +import { SpecList } from '@looker/sdk-codegen' + +export const selectSpec = (specs: SpecList, specKey: string) => ({ type: 'SELECT_SPEC', key: specKey, payload: specs, diff --git a/packages/api-explorer/src/reducers/spec/index.ts b/packages/api-explorer/src/reducers/spec/index.ts index dbe2b3c94..8e94b150a 100644 --- a/packages/api-explorer/src/reducers/spec/index.ts +++ b/packages/api-explorer/src/reducers/spec/index.ts @@ -23,11 +23,6 @@ SOFTWARE. */ -export { - initDefaultSpecState, - getSpecKey, - AbstractLocation, - getSpecsFromVersions, -} from './utils' +export { initDefaultSpecState, getSpecKey, AbstractLocation } from './utils' export { selectSpec } from './actions' export { specReducer, SpecAction, SpecState } from './reducer' diff --git a/packages/api-explorer/src/reducers/spec/reducer.ts b/packages/api-explorer/src/reducers/spec/reducer.ts index 04f86bc93..f12ed7109 100644 --- a/packages/api-explorer/src/reducers/spec/reducer.ts +++ b/packages/api-explorer/src/reducers/spec/reducer.ts @@ -24,8 +24,7 @@ */ -import { ApiModel } from '@looker/sdk-codegen' -import { SpecItem, SpecItems } from '../../ApiExplorer' +import { ApiModel, SpecItem, SpecList } from '@looker/sdk-codegen' import { fetchSpec } from './utils' export interface SpecState extends SpecItem { @@ -36,7 +35,7 @@ export interface SpecState extends SpecItem { export interface SpecAction { type: string key: string - payload: SpecItems + payload: SpecList } export const specReducer = ( diff --git a/packages/api-explorer/src/reducers/spec/utils.spec.ts b/packages/api-explorer/src/reducers/spec/utils.spec.ts index 9e0bff9ef..06eb70b09 100644 --- a/packages/api-explorer/src/reducers/spec/utils.spec.ts +++ b/packages/api-explorer/src/reducers/spec/utils.spec.ts @@ -23,27 +23,45 @@ SOFTWARE. */ -import { ApiModel } from '@looker/sdk-codegen' -import { cloneDeep, omit } from 'lodash' +import { ApiModel, SpecList } from '@looker/sdk-codegen' +import { omit } from 'lodash' import { specs } from '../../test-data' -import { SpecItems } from '../../ApiExplorer' import { getDefaultSpecKey, parseSpec, fetchSpec, initDefaultSpecState, getSpecKey, - getSpecsFromVersions, } from './utils' describe('Spec reducer utils', () => { const spec = specs['3.1'] - const specList: SpecItems = { - defaultKey: { status: 'experimental', isDefault: true }, - deprecatedKey: { status: 'deprecated' }, - currentKey: { status: 'current' }, - stableKey: { status: 'stable' }, + const specList: SpecList = { + defaultKey: { + key: 'defaultKey', + version: '4.0', + status: 'experimental', + isDefault: true, + }, + deprecatedKey: { + key: 'deprecatedKey', + version: '3.0', + status: 'deprecated', + isDefault: false, + }, + currentKey: { + key: 'currentKey', + version: '3.1', + status: 'current', + isDefault: false, + }, + stableKey: { + key: 'stableKey', + version: '3.1', + status: 'stable', + isDefault: false, + }, } describe('parseSpec', () => { @@ -98,7 +116,7 @@ describe('Spec reducer utils', () => { describe('getDefaultSpecKey', () => { test('it throws if no specs are provided', () => { expect(() => { - getDefaultSpecKey({} as SpecItems) + getDefaultSpecKey({} as SpecList) }).toThrow('No specs found.') }) @@ -118,16 +136,27 @@ describe('Spec reducer utils', () => { }) describe('fetchSpec', () => { - const specList: SpecItems = { + const specList: SpecList = { fromModel: { + key: 'fromModel', status: 'experimental', + isDefault: false, api: ApiModel.fromJson(specs['3.1'].specContent), + version: 'model', }, fromSpecContent: { + key: 'fromSpecContent', status: 'current', + isDefault: true, specContent: specs['3.1'].specContent, + version: 'spec', + }, + emptySpecItem: { + key: 'emptySpecItem', + status: 'deprecated', + isDefault: false, + version: 'empty', }, - emptySpecItem: { status: 'deprecated' }, } test('it uses api model if found ', () => { @@ -174,101 +203,4 @@ describe('Spec reducer utils', () => { expect(fetchedSpec.key).toEqual('4.0') }) }) - - describe('getSpecsFromVersions', () => { - const versions = { - looker_release_version: '21.3.0', - current_version: { - version: '3.1', - full_version: '3.1.0', - status: 'current', - swagger_url: 'http://localhost:19999/api/3.1/swagger.json', - }, - supported_versions: [ - { - version: '2.99', - full_version: '2.99.0', - status: 'internal_test', - swagger_url: 'http://localhost:19999/api/2.99/swagger.json', - }, - { - version: '3.0', - full_version: '3.0.0', - status: 'legacy', - swagger_url: 'http://localhost:19999/api/3.0/swagger.json', - }, - { - version: '3.1', - full_version: '3.1.0', - status: 'current', - swagger_url: 'http://localhost:19999/api/3.1/swagger.json', - }, - { - version: '4.0', - full_version: '4.0.21.3', - status: 'experimental', - swagger_url: 'http://localhost:19999/api/4.0/swagger.json', - }, - ], - api_server_url: 'http://localhost:19999', - } - - test('only gets supported specifications', async () => { - const actual = await getSpecsFromVersions(versions) - expect(Object.keys(actual)).toEqual(['3.1', '4.0']) - }) - - test('current is the default spec', async () => { - const specs = await getSpecsFromVersions(versions) - const actual = Object.entries(specs).find( - ([_, a]) => a.status === 'current' - ) - expect(actual).toBeDefined() - if (actual) { - const [, current] = actual - expect(current).toBeDefined() - expect(current.status).toEqual('current') - expect(current.isDefault).toEqual(true) - } - }) - - test('specs have unique keys', async () => { - const moar = cloneDeep(versions) - moar.supported_versions.push( - { - version: '4.0', - full_version: 'full', - status: 'un', - swagger_url: 'http://localhost:19999/api/4.0/u.json', - }, - { - version: '4.0', - full_version: 'full', - status: 'un', - swagger_url: 'http://localhost:19999/api/4.0/un.json', - }, - { - version: '4.0', - full_version: 'full', - status: 'un', - swagger_url: 'http://localhost:19999/api/4.0/un3.json', - }, - { - version: '4.0', - full_version: 'full', - status: 'un', - swagger_url: 'http://localhost:19999/api/4.0/un4.json', - } - ) - const actual = await getSpecsFromVersions(moar) - expect(Object.keys(actual)).toEqual([ - '3.1', - '4.0', - '4.0u', - '4.0un', - '4.0un3', - '4.0un4', - ]) - }) - }) }) diff --git a/packages/api-explorer/src/reducers/spec/utils.ts b/packages/api-explorer/src/reducers/spec/utils.ts index a8d758de1..bbed4f34a 100644 --- a/packages/api-explorer/src/reducers/spec/utils.ts +++ b/packages/api-explorer/src/reducers/spec/utils.ts @@ -24,11 +24,9 @@ */ -import { ApiModel } from '@looker/sdk-codegen' +import { ApiModel, SpecList } from '@looker/sdk-codegen' import { Location as HLocation } from 'history' -import { IApiVersion, IApiVersionElement } from '@looker/sdk' -import { SpecItem, SpecItems } from '../../ApiExplorer' import { diffPath, oAuthPath } from '../../utils' import { SpecState } from './reducer' @@ -40,7 +38,7 @@ export type AbstractLocation = HLocation | Location * @param specs A collection of specs * @returns A spec */ -export const getDefaultSpecKey = (specs: SpecItems): string => { +export const getDefaultSpecKey = (specs: SpecList): string => { const items = Object.entries(specs) if (items.length === 0) { @@ -92,7 +90,7 @@ export const parseSpec = (spec: string) => ApiModel.fromJson(spec) * @param specs A collection of specs * @returns SpecItem Parsed api with dynamic types loaded */ -export const fetchSpec = (key: string, specs: SpecItems): SpecState => { +export const fetchSpec = (key: string, specs: SpecList): SpecState => { const selectedSpec = specs[key] if (!selectedSpec) { throw Error(`Spec not found: "${key}"`) @@ -127,7 +125,7 @@ export const fetchSpec = (key: string, specs: SpecItems): SpecState => { * @param location service to examine * @param specs to use to find the default spec key */ -export const getSpecKey = (location: AbstractLocation, specs?: SpecItems) => { +export const getSpecKey = (location: AbstractLocation, specs?: SpecList) => { const pathNodes = location.pathname.split('/') let specKey = '' if ( @@ -151,71 +149,9 @@ export const getSpecKey = (location: AbstractLocation, specs?: SpecItems) => { * @returns An object to be used as default state */ export const initDefaultSpecState = ( - specs: SpecItems, + specs: SpecList, location: AbstractLocation ): SpecState => { const specKey = getSpecKey(location, specs) return fetchSpec(specKey, specs) } - -/** - * Callback for fetching and compiling specification to ApiModel - */ -export type SpecFetcher = (spec: SpecItem) => Promise - -/** - * Return all public API specifications from an ApiVersion payload - * @param versions payload from a Looker server - * @param fetcher fetches and compiles spec to ApiModel - */ -export const getSpecsFromVersions = async ( - versions: IApiVersion, - fetcher: SpecFetcher | undefined = undefined -): Promise => { - const items = {} - - /** - * Create a unique spec key for this version - * @param v version to identify - */ - const uniqueId = (v: IApiVersionElement) => { - let specKey = v.version || 'api' - const max = v.status?.length || 0 - let frag = 1 - while (items[specKey]) { - if (frag <= max) { - // More than one spec for this version - specKey = `${v.version}${v.status?.substr(0, frag)}` - } else { - specKey = `${v.version}${v.status}${frag}` - } - frag++ - } - return specKey - } - - if (versions.supported_versions) { - for (const v of versions.supported_versions) { - // Tell Typescript these are all defined because IApiVersion definition is lax - if (v.status && v.version && v.swagger_url) { - if ( - v.status !== 'internal_test' && - v.status !== 'deprecated' && - v.status !== 'legacy' - ) { - const spec: SpecItem = { - status: v.status, - isDefault: v.status === 'current', - specURL: v.swagger_url, - } - if (fetcher) { - spec.api = await fetcher(spec) - } - const specKey = uniqueId(v) - items[specKey] = spec - } - } - } - } - return items -} diff --git a/packages/api-explorer/src/routes/AppRouter.tsx b/packages/api-explorer/src/routes/AppRouter.tsx index 55e43622a..309879240 100644 --- a/packages/api-explorer/src/routes/AppRouter.tsx +++ b/packages/api-explorer/src/routes/AppRouter.tsx @@ -25,19 +25,18 @@ */ import React, { FC, useContext } from 'react' import { Redirect, Route, Switch } from 'react-router-dom' -import { ApiModel } from '@looker/sdk-codegen' +import { ApiModel, SpecList } from '@looker/sdk-codegen' import { OAuthScene, RunItContext } from '@looker/run-it' import { Looker40SDK } from '@looker/sdk' import { HomeScene, MethodScene, TagScene, TypeScene } from '../scenes' import { DiffScene } from '../scenes/DiffScene' -import { SpecItems } from '../ApiExplorer' import { diffPath, oAuthPath } from '../utils' interface AppRouterProps { api: ApiModel specKey: string - specs: SpecItems + specs: SpecList toggleNavigation: (target?: boolean) => void } diff --git a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx index b88843b37..471c94fdd 100644 --- a/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx +++ b/packages/api-explorer/src/scenes/DiffScene/DiffScene.tsx @@ -25,7 +25,7 @@ */ import React, { FC, useState, useEffect } from 'react' -import { ApiModel, DiffRow } from '@looker/sdk-codegen' +import { ApiModel, DiffRow, SpecList } from '@looker/sdk-codegen' import { useHistory, useRouteMatch } from 'react-router-dom' import { Box, @@ -37,14 +37,13 @@ import { Select, SelectMulti, } from '@looker/components' -import { SpecItems } from '../../ApiExplorer' import { getDefaultSpecKey } from '../../reducers/spec/utils' import { diffPath } from '../../utils' import { diffSpecs, standardDiffToggles } from './diffUtils' import { DocDiff } from './DocDiff' export interface DiffSceneProps { - specs: SpecItems + specs: SpecList toggleNavigation: (target?: boolean) => void } @@ -53,7 +52,7 @@ export interface DiffSceneProps { * @param specs to pick from * @param leftKey spec key that may or may not have a value */ -const pickLeft = (specs: SpecItems, leftKey: string) => { +const pickLeft = (specs: SpecList, leftKey: string) => { if (leftKey) return leftKey return getDefaultSpecKey(specs) } @@ -64,7 +63,7 @@ const pickLeft = (specs: SpecItems, leftKey: string) => { * @param rightKey spec key from url path * @param leftKey spec key from url path */ -const pickRight = (specs: SpecItems, rightKey: string, leftKey: string) => { +const pickRight = (specs: SpecList, rightKey: string, leftKey: string) => { if (rightKey) return rightKey if (!leftKey) leftKey = getDefaultSpecKey(specs) return Object.keys(specs).find((k) => k !== leftKey) || '' diff --git a/packages/api-explorer/src/test-data/specs.ts b/packages/api-explorer/src/test-data/specs.ts index b20baca6f..e72aaf156 100644 --- a/packages/api-explorer/src/test-data/specs.ts +++ b/packages/api-explorer/src/test-data/specs.ts @@ -23,22 +23,26 @@ SOFTWARE. */ -import { ApiModel } from '@looker/sdk-codegen' +import { ApiModel, SpecList } from '@looker/sdk-codegen' -import { SpecItems } from '../ApiExplorer' import { initDefaultSpecState } from '../reducers' -export const specs: SpecItems = { +export const specs: SpecList = { '3.1': { + key: '3.1', + isDefault: false, status: 'current', specURL: 'https://self-signed.looker.com:19999/api/3.1/swagger.json', specContent: require('../../../../spec/Looker.3.1.oas.json'), + version: '3.1', }, '4.0': { + key: '4.0', isDefault: true, status: 'experimental', specURL: 'https://self-signed.looker.com:19999/api/4.0/swagger.json', specContent: require('../../../../spec/Looker.4.0.oas.json'), + version: '4.0', }, } diff --git a/packages/extension-api-explorer/package.json b/packages/extension-api-explorer/package.json index 6b752e846..94f725c13 100644 --- a/packages/extension-api-explorer/package.json +++ b/packages/extension-api-explorer/package.json @@ -1,6 +1,6 @@ { "name": "@looker/extension-api-explorer", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker API Explorer extension version ", "main": "lib/index.js", "module": "lib/esm/index.js", @@ -16,12 +16,12 @@ "watch": "yarn lerna exec --scope @looker/extension-api-explorer --stream 'BABEL_ENV=build babel src --root-mode upward --out-dir lib/esm --source-maps --extensions .ts,.tsx --no-comments --watch'" }, "dependencies": { - "@looker/api-explorer": "^21.0.9", - "@looker/extension-sdk": "^21.0.9", - "@looker/extension-sdk-react": "^21.0.9", - "@looker/run-it": "^21.0.9", - "@looker/sdk": "^21.0.9", - "@looker/sdk-codegen": "^21.0.9", + "@looker/api-explorer": "^21.0.10", + "@looker/extension-sdk": "^21.0.10", + "@looker/extension-sdk-react": "^21.0.10", + "@looker/run-it": "^21.0.10", + "@looker/sdk": "^21.0.10", + "@looker/sdk-codegen": "^21.0.10", "react": "^16.13.1", "react-dom": "^16.13.1", "react-router-dom": "^5.2.0", diff --git a/packages/extension-api-explorer/src/ExtensionApiExplorer.tsx b/packages/extension-api-explorer/src/ExtensionApiExplorer.tsx index 399e6b216..a89d8a7a7 100644 --- a/packages/extension-api-explorer/src/ExtensionApiExplorer.tsx +++ b/packages/extension-api-explorer/src/ExtensionApiExplorer.tsx @@ -25,19 +25,21 @@ */ import React, { FC, useContext, useEffect, useState } from 'react' -import ApiExplorer, { - SpecItems, - SpecItem, -} from '@looker/api-explorer/src/ApiExplorer' import { IStorageValue, RunItProvider, RunItConfigurator } from '@looker/run-it' import { useRouteMatch } from 'react-router-dom' import { ExtensionContext, ExtensionContextData, } from '@looker/extension-sdk-react' -import { ApiModel, upgradeSpecObject } from '@looker/sdk-codegen' +import { + ApiModel, + getSpecsFromVersions, + SpecItem, + SpecList, + upgradeSpecObject, +} from '@looker/sdk-codegen' import { Looker31SDK, Looker40SDK } from '@looker/sdk' -import { getSpecsFromVersions } from '@looker/api-explorer/src/reducers' +import ApiExplorer from '@looker/api-explorer/lib/ApiExplorer' class ExtensionConfigurator implements RunItConfigurator { storage: Record = {} @@ -70,7 +72,7 @@ const configurator = new ExtensionConfigurator() export const ExtensionApiExplorer: FC = () => { const match = useRouteMatch<{ specKey: string }>(`/:specKey`) const extensionContext = useContext(ExtensionContext) - const [specs, setSpecs] = useState() + const [specs, setSpecs] = useState() let sdk: Looker31SDK | Looker40SDK if (match?.params.specKey === '3.1') { @@ -89,7 +91,8 @@ export const ExtensionApiExplorer: FC = () => { const sdk = extensionContext.core40SDK const [version, name] = spec.specURL.split('/').slice(-2) const content = await sdk.ok(sdk.api_spec(version, name)) - // TODO figure out why this crazy step is required + // TODO switch this to just call const api = ApiModel.fromString(content) now + // TODO I think we can remove this this crazy step now that the api_spec endpoint is cleaner let json = JSON.parse(content) if (typeof json === 'string') { json = JSON.parse(json) @@ -103,7 +106,7 @@ export const ExtensionApiExplorer: FC = () => { /** Load Looker /versions information and retrieve all supported specs */ async function loadSpecs() { const versions = await sdk.ok(sdk.versions()) - const result = await getSpecsFromVersions(versions, (spec) => + const result = await getSpecsFromVersions(versions, (spec: SpecItem) => extFetch(spec) ) setSpecs(result) diff --git a/packages/extension-sdk-react/package.json b/packages/extension-sdk-react/package.json index cde52a55c..91df38e6c 100644 --- a/packages/extension-sdk-react/package.json +++ b/packages/extension-sdk-react/package.json @@ -1,6 +1,6 @@ { "name": "@looker/extension-sdk-react", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker Extension SDK for React", "main": "lib/index.js", "module": "lib/esm/index.js", @@ -41,9 +41,9 @@ "enzyme": "^3.11.0" }, "dependencies": { - "@looker/extension-sdk": "^21.0.9", - "@looker/sdk": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/extension-sdk": "^21.0.10", + "@looker/sdk": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "history": "^4.9.0", "lodash": "^4.17.20", "react": "^16.13.1", diff --git a/packages/extension-sdk/package.json b/packages/extension-sdk/package.json index 699d79916..fb7e8a3c3 100644 --- a/packages/extension-sdk/package.json +++ b/packages/extension-sdk/package.json @@ -1,6 +1,6 @@ { "name": "@looker/extension-sdk", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker Extension SDK", "main": "lib/index.js", "module": "lib/esm/index.js", @@ -40,8 +40,8 @@ }, "dependencies": { "@looker/chatty": "^2.3.0", - "@looker/sdk": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/sdk": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "deepmerge": "^4.2.2", "readable-stream": "^3.4.0", "request": "^2.88.0", diff --git a/packages/hackathon/package.json b/packages/hackathon/package.json index 39eaee4e2..41956eed5 100644 --- a/packages/hackathon/package.json +++ b/packages/hackathon/package.json @@ -1,6 +1,6 @@ { "name": "@looker/hackathon", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker Hackathon extension", "main": "lib/esm/index.js", "typings": "lib/index.d.ts", @@ -34,11 +34,11 @@ }, "dependencies": { "@looker/components": "^0.9.30", - "@looker/extension-sdk": "^21.0.9", - "@looker/extension-sdk-react": "^21.0.9", - "@looker/sdk": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", - "@looker/wholly-sheet": "^21.0.9", + "@looker/extension-sdk": "^21.0.10", + "@looker/extension-sdk-react": "^21.0.10", + "@looker/sdk": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", + "@looker/wholly-sheet": "^21.0.10", "lodash": "^4.17.20", "react": "^16.13.1", "react-dom": "^16.13.1", diff --git a/packages/run-it/package.json b/packages/run-it/package.json index 59b1ed93f..8055a1257 100644 --- a/packages/run-it/package.json +++ b/packages/run-it/package.json @@ -1,6 +1,6 @@ { "name": "@looker/run-it", - "version": "21.0.9", + "version": "21.0.10", "description": "A dynamic REST request input form and response visualizer", "main": "lib/esm/index.js", "typings": "lib/index.d.ts", @@ -50,10 +50,10 @@ "dependencies": { "@looker/components": "^0.9.30", "@looker/design-tokens": "0.9.27", - "@looker/sdk": "^21.0.9", - "@looker/sdk-codegen": "^21.0.9", - "@looker/sdk-codegen-utils": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/sdk": "^21.0.10", + "@looker/sdk-codegen": "^21.0.10", + "@looker/sdk-codegen-utils": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "@types/readable-stream": "^2.3.5", "lodash": "^4.17.19", "papaparse": "^5.3.0", diff --git a/packages/sdk-codegen-scripts/README.md b/packages/sdk-codegen-scripts/README.md index be7372cc5..01a8ee719 100644 --- a/packages/sdk-codegen-scripts/README.md +++ b/packages/sdk-codegen-scripts/README.md @@ -2,7 +2,9 @@ This package contains the Node-based scripts used by the Looker SDK Codegen project. -**NOT SUPPORTED**: Looker does not support direct use of this package, other than via direct use in the +It has node dependencies, so it cannot be used in the browser. + +**DEPRECATED AND NOT SUPPORTED**: Looker does not support direct use of this package, other than via direct use in the [SDK Codegen project](https://github.com/looker-open-source/sdk-codegen) repository. ## Scripts diff --git a/packages/sdk-codegen-scripts/config/development.js b/packages/sdk-codegen-scripts/config/development.js index a3c0c9c10..8052a54a5 100644 --- a/packages/sdk-codegen-scripts/config/development.js +++ b/packages/sdk-codegen-scripts/config/development.js @@ -2,7 +2,7 @@ MIT License - Copyright (c) 2020 Looker Data Sciences, Inc. + Copyright (c) 2021 Looker Data Sciences, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -62,8 +62,8 @@ module.exports = { */ packagePath: 'sdk/src', /** - * yes, another `sdk` folder is underneath the src folder in the package path + * use this to customize the sdk subfolder + sdkPath: `sdk`, */ - sdkPath: `sdk`, }, } diff --git a/packages/sdk-codegen-scripts/package.json b/packages/sdk-codegen-scripts/package.json index 86ae8f398..420acf77c 100644 --- a/packages/sdk-codegen-scripts/package.json +++ b/packages/sdk-codegen-scripts/package.json @@ -1,6 +1,6 @@ { "name": "@looker/sdk-codegen-scripts", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker SDK Codegen scripts", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -9,7 +9,7 @@ ], "author": "Looker", "license": "MIT", - "private": true, + "private": false, "publishConfig": { "access": "public" }, @@ -27,11 +27,11 @@ "watch:cjs": "yarn lerna exec --scope @looker/sdk-codegen-scripts --stream 'BABEL_ENV=build_cjs babel src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --no-comments --watch'" }, "dependencies": { - "@looker/sdk": "^21.0.9", - "@looker/sdk-codegen": "^21.0.9", - "@looker/sdk-codegen-utils": "^21.0.9", - "@looker/sdk-node": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/sdk": "^21.0.10", + "@looker/sdk-codegen": "^21.0.10", + "@looker/sdk-codegen-utils": "^21.0.10", + "@looker/sdk-node": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "config": "^3.3.1", "cross-env": "^7.0.2" }, @@ -47,5 +47,5 @@ "prettier": "^2.1.2", "swagger2openapi": "^7.0.3" }, - "gitHead": "308cb8c61290837e71345a5ef6fb17be9285990e" + "gitHead": "d16d6fd98594572f130dd0b7959d49a737de8722" } diff --git a/packages/sdk-codegen-scripts/scripts/utils.ts b/packages/sdk-codegen-scripts/scripts/utils.ts index d3f4df103..3da91b0bc 100644 --- a/packages/sdk-codegen-scripts/scripts/utils.ts +++ b/packages/sdk-codegen-scripts/scripts/utils.ts @@ -28,6 +28,7 @@ import path from 'path' import fs from 'fs' import { IOauthClientApp } from '@looker/sdk' import { LookerNodeSDK, NodeSettingsIniFile } from '@looker/sdk-node' +import { getSpecsFromVersions } from '@looker/sdk-codegen' import { SDKConfig } from '../../sdk-codegen-scripts/src/sdkConfig' import { fetchLookerVersions, @@ -62,13 +63,10 @@ export const updateSpecs = async (apiVersions = supportedApiVersions) => { } in ${iniFile} ...` ) const lookerVersions = await fetchLookerVersions(props) + const specs = getSpecsFromVersions(lookerVersions) for (const v of apiVersions) { try { - const specFile = await logConvertSpec( - name, - { ...props, ...{ api_version: v } }, - lookerVersions - ) + const specFile = await logConvertSpec(name, specs[v], lookerVersions) if (!specFile) { console.error( `Could not fetch spec for API ${v} from ${props.base_url}` diff --git a/packages/sdk-codegen-scripts/src/convert.spec.ts b/packages/sdk-codegen-scripts/src/convert.spec.ts deleted file mode 100644 index ea2b24324..000000000 --- a/packages/sdk-codegen-scripts/src/convert.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - - MIT License - - Copyright (c) 2021 Looker Data Sciences, Inc. - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. - - */ - -import { unlinkSync } from 'fs' -import { TestConfig } from './testUtils' -import { convertSpec } from './convert' -import { readFileSync } from './nodeUtils' - -const config = TestConfig() -const swaggerFile = `${config.testPath}swaggerRef.json` -const openApiTestFile = `${config.testPath}openApiRef.test.json` - -describe('spec file conversion', () => { - it('handles a full conversion', () => { - convertSpec(swaggerFile, openApiTestFile, true) - const actual = readFileSync(openApiTestFile) - unlinkSync(openApiTestFile) - expect(actual).toContain(`"style":"simple"`) - }) -}) diff --git a/packages/sdk-codegen-scripts/src/convert.ts b/packages/sdk-codegen-scripts/src/convert.ts index 4e6604191..d631344bc 100644 --- a/packages/sdk-codegen-scripts/src/convert.ts +++ b/packages/sdk-codegen-scripts/src/convert.ts @@ -24,30 +24,16 @@ */ -import { log, success } from '@looker/sdk-codegen-utils' -import { fixConversion, swapXLookerTags } from '@looker/sdk-codegen' +import { log } from '@looker/sdk-codegen-utils' +import { upgradeSpec } from '@looker/sdk-codegen' import { + createJsonFile, fail, isFileSync, - quit, readFileSync, run, writeFileSync, } from './nodeUtils' -import { writeSpecFile } from './fetchSpec' - -/** - * Replaces Looker-specific tags with OpenAPI equivalents - * @param {string} openApiFile name of the Open API file to process - * @returns {Promise} the string contents of the updated spec - */ -export const swapXLookerTagsInFile = (openApiFile: string) => { - if (!isFileSync(openApiFile)) { - return quit(`${openApiFile} was not found`) - } - const spec = readFileSync(openApiFile) - return swapXLookerTags(spec) -} /** * Convert a Swagger specification to OpenAPI @@ -93,9 +79,7 @@ export const convertSpec = ( if (!isFileSync(openApiFilename)) { return fail('convertSpec', `creating ${openApiFilename} failed`) } - const source = swapXLookerTagsInFile(openApiFilename) - const result = fixConversion(source, readFileSync(specFileName)) - writeSpecFile(openApiFilename, result.spec) - success(`${openApiFilename} has ${result.fixes.length} fixes`) + const source = upgradeSpec(readFileSync(openApiFilename)) + createJsonFile(openApiFilename, source) return openApiFilename } diff --git a/packages/sdk-codegen-scripts/src/fetchSpec.spec.ts b/packages/sdk-codegen-scripts/src/fetchSpec.spec.ts index fb3cae6ff..4b1936834 100644 --- a/packages/sdk-codegen-scripts/src/fetchSpec.spec.ts +++ b/packages/sdk-codegen-scripts/src/fetchSpec.spec.ts @@ -24,13 +24,12 @@ */ +import { getSpecsFromVersions } from '@looker/sdk-codegen' import { TestConfig } from './testUtils' import { - authGetUrl, fetchLookerVersions, getVersionInfo, login, - swaggerFileUrl, supportedVersion, logConvertSpec, fetchLookerVersion, @@ -70,7 +69,7 @@ describe('fetch operations', () => { expect(version).toBeDefined() if (version) { expect(version.lookerVersion).toBeDefined() - expect(version.apiVersion).toBeDefined() + expect(version.spec).toBeDefined() } }) @@ -96,24 +95,17 @@ describe('fetch operations', () => { } }) - it('authGetUrl', async () => { - expect(props).toBeDefined() - const versions = await fetchLookerVersions(props) - const fileUrl = (swaggerFileUrl(props, versions) as string).replace( - versions.api_server_url, - props.base_url - ) - const content = await authGetUrl(props, fileUrl) - expect(content).toBeDefined() - expect(content.swagger).toBeDefined() - expect(content.swagger).toEqual('2.0') - }) - it('logConvertSpec', async () => { expect(props).toBeDefined() const name = 'Looker' const lookerVersions = await fetchLookerVersions(props) - const actual = await logConvertSpec(name, props, lookerVersions, true) + const specs = await getSpecsFromVersions(lookerVersions) + const actual = await logConvertSpec( + name, + specs[props.api_version], + lookerVersions, + true + ) expect(actual).toBeDefined() expect(actual.length).toBeGreaterThan(0) }) diff --git a/packages/sdk-codegen-scripts/src/fetchSpec.ts b/packages/sdk-codegen-scripts/src/fetchSpec.ts index 3cabb9f35..788f9ee31 100644 --- a/packages/sdk-codegen-scripts/src/fetchSpec.ts +++ b/packages/sdk-codegen-scripts/src/fetchSpec.ts @@ -24,36 +24,25 @@ */ -import * as fs from 'fs' import { danger, log, warn } from '@looker/sdk-codegen-utils' -import { IVersionInfo } from '@looker/sdk-codegen' -import { - defaultTimeout, - ITransportSettings, - sdkOk, - sdkError, -} from '@looker/sdk-rtl' +import { IVersionInfo, SpecItem } from '@looker/sdk-codegen' +import { defaultTimeout, ITransportSettings, sdkOk } from '@looker/sdk-rtl' import { NodeTransport } from '@looker/sdk-node' -import { fail, quit, isFileSync, utf8Encoding, isDirSync } from './nodeUtils' +import { + fail, + quit, + isFileSync, + readFileSync, + createJsonFile, +} from './nodeUtils' import { ISDKConfigProps } from './sdkConfig' import { convertSpec } from './convert' -/** - * Checks OpenAPI file for lint errors - * - * NOTE: Currently disabled - * @param {string} fileName - * @returns {Promise} - */ -export const lintCheck = async (_fileName: string) => { - return '' -} - let transport: NodeTransport /** * Customize request transport properties for SDK codegen - * @param {ISDKConfigProps} props SDK configuration properties + * @param props SDK configuration properties * @returns {NodeTransport} codegen-specific overrides * @constructor */ @@ -87,42 +76,18 @@ export const supportedVersion = (version: string, versions: any) => { return undefined } -export const swaggerFileUrl = (props: ISDKConfigProps, versions: any) => { - const apiVersion = props.api_version - if (!versions) { - return `${props.base_url}/api/${apiVersion}/swagger.json` - } - const version: any = supportedVersion(apiVersion, versions) - if (!version) { - throw sdkError(`${apiVersion} is not a supported version`) - } - return version.swagger_url -} - -export const openApiFileUrl = (props: ISDKConfigProps, versions: any) => { - const apiVersion = props.api_version - if (!versions) { - return '' - } - const version: any = supportedVersion(apiVersion, versions) - if (!version) { - throw sdkError(`${apiVersion} is not a supported version`) - } - return version.openapi_url -} - export const specPath = 'spec' -export const swaggerFileName = (name: string, props: ISDKConfigProps) => - `${specPath}/${name}.${props.api_version}.json` +export const swaggerFileName = (name: string, specKey: string) => + `${specPath}/${name}.${specKey}.json` -export const openApiFileName = (name: string, props: ISDKConfigProps) => - `${specPath}/${name}.${props.api_version}.oas.json` +export const openApiFileName = (name: string, specKey: string) => + `${specPath}/${name}.${specKey}.oas.json` /** * Is there an authentication error? - * @param {string | object} content response to check - * @returns {boolean} True if there's an authentication error + * @param content response to check + * @returns True if there's an authentication error */ const badAuth = (content: string | Record) => { const text = typeof content === 'object' ? JSON.stringify(content) : content @@ -176,29 +141,26 @@ NOTE! Certificate validation can be disabled with: return false } +/** + * gets either an http(s) url, or a file URL by reading directly if it's a file url + * @param props SDK configuration props + * @param url to fetch + * @param options for transport call + */ export const getUrl = async ( props: ISDKConfigProps, url: string, options?: Partial ) => { + const ref = new URL(url) + + if (ref.protocol === 'file:') { + return readFileSync(ref.pathname) + } const xp = specTransport(props) - // log(`GETting ${url} ...`) return await sdkOk( xp.request('GET', url, undefined, undefined, undefined, options) ) - // - // const response = await xp.rawRequest( - // 'GET', - // url, - // undefined, - // undefined, - // undefined, - // options - // ) - // if (!response.ok) { - // throw new Error(response.body) - // } - // return response.body.toString() } export const authGetUrl = async ( @@ -262,38 +224,20 @@ export const fetchLookerVersion = async ( return matches[0] } -/** - * Creates spec directory if needed, converts content to JSON string, writes file - * - * NOTE: if specFile is not in the spec path, write errors may occur - * - * @param {string} specFile name of spec file to write - * @param {object | string} content to convert to a JSON string - * @returns {string} name of file written - */ -export const writeSpecFile = ( - specFile: string, - content: Record | string -) => { - const data = typeof content === 'string' ? content : JSON.stringify(content) - if (!isDirSync(specPath)) fs.mkdirSync(specPath, { recursive: true }) - fs.writeFileSync(specFile, data, utf8Encoding) - return specFile -} - -export const fetchSwaggerFile = async ( +export const fetchSpec = async ( name: string, + spec: SpecItem, props: ISDKConfigProps ) => { - const fileName = swaggerFileName(name, props) + const fileName = swaggerFileName(name, spec.key) + // No need to fetch if the file already exists + // TODO make this a switch or remove caching? if (isFileSync(fileName)) return fileName try { - const versions = await fetchLookerVersions(props) - const fileUrl = swaggerFileUrl(props, versions) - const content = await authGetUrl(props, fileUrl) + const content = await authGetUrl(props, spec.specURL || 'missing spec url') - writeSpecFile(fileName, content) + createJsonFile(fileName, content) return fileName } catch (err) { @@ -302,10 +246,14 @@ export const fetchSwaggerFile = async ( } } -export const logFetchSwagger = async (name: string, props: ISDKConfigProps) => { - const specFile = await fetchSwaggerFile(name, props) +export const logFetchSpec = async ( + name: string, + spec: SpecItem, + props: ISDKConfigProps +) => { + const specFile = await fetchSpec(name, spec, props) if (!specFile) { - return fail('fetchSwaggerFile', 'No specification file name returned') + return fail('fetchSpec', 'No specification file name returned') } return specFile } @@ -316,7 +264,12 @@ export const getVersionInfo = async ( try { const lookerVersion = await fetchLookerVersion(props) return { - apiVersion: props.api_version, + spec: { + key: props.api_version, + version: props.api_version, + isDefault: false, + status: 'mocked', + }, lookerVersion, } } catch (e) { @@ -333,35 +286,25 @@ export const getVersionInfo = async ( * Fetch (if needed) and convert a Swagger API specification to OpenAPI * @param name base name of the target file * @param props SDK configuration properties to use - * @param versions version information structure from Looker * @param force true to force re-conversion of the spec * @returns {Promise} name of converted OpenAPI file */ export const logConvertSpec = async ( name: string, + spec: SpecItem, props: ISDKConfigProps, - versions: any, force = false ) => { // if openApiFile is resolved correctly, this value will be the file name let result = '' - const oaFile = openApiFileName(name, props) + const oaFile = openApiFileName(name, spec.key) if (isFileSync(oaFile) && !force) return oaFile - const apiUrl = await openApiFileUrl(props, versions) - if (apiUrl) { - const spec = await authGetUrl(props, apiUrl) - if (spec) { - result = writeSpecFile(oaFile, spec) - } - } else { - const specFile = await logFetchSwagger(name, props) - result = convertSpec(specFile, oaFile, force) - if (!result) { - return fail('logConvert', 'No file name returned for openAPI upgrade') - } - - await lintCheck(result) + const specFile = await logFetchSpec(name, spec, props) + result = convertSpec(specFile, oaFile, force) + if (!result) { + return fail('logConvert', 'No file name returned for openAPI upgrade') } + return result } diff --git a/packages/sdk-codegen-scripts/src/legacyGenerator.ts b/packages/sdk-codegen-scripts/src/legacyGenerator.ts index 822afc1a8..f61bda4a1 100644 --- a/packages/sdk-codegen-scripts/src/legacyGenerator.ts +++ b/packages/sdk-codegen-scripts/src/legacyGenerator.ts @@ -25,7 +25,12 @@ */ import { log } from '@looker/sdk-codegen-utils' -import { IGeneratorSpec, legacyLanguages } from '@looker/sdk-codegen' +import { + getSpecsFromVersions, + IGeneratorSpec, + legacyLanguages, +} from '@looker/sdk-codegen' +import { IApiVersion } from '@looker/sdk' import { ISDKConfigProps } from './sdkConfig' import { run } from './nodeUtils' import { fetchLookerVersions, logConvertSpec } from './fetchSpec' @@ -89,7 +94,8 @@ export const runConfig = async ( const apiVersion = defaultApiVersion(props) props.api_version = apiVersion const lookerVersions = fetchLookerVersions(props) - const openApiFile = await logConvertSpec(name, props, lookerVersions) + const specs = await getSpecsFromVersions(lookerVersions as IApiVersion) + const openApiFile = await logConvertSpec(name, specs[apiVersion], props) const languages = legacyLanguages() const results: any[] = [] diff --git a/packages/sdk-codegen-scripts/src/nodeUtils.ts b/packages/sdk-codegen-scripts/src/nodeUtils.ts index 0d0d7d788..4b1f63ace 100644 --- a/packages/sdk-codegen-scripts/src/nodeUtils.ts +++ b/packages/sdk-codegen-scripts/src/nodeUtils.ts @@ -26,6 +26,7 @@ import * as fs from 'fs' import { execSync, ExecSyncOptionsWithStringEncoding } from 'child_process' +import path from 'path' import { warn } from '@looker/sdk-codegen-utils' const utf8: BufferEncoding = 'utf-8' @@ -60,6 +61,30 @@ export const isDirSync = (filePath: string) => { } } +const homeToRoost = '../../../' + +export const getRootPath = () => path.join(__dirname, homeToRoost) +export const rootFile = (fileName = '') => path.join(getRootPath(), fileName) + +/** + * Creates the directory if needed, converts content to JSON string, writes file + * + * @param fileName to write that may include a relative path + * @param {object | string} content to convert to a JSON string + * @returns name of file written + */ +export const createJsonFile = ( + fileName: string, + content: Record | string +) => { + const fullName = rootFile(fileName) + const dir = path.dirname(fullName) + const data = typeof content === 'string' ? content : JSON.stringify(content) + if (!isDirSync(dir)) fs.mkdirSync(dir, { recursive: true }) + fs.writeFileSync(fullName, data, utf8Encoding) + return fullName +} + export const isFileSync = (filePath: string) => { try { return fs.statSync(filePath).isFile() diff --git a/packages/sdk-codegen-scripts/src/reformatter.ts b/packages/sdk-codegen-scripts/src/reformatter.ts index 318f42f44..56a58f4d9 100644 --- a/packages/sdk-codegen-scripts/src/reformatter.ts +++ b/packages/sdk-codegen-scripts/src/reformatter.ts @@ -205,7 +205,7 @@ class KotlinFormatter extends BaseFormatter { ) content = content.replace( apiPattern, - `API_VERSION = "${gen.versions.apiVersion}"` + `API_VERSION = "${gen.versions.spec.version}"` ) content = content.replace( envPattern, @@ -213,7 +213,7 @@ class KotlinFormatter extends BaseFormatter { ) writeFile(stampFile, content) return success( - `updated ${stampFile} to ${gen.versions.apiVersion}.${gen.versions.lookerVersion}` + `updated ${stampFile} to ${gen.versions.spec.version}.${gen.versions.lookerVersion}` ) } return this.skipping() @@ -242,7 +242,7 @@ class SwiftFormatter extends BaseFormatter { ) content = content.replace( apiPattern, - `apiVersion = "${gen.versions.apiVersion}"` + `apiVersion = "${gen.versions.spec.version}"` ) content = content.replace( envPattern, @@ -250,7 +250,7 @@ class SwiftFormatter extends BaseFormatter { ) writeFile(stampFile, content) return success( - `updated ${stampFile} to ${gen.versions.apiVersion}.${gen.versions.lookerVersion}` + `updated ${stampFile} to ${gen.versions.spec.version}.${gen.versions.lookerVersion}` ) } return this.skipping() @@ -279,7 +279,7 @@ class CsharpFormatter extends BaseFormatter { ) content = content.replace( apiPattern, - `ApiVersion = "${gen.versions.apiVersion}"` + `ApiVersion = "${gen.versions.spec.version}"` ) content = content.replace( envPattern, @@ -287,7 +287,7 @@ class CsharpFormatter extends BaseFormatter { ) writeFile(stampFile, content) return success( - `updated ${stampFile} to ${gen.versions.apiVersion}.${gen.versions.lookerVersion}` + `updated ${stampFile} to ${gen.versions.spec.version}.${gen.versions.lookerVersion}` ) } return this.skipping() diff --git a/packages/sdk-codegen-scripts/src/sdkGen.ts b/packages/sdk-codegen-scripts/src/sdkGen.ts index c947ed250..a1dc8c217 100644 --- a/packages/sdk-codegen-scripts/src/sdkGen.ts +++ b/packages/sdk-codegen-scripts/src/sdkGen.ts @@ -25,105 +25,55 @@ */ import * as fs from 'fs' +import path from 'path' import { danger, log } from '@looker/sdk-codegen-utils' -import { IVersionInfo, ICodeGen, codeGenerators } from '@looker/sdk-codegen' -import { ISDKConfigProps, SDKConfig } from './sdkConfig' -import { - fetchLookerVersion, - fetchLookerVersions, - logConvertSpec, -} from './fetchSpec' -import { - MethodGenerator, - specFromFile, - StreamGenerator, - TypeGenerator, -} from './sdkGenerator' +import { IVersionInfo } from '@looker/sdk-codegen' +import { MethodGenerator, StreamGenerator, TypeGenerator } from './sdkGenerator' import { FilesFormatter } from './reformatter' import { isDirSync, quit } from './nodeUtils' import { getGenerator } from './languages' - -const apiVersions = (props: any) => { - const versions = props.api_versions ?? '3.1,4.0' - return versions.split(',') -} - -/** - * Ensures the existence - * @param gen the SDK source code path - */ -const sdkPathPrep = (gen: ICodeGen) => { - const path = `${gen.codePath}${gen.packagePath}/sdk/${gen.apiVersion}` - if (!isDirSync(path)) fs.mkdirSync(path, { recursive: true }) - return path -} +import { loadSpecs, prepGen } from './utils' const formatter = new FilesFormatter() /** - * Writes the output file and registers it with the file reformatter for processing + * Writes the source code file and registers it with the file reformatter for processing * @param fileName name of source file * @param content contents to (over) write into source file * @returns the name of the file written */ -const writeFile = (fileName: string, content: string): string => { +export const writeCodeFile = (fileName: string, content: string): string => { + const filePath = path.dirname(fileName) + if (!isDirSync(filePath)) fs.mkdirSync(filePath, { recursive: true }) + fs.writeFileSync(fileName, content) formatter.addFile(fileName) return fileName } ;(async () => { - const args = process.argv.slice(2) - let languages = codeGenerators - .filter((l) => l.factory !== undefined) - .map((l) => l.language) - if (args.length > 0) { - if (args.toString().toLowerCase() !== 'all') { - languages = [] - for (const arg of args) { - const values = arg.toString().split(',') - values.forEach((v) => (v.trim() ? languages.push(v.trim()) : null)) - } - } - } + const config = await prepGen(process.argv.slice(2)) + const { props, languages, lookerVersion, lastApi } = config + + // load the specifications and create the unique keys in case of spec API version overlap + const specs = await loadSpecs(config) + const apis = config.apis + log(`generating ${languages.join(',')} SDKs for APIs ${apis}`) try { - const config = SDKConfig() for (const language of languages) { - const [name, props] = Object.entries(config)[0] - let lookerVersions = {} - let lookerVersion = '' - try { - lookerVersions = await fetchLookerVersions(props) - lookerVersion = await fetchLookerVersion(props, lookerVersions) - } catch { - // Looker server may not be required, so default things for the generator - lookerVersions = { - supported_versions: [ - { - version: '3.1', - swagger_url: `https://${props.base_url}/api/3.1/swagger.json`, - }, - { - version: '4.0', - swagger_url: `https://${props.base_url}/api/4.0/swagger.json`, - }, - ], - } - lookerVersion = '' - } - // Iterate through all specified API versions - const apis = apiVersions(props) - const lastApi = apis[apis.length - 1] for (const api of apis) { - const p = JSON.parse(JSON.stringify(props)) as ISDKConfigProps - p.api_version = api + const spec = specs[api] const versions: IVersionInfo = { - apiVersion: api, + spec, lookerVersion, } - const oasFile = await logConvertSpec(name, p, lookerVersions) - log(`Using specification ${oasFile} for code generation`) - const apiModel = specFromFile(oasFile) + const apiModel = spec.api + if (!apiModel) { + danger( + `Could not fetch or compile apiModel for ${api} ${spec.specURL}` + ) + continue + } const gen = getGenerator(language, apiModel, versions) if (!gen) { danger(`${language} does not have a code generator defined`) @@ -135,25 +85,26 @@ const writeFile = (fileName: string, content: string): string => { ) continue } - log(`generating ${language} from ${props.base_url} ${api}...`) + log(`generating ${language} from ${props.base_url} ${api} ...`) - sdkPathPrep(gen) // Generate standard method declarations const sdk = new MethodGenerator(apiModel, gen) let output = sdk.render(gen.indentStr) - writeFile(gen.sdkFileName(`methods`), output) + writeCodeFile(gen.sdkFileName(`methods`), output) if (gen.willItStream) { // Generate streaming method declarations const s = new StreamGenerator(apiModel, gen) const output = s.render(gen.indentStr) - writeFile(gen.sdkFileName(`streams`), output) + writeCodeFile(gen.sdkFileName(`streams`), output) } const types = new TypeGenerator(apiModel, gen) output = types.render('') - writeFile(gen.sdkFileName(`models`), output) - formatter.versionStamp(gen) + writeCodeFile(gen.sdkFileName(`models`), output) + if (api === lastApi) { + formatter.versionStamp(gen) + } } } // finally, reformat all the files that have been generated diff --git a/packages/sdk-codegen-scripts/src/sdkGenerator.ts b/packages/sdk-codegen-scripts/src/sdkGenerator.ts index f65f24a8d..a51a0c1c2 100644 --- a/packages/sdk-codegen-scripts/src/sdkGenerator.ts +++ b/packages/sdk-codegen-scripts/src/sdkGenerator.ts @@ -24,6 +24,7 @@ */ +import { existsSync } from 'fs' import * as Models from '@looker/sdk-codegen' import { success, warn } from '@looker/sdk-codegen-utils' import { readFileSync } from './nodeUtils' @@ -37,7 +38,10 @@ export interface IGeneratorCtor { new (model: T, formatter: Models.ICodeGen): Generator } -const licenseText = readFileSync('./LICENSE') +const licenseFile = `./LICENSE` +const licenseText = existsSync(licenseFile) + ? readFileSync('./LICENSE') + : `${licenseFile} file not found` export abstract class Generator { codeFormatter: Models.ICodeGen diff --git a/packages/sdk-codegen-scripts/src/specConvert.ts b/packages/sdk-codegen-scripts/src/specConvert.ts index 6f592f6e0..e4e2a3b01 100644 --- a/packages/sdk-codegen-scripts/src/specConvert.ts +++ b/packages/sdk-codegen-scripts/src/specConvert.ts @@ -25,6 +25,8 @@ */ import * as path from 'path' import { log } from '@looker/sdk-codegen-utils' +import { IApiVersion } from '@looker/sdk' +import { getSpecsFromVersions } from '@looker/sdk-codegen' import { ISDKConfigProps, SDKConfig } from './sdkConfig' import { convertSpec } from './convert' import { quit } from './nodeUtils' @@ -41,10 +43,11 @@ const fetchAndConvert = async () => { // Iterate through all specified API versions const apis = apiVersions(props) const lookerVersions = fetchLookerVersions(props) + const specs = await getSpecsFromVersions(lookerVersions as IApiVersion) for (const api of apis) { const p = JSON.parse(JSON.stringify(props)) as ISDKConfigProps p.api_version = api - await logConvertSpec(name, p, lookerVersions) + await logConvertSpec(name, specs[api], p, true) } } ;(async () => { diff --git a/packages/sdk-codegen-scripts/src/utils.spec.ts b/packages/sdk-codegen-scripts/src/utils.spec.ts new file mode 100644 index 000000000..01cbfcd4e --- /dev/null +++ b/packages/sdk-codegen-scripts/src/utils.spec.ts @@ -0,0 +1,203 @@ +/* + + MIT License + + Copyright (c) 2021 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +import fs from 'fs' +import { codeGenerators } from '@looker/sdk-codegen' +import { doArgs, loadSpecs, prepGen } from './utils' + +const mockIni = ` +[Looker] +base_url=https://self-signed.looker.com:19999 +client_id=id +` + +const mockVersions = { + looker_release_version: '21.0.25', + current_version: { + version: '3.1', + full_version: '3.1.0', + status: 'current', + swagger_url: 'https://self-signed.looker.com:19999/api/3.1/swagger.json', + }, + supported_versions: [ + { + version: '2.99', + full_version: '2.99.0', + status: 'internal_test', + swagger_url: 'https://self-signed.looker.com:19999/api/2.99/swagger.json', + }, + { + version: '3.0', + full_version: '3.0.0', + status: 'legacy', + swagger_url: 'https://self-signed.looker.com:19999/api/3.0/swagger.json', + }, + { + version: '3.1', + full_version: '3.1.0', + status: 'current', + swagger_url: 'https://self-signed.looker.com:19999/api/3.1/swagger.json', + }, + { + version: '4.0', + full_version: '4.0.21.0', + status: 'experimental', + swagger_url: 'https://self-signed.looker.com:19999/api/4.0/swagger.json', + }, + { + version: '4.0', + full_version: '4.0.21.0', + status: 'undocumented', + swagger_url: 'https://self-signed.looker.com:19999/api/4.0/undoc.json', + }, + ], + api_server_url: 'https://self-signed.looker.com:19999', +} + +describe.skip('utils', () => { + jest.mock('fs') + beforeAll(() => { + jest.spyOn(fs, 'readFileSync').mockImplementation((path, _options) => { + path = path.toString() + if (path.match(/looker\.ini/)) return mockIni + if (path.match(/\.env/)) return '' + return JSON.stringify(mockVersions) + }) + jest.spyOn(fs, 'existsSync').mockImplementation((_path) => { + return true + }) + }) + + describe('doArgs', () => { + test('no args', () => { + const expLangs = codeGenerators + .filter((l) => l.factory !== undefined) + .map((l) => l.language) + + const actual = doArgs([]) + expect(actual).toBeDefined() + expect(actual.versions).toBeUndefined() + expect(actual.languages).toEqual(expLangs) + }) + test('ts,python,cs,typescript', () => { + const expected = ['Typescript', 'Python', 'Csharp'] + + const actual = doArgs(['ts,python,cs,typescript']) + expect(actual).toBeDefined() + expect(actual.versions).toBeUndefined() + expect(actual.languages).toEqual(expected) + }) + test('-v foo.json kotlin', () => { + const expected = ['Kotlin'] + const actual = doArgs('-v foo.json kotlin'.split(' ')) + expect(actual).toBeDefined() + expect(actual.languages).toEqual(expected) + expect(actual.versions).toEqual(mockVersions) + }) + test('ts,py --versions foo.json kotlin', () => { + const expected = ['Typescript', 'Python', 'Kotlin'] + const actual = doArgs('ts,py --versions foo.json kotlin'.split(' ')) + expect(actual).toBeDefined() + expect(actual.languages).toEqual(expected) + expect(actual.versions).toEqual(mockVersions) + }) + }) + + describe('prepGen', () => { + test('default prepGen', async () => { + const expLangs = codeGenerators + .filter((l) => l.factory !== undefined) + .map((l) => l.language) + const release = mockVersions.looker_release_version + .split('.', 2) + .join('.') + const actual = await prepGen([]) + expect(actual).toBeDefined() + expect(actual.languages).toEqual(expLangs) + expect(actual.lookerVersion).toEqual(release) + expect(actual.lookerVersions).toEqual(mockVersions) + expect(actual.apis).toEqual(['3.1', '4.0']) + expect(actual.name).toEqual('Looker') + expect(actual.lastApi).toEqual('4.0') + expect(actual.props.base_url).toEqual( + 'https://self-signed.looker.com:19999' + ) + }) + test('prepGen ts', async () => { + const expLangs = codeGenerators + .filter((l) => l.factory !== undefined) + .map((l) => l.language) + const release = mockVersions.looker_release_version + .split('.', 2) + .join('.') + const actual = await prepGen(['ts']) + expect(actual).toBeDefined() + expect(actual.languages).toEqual(expLangs) + expect(actual.lookerVersion).toEqual(release) + expect(actual.lookerVersions).toEqual(mockVersions) + expect(actual.apis).toEqual(['3.1', '4.0']) + expect(actual.name).toEqual('Looker') + expect(actual.lastApi).toEqual('4.0') + expect(actual.props.base_url).toEqual( + 'https://self-signed.looker.com:19999' + ) + }) + test('-v foo.json ts', async () => { + const langs = ['Typescript'] + const release = mockVersions.looker_release_version + .split('.', 2) + .join('.') + const actual = await prepGen('-v foo.json ts'.split(' ')) + expect(actual).toBeDefined() + expect(actual.languages).toEqual(langs) + expect(actual.lookerVersion).toEqual(release) + expect(actual.lookerVersions).toEqual(mockVersions) + expect(actual.apis).toEqual(['3.1', '4.0']) + expect(actual.name).toEqual('Looker') + expect(actual.lastApi).toEqual('4.0') + expect(actual.props.base_url).toEqual( + 'https://self-signed.looker.com:19999' + ) + }) + }) + + describe('loadSpecs', () => { + test('load mockVersions', async () => { + const config = await prepGen('-v foo.json'.split(' ')) + expect(config).toBeDefined() + const actual = await loadSpecs(config, false) + expect(actual).toBeDefined() + expect(config.apis).toEqual(['3.1', '4.0', '4.0u']) + }) + test('no version, with ts', async () => { + const config = await prepGen(['ts']) + expect(config).toBeDefined() + const actual = await loadSpecs(config, false) + expect(actual).toBeDefined() + expect(config.apis).toEqual(['3.1', '4.0']) + }) + }) +}) diff --git a/packages/sdk-codegen-scripts/src/utils.ts b/packages/sdk-codegen-scripts/src/utils.ts new file mode 100644 index 000000000..f1c2843c0 --- /dev/null +++ b/packages/sdk-codegen-scripts/src/utils.ts @@ -0,0 +1,223 @@ +/* + + MIT License + + Copyright (c) 2021 Looker Data Sciences, Inc. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + + */ + +import { + ApiModel, + codeGenerators, + findGenerator, + getSpecsFromVersions, + ILookerVersions, + SpecItem, + upgradeSpecObject, +} from '@looker/sdk-codegen' +import { log } from '@looker/sdk-codegen-utils' +import { createJsonFile, readFileSync } from './nodeUtils' +import { ISDKConfigProps, SDKConfig } from './sdkConfig' +import { + authGetUrl, + fetchLookerVersion, + fetchLookerVersions, + openApiFileName, + specPath, + swaggerFileName, +} from './fetchSpec' + +export const apiVersions = (props: any) => { + const versions = props.api_versions ?? '3.1,4.0' + return versions.split(',') +} + +export interface IGenProps { + /** Languages to generate */ + languages: string[] + /** name of first INI config section, used for package name */ + name: string + /** SDK config properties from the first section */ + props: ISDKConfigProps + /** api specifications */ + lookerVersions: ILookerVersions + /** Release version */ + lookerVersion: string + /** Api version collection */ + apis: string[] + /** Last API version */ + lastApi: string +} + +const generatorHelp = () => { + log(`sdkGen [languages...] [-v|--versions ] [-h|--help]`) + process.exit(0) +} + +/** + * Process command-line switches for versions payload and languages + * @param args + */ +export const doArgs = (args: string[]) => { + let versions: ILookerVersions | undefined + + const langs: string[] = [] + if (args.length > 0 && args.toString().toLowerCase() !== 'all') { + let i = 0 + while (i < args.length) { + const arg = args[i].toLowerCase() + switch (arg) { + case '-v': + case '--versions': + { + i++ + const content = readFileSync(args[i], 'utf8') + versions = JSON.parse(content) + } + break + case '-h': + case '--help': + generatorHelp() + break + default: + { + const values = arg.split(',').filter((v) => v.trim()) + values.forEach((v) => { + const gen = findGenerator(v.trim()) + if (gen) { + // Valid language match + langs.push(gen.language) + } + }) + } + break + } + i++ + } + } + + // Default languages to all + const languages = (langs.length > 0 + ? langs + : codeGenerators + .filter((l) => l.factory !== undefined) + .map((l) => l.language) + ).filter((value, index, all) => all.indexOf(value) === index) + + return { languages, versions } +} + +/** + * Load the default configuration settings from looker.ini + */ +export const loadConfig = () => { + const config = SDKConfig() + const [name, props] = Object.entries(config)[0] + return { name, props } +} + +/** + * Prepare the generator configuration from all configuration options and return the config + * @param args command-line style arguments to parse. + */ +export const prepGen = async (args: string[]): Promise => { + const { languages, versions } = doArgs(args) + const { name, props } = loadConfig() + let lookerVersions + let lookerVersion = '' + try { + if (versions) { + lookerVersions = versions + } else { + lookerVersions = await fetchLookerVersions(props) + createJsonFile( + `${specPath}/versions.json`, + JSON.stringify(lookerVersions, null, 2) + ) + } + lookerVersion = await fetchLookerVersion(props, lookerVersions) + } catch { + // Looker server is not required, so default values for the generator + lookerVersions = { + supported_versions: [ + { + version: '3.1', + status: 'stable', + full_version: '', + swagger_url: `https://${props.base_url}/api/3.1/swagger.json`, + }, + { + version: '4.0', + status: 'experimental', + full_version: '', + swagger_url: `https://${props.base_url}/api/4.0/swagger.json`, + }, + ], + } + lookerVersion = '' + } + // Iterate through all specified API versions + const apis = apiVersions(props) + const lastApi = apis[apis.length - 1] + + return { + name, + props, + languages, + lookerVersions: lookerVersions as ILookerVersions, + lookerVersion, + apis, + lastApi, + } +} + +/** + * Load and save specifications from the versions file + * @param config generation configuration properties + * @param fetch false to skip fetching the spec, true to fetch. Defaults to true + */ +export const loadSpecs = async (config: IGenProps, fetch = true) => { + const specFetch = async (spec: SpecItem) => { + if (!fetch) return undefined + if (!spec.specURL) return undefined + const p = { ...config.props, ...{ api_version: spec.version } } + let source = await authGetUrl(p, spec.specURL) + if (typeof source === 'string') source = JSON.parse(source) + const upgrade = upgradeSpecObject(source) + spec.api = ApiModel.fromJson(upgrade) + if (/^http[s]?:\/\//i.test(spec.specURL)) { + const swagger = JSON.stringify(source, null, 2) + const oas = JSON.stringify(upgrade, null, 2) + const swaggerName = swaggerFileName(config.name, spec.key) + const oaName = openApiFileName(config.name, spec.key) + createJsonFile(swaggerName, swagger) + createJsonFile(oaName, oas) + log(`fetched and saved ${swaggerName} and converted it to ${oaName}`) + } + return spec.api + } + + const specs = await getSpecsFromVersions(config.lookerVersions, specFetch) + // NOTE: Reaching in and updating the api versions list from established spec keys + config.apis = Object.keys(specs) + + return specs +} diff --git a/packages/sdk-codegen-utils/README.md b/packages/sdk-codegen-utils/README.md index e8bef8ed3..fbd025490 100644 --- a/packages/sdk-codegen-utils/README.md +++ b/packages/sdk-codegen-utils/README.md @@ -2,6 +2,11 @@ This package contains utility routines for the SDK code generator projects -The code in this package is really trivial and probably not pulling in as a dependency to anything. +The code in this package is really trivial and should not be used directly in your own projects. + +This package has no node dependencies. + +**DEPRECATED AND NOT SUPPORTED**: Looker does not support direct use of this package, other than via direct use in the +[SDK Codegen project](https://github.com/looker-open-source/sdk-codegen) repository. See the [SDK Codegen project](https://github.com/looker-open-source/sdk-codegen) repository for more information. diff --git a/packages/sdk-codegen-utils/package.json b/packages/sdk-codegen-utils/package.json index 564008bc2..ec35a4774 100644 --- a/packages/sdk-codegen-utils/package.json +++ b/packages/sdk-codegen-utils/package.json @@ -1,6 +1,6 @@ { "name": "@looker/sdk-codegen-utils", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker SDK Codegen utils", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -9,7 +9,7 @@ ], "author": "Looker", "license": "MIT", - "private": true, + "private": false, "publishConfig": { "access": "public" }, diff --git a/packages/sdk-codegen-utils/src/utils.ts b/packages/sdk-codegen-utils/src/utils.ts index 29476585d..a05c86cc3 100644 --- a/packages/sdk-codegen-utils/src/utils.ts +++ b/packages/sdk-codegen-utils/src/utils.ts @@ -76,16 +76,3 @@ export const debug = (message: any, value?: any) => { } export const dump = (value: any) => log(JSON.stringify(value, null, 2)) - -export const commentBlock = ( - text: string | undefined, - indent = '', - commentStr = '// ' -) => { - if (!text) return '' - text = text.trim() - if (!text) return '' - const indentation = indent + commentStr - const parts = text.split('\n').map((x) => `${indentation}${x}`.trimRight()) - return parts.join('\n') -} diff --git a/packages/sdk-codegen/README.md b/packages/sdk-codegen/README.md index 7c073de8e..23ea13292 100644 --- a/packages/sdk-codegen/README.md +++ b/packages/sdk-codegen/README.md @@ -2,6 +2,11 @@ This package contains the OpenAPI analysis files, and all source code necessary to generate method and type declarations for supported languages. +This package can be used in a browser because it has no node dependencies. + +**DEPRECATED AND NOT SUPPORTED**: Looker does not support direct use of this package, other than via direct use in the +[SDK Codegen project](https://github.com/looker-open-source/sdk-codegen) repository. + ## Supported languages This package includes the following language generators: diff --git a/packages/sdk-codegen/package.json b/packages/sdk-codegen/package.json index 1ea29a90f..1fad805dd 100644 --- a/packages/sdk-codegen/package.json +++ b/packages/sdk-codegen/package.json @@ -1,6 +1,6 @@ { "name": "@looker/sdk-codegen", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker SDK Codegen core", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -9,7 +9,7 @@ ], "author": "Looker", "license": "MIT", - "private": true, + "private": false, "publishConfig": { "access": "public" }, @@ -33,9 +33,10 @@ "watch": "yarn lerna exec --scope @looker/sdk-codegen --stream 'BABEL_ENV=build babel src --root-mode upward --out-dir lib --source-maps --extensions .ts,.tsx --no-comments --watch'" }, "dependencies": { - "@looker/sdk-codegen-utils": "^21.0.9", - "@looker/sdk-node": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/sdk": "^21.0.10", + "@looker/sdk-codegen-utils": "^21.0.10", + "@looker/sdk-node": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "blueimp-md5": "^2.13.0", "openapi3-ts": "^1.3.0" }, diff --git a/packages/sdk-codegen/src/codeGen.ts b/packages/sdk-codegen/src/codeGen.ts index 4db6a338b..8f82bf7db 100644 --- a/packages/sdk-codegen/src/codeGen.ts +++ b/packages/sdk-codegen/src/codeGen.ts @@ -24,7 +24,6 @@ */ -import { commentBlock } from '@looker/sdk-codegen-utils' import { DelimArray } from '@looker/sdk-rtl' import { ApiModel, @@ -41,10 +40,27 @@ import { mayQuote, Type, } from './sdkModels' +import { SpecItem } from './specConverter' +export const commentBlock = ( + text: string | undefined, + indent = '', + commentStr = '// ' +) => { + if (!text) return '' + text = text.trim() + if (!text) return '' + const indentation = indent + commentStr + const parts = text.split('\n').map((x) => `${indentation}${x}`.trimRight()) + return parts.join('\n') +} + +/** Version and spec references for the generator */ export interface IVersionInfo { + /** Server release version (Not the API version) */ lookerVersion: string - apiVersion: string + /** API specification for generating the SDK */ + spec: SpecItem } /** @@ -675,10 +691,10 @@ export abstract class CodeGen implements ICodeGen { apiPath = '' constructor(public api: ApiModel, public versions?: IVersionInfo) { - if (versions && versions.apiVersion) { - this.apiVersion = versions.apiVersion - this.apiRef = this.apiVersion.replace('.', '') - this.apiPath = `/${this.apiVersion}` + if (versions && versions.spec) { + this.apiVersion = versions.spec.version + this.apiPath = `/${versions.spec.key}` + this.apiRef = versions.spec.key.replace('.', '') this.packageName = this.supportsMultiApi() ? `Looker${this.apiRef}SDK` : `LookerSDK` diff --git a/packages/sdk-codegen/src/codeGenerators.spec.ts b/packages/sdk-codegen/src/codeGenerators.spec.ts index 54ec292c3..86abb1c32 100644 --- a/packages/sdk-codegen/src/codeGenerators.spec.ts +++ b/packages/sdk-codegen/src/codeGenerators.spec.ts @@ -67,6 +67,14 @@ describe('generator factory', () => { expect(actual).toBeDefined() expect(actual?.language).toEqual('Typescript') }) + it('returns generator by implicit file extension', () => { + let actual = findGenerator('ts') + expect(actual).toBeDefined() + expect(actual?.language).toEqual('Typescript') + actual = findGenerator('TSX') + expect(actual).toBeDefined() + expect(actual?.language).toEqual('Typescript') + }) }) describe('getCodeGenerator', () => { diff --git a/packages/sdk-codegen/src/codeGenerators.ts b/packages/sdk-codegen/src/codeGenerators.ts index 7d100c123..13066686e 100644 --- a/packages/sdk-codegen/src/codeGenerators.ts +++ b/packages/sdk-codegen/src/codeGenerators.ts @@ -131,9 +131,10 @@ export const findGenerator = (target: string) => { // Convenience alias return codeGenerators.find( (item) => - target.match(item.extension) || item.language.toLocaleLowerCase() === target || - item.label?.toLocaleLowerCase() === target + item.label?.toLocaleLowerCase() === target || + target.match(item.extension) || + ('.' + target).match(item.extension) ) } diff --git a/packages/sdk-codegen/src/csharp.gen.ts b/packages/sdk-codegen/src/csharp.gen.ts index c80fee0a4..0d5d7fd95 100644 --- a/packages/sdk-codegen/src/csharp.gen.ts +++ b/packages/sdk-codegen/src/csharp.gen.ts @@ -24,8 +24,7 @@ */ -import { commentBlock } from '@looker/sdk-codegen-utils' -import { CodeGen, IMappedType } from './codeGen' +import { CodeGen, IMappedType, commentBlock } from './codeGen' import { IMethod, IParameter, @@ -157,7 +156,7 @@ using Password = System.String; // ReSharper disable InconsistentNaming ${this.commentHeader('', this.warnEditing())} -namespace Looker.SDK.API${this.apiRef} +namespace Looker.SDK.API${this.apiRef} { ` diff --git a/packages/sdk-codegen/src/go.gen.ts b/packages/sdk-codegen/src/go.gen.ts index 71385e4aa..1c02457bd 100644 --- a/packages/sdk-codegen/src/go.gen.ts +++ b/packages/sdk-codegen/src/go.gen.ts @@ -24,8 +24,7 @@ */ -import { commentBlock } from '@looker/sdk-codegen-utils' -import { CodeGen, IMappedType, IVersionInfo } from './codeGen' +import { CodeGen, IMappedType, IVersionInfo, commentBlock } from './codeGen' import { ApiModel, Arg, diff --git a/packages/sdk-codegen/src/kotlin.gen.ts b/packages/sdk-codegen/src/kotlin.gen.ts index e04200131..7c0b1f441 100644 --- a/packages/sdk-codegen/src/kotlin.gen.ts +++ b/packages/sdk-codegen/src/kotlin.gen.ts @@ -24,7 +24,6 @@ */ -import { commentBlock } from '@looker/sdk-codegen-utils' import { Arg, EnumType, @@ -36,7 +35,7 @@ import { mayQuote, strBody, } from './sdkModels' -import { IMappedType, CodeGen } from './codeGen' +import { IMappedType, CodeGen, commentBlock } from './codeGen' export class KotlinGen extends CodeGen { codePath = './kotlin/src/main/com/' diff --git a/packages/sdk-codegen/src/pseudo.gen.spec.ts b/packages/sdk-codegen/src/pseudo.gen.spec.ts index 77a3aaf3b..a79277e74 100644 --- a/packages/sdk-codegen/src/pseudo.gen.spec.ts +++ b/packages/sdk-codegen/src/pseudo.gen.spec.ts @@ -40,7 +40,8 @@ describe('pseudocode', () => { const expected = `create_user_credentials_email( user_id: int64, body: CredentialsEmail, - [fields: string]): CredentialsEmail` + [fields: string] +): CredentialsEmail` const actual = gen.methodSignature('', method) expect(actual).toEqual(expected) }) @@ -51,6 +52,17 @@ describe('pseudocode', () => { const actual = gen.methodSignature('', method) expect(actual).toEqual(expected) }) + test('import_lookml_dashboard', () => { + const method = apiTestModel.methods.import_lookml_dashboard + const expected = `import_lookml_dashboard( + lookml_dashboard_id: string, + space_id: string, + [body: Dashboard], + [raw_locale: boolean] +): Dashboard` + const actual = gen.methodSignature('', method) + expect(actual).toEqual(expected) + }) }) describe('declare type', () => { it('declared a type', () => { diff --git a/packages/sdk-codegen/src/sdkModels.ts b/packages/sdk-codegen/src/sdkModels.ts index 6c230e1a3..0efab7c31 100644 --- a/packages/sdk-codegen/src/sdkModels.ts +++ b/packages/sdk-codegen/src/sdkModels.ts @@ -2055,8 +2055,8 @@ export class ApiModel implements ISymbolTable, IApiModel { if (!resolved) { throw new Error(`Could not resolve ${JSON.stringify(schema)}`) } - if (style === 'simple') { - // FKA 'csv' + if (style === 'simple' || style === 'form') { + // FKA 'csv' .. OpenAPI converter now uses "form" instead of "simple" for this return new DelimArrayType(resolved, schema) } if (this.schemaHasEnums(schema)) { diff --git a/packages/sdk-codegen/src/specConverter.spec.ts b/packages/sdk-codegen/src/specConverter.spec.ts index 4bb18e06f..ecf0b6df6 100644 --- a/packages/sdk-codegen/src/specConverter.spec.ts +++ b/packages/sdk-codegen/src/specConverter.spec.ts @@ -25,7 +25,7 @@ */ import { readFileSync } from 'fs' -import { isEmpty } from 'lodash' +import { cloneDeep, isEmpty } from 'lodash' import { NodeSettingsIniFile, LookerNodeSDK } from '@looker/sdk-node' import { fixConversion, @@ -38,6 +38,7 @@ import { getSpecLinks, loadSpecs, ISpecItem, + getSpecsFromVersions, } from './specConverter' import { TestConfig } from './testUtils' import { compareSpecs } from './specDiff' @@ -524,4 +525,101 @@ describe('spec conversion', () => { expect(diff).toHaveLength(0) }) }) + + describe('getSpecsFromVersions', () => { + const versions = { + looker_release_version: '21.3.0', + current_version: { + version: '3.1', + full_version: '3.1.0', + status: 'current', + swagger_url: 'http://localhost:19999/api/3.1/swagger.json', + }, + supported_versions: [ + { + version: '2.99', + full_version: '2.99.0', + status: 'internal_test', + swagger_url: 'http://localhost:19999/api/2.99/swagger.json', + }, + { + version: '3.0', + full_version: '3.0.0', + status: 'legacy', + swagger_url: 'http://localhost:19999/api/3.0/swagger.json', + }, + { + version: '3.1', + full_version: '3.1.0', + status: 'current', + swagger_url: 'http://localhost:19999/api/3.1/swagger.json', + }, + { + version: '4.0', + full_version: '4.0.21.3', + status: 'experimental', + swagger_url: 'http://localhost:19999/api/4.0/swagger.json', + }, + ], + api_server_url: 'http://localhost:19999', + } + + test('only gets supported specifications', async () => { + const actual = await getSpecsFromVersions(versions) + expect(Object.keys(actual)).toEqual(['3.1', '4.0']) + }) + + test('current is the default spec', async () => { + const specs = await getSpecsFromVersions(versions) + const actual = Object.entries(specs).find( + ([_, a]) => a.status === 'current' + ) + expect(actual).toBeDefined() + if (actual) { + const [, current] = actual + expect(current).toBeDefined() + expect(current.status).toEqual('current') + expect(current.isDefault).toEqual(true) + } + }) + + test('specs have unique keys', async () => { + const moar = cloneDeep(versions) + moar.supported_versions.push( + { + version: '4.0', + full_version: 'full', + status: 'un', + swagger_url: 'http://localhost:19999/api/4.0/u.json', + }, + { + version: '4.0', + full_version: 'full', + status: 'un', + swagger_url: 'http://localhost:19999/api/4.0/un.json', + }, + { + version: '4.0', + full_version: 'full', + status: 'un', + swagger_url: 'http://localhost:19999/api/4.0/un3.json', + }, + { + version: '4.0', + full_version: 'full', + status: 'un', + swagger_url: 'http://localhost:19999/api/4.0/un4.json', + } + ) + const actual = await getSpecsFromVersions(moar) + expect(Object.keys(actual)).toEqual([ + '3.1', + '4.0', + '4.0u', + '4.0un', + '4.0un3', + '4.0un4', + ]) + }) + }) }) diff --git a/packages/sdk-codegen/src/specConverter.ts b/packages/sdk-codegen/src/specConverter.ts index c32639372..6e860b46e 100644 --- a/packages/sdk-codegen/src/specConverter.ts +++ b/packages/sdk-codegen/src/specConverter.ts @@ -26,7 +26,8 @@ import { isEmpty } from 'lodash' import { APIMethods } from '@looker/sdk-rtl' -import { ApiModel, ArgValues, IApiModel } from './sdkModels' +import { IApiVersion, IApiVersionElement } from '@looker/sdk' +import { ApiModel, ArgValues, IApiModel, KeyedCollection } from './sdkModels' const warn = (warning: string) => { throw new Error(warning) @@ -48,8 +49,29 @@ export interface MimeFormats { consumes: string[] } +/** codegen specification item */ +export interface SpecItem { + /** Key for specification in collection. Duplicated for atomic passing */ + key: string + /** API version status */ + status: string // 'current' | 'deprecated' | 'experimental' | 'stable' + /** API version of spec */ + version: string + /** true if this is the default spec */ + isDefault: boolean + /** Compiled version of spec */ + api?: ApiModel + /** URL for retrieving spec */ + specURL?: string + /** string content of spec */ + specContent?: string +} + +/** Keyed collection of specification items */ +export type SpecList = KeyedCollection + /** - * Looker spec version item + * Looker specification version item from versions payload */ export interface ISpecItem { /** Abbreviated version of the API */ @@ -62,6 +84,69 @@ export interface ISpecItem { swagger_url: string } +/** + * Callback for fetching and compiling specification to ApiModel + */ +export type SpecFetcher = (spec: SpecItem) => Promise + +/** + * Return all public API specifications from an ApiVersion payload + * @param versions payload from a Looker server + * @param fetcher fetches and compiles spec to ApiModel + */ +export const getSpecsFromVersions = async ( + versions: IApiVersion, + fetcher: SpecFetcher | undefined = undefined +): Promise => { + const items = {} + + /** + * Create a unique spec key for this version + * @param v version to identify + */ + const uniqueId = (v: IApiVersionElement) => { + let specKey = v.version || 'api' + const max = v.status?.length || 0 + let frag = 1 + while (items[specKey]) { + if (frag <= max) { + // More than one spec for this version + specKey = `${v.version}${v.status?.substr(0, frag)}` + } else { + specKey = `${v.version}${v.status}${frag}` + } + frag++ + } + return specKey + } + + if (versions.supported_versions) { + for (const v of versions.supported_versions) { + // Tell Typescript these are all defined because IApiVersion definition is lax + if (v.status && v.version && v.swagger_url) { + if ( + v.status !== 'internal_test' && + v.status !== 'deprecated' && + v.status !== 'legacy' + ) { + const spec: SpecItem = { + key: uniqueId(v), + status: v.status, + version: v.version, + isDefault: v.status === 'current', + specURL: v.swagger_url, + } + if (fetcher) { + spec.api = await fetcher(spec) + } + items[spec.key] = spec + } + } + } + } + return items +} + /** * Payload returned by the Looker /versions endpoint */ @@ -72,8 +157,12 @@ export interface ILookerVersions { current_version: ISpecItem /** All API versions */ supported_versions: ISpecItem[] + /** API server url */ + api_server_url: string } +// TODO this work was duplicated in API Explorer. Need to merge and use one version. + /** * Api Specification with on-demand determination of values */ @@ -90,6 +179,7 @@ export interface IApiSpecLink { api: IApiModel } +// TODO this work was duplicated in API Explorer. Need to merge and use one version. export type SpecLinks = IApiSpecLink[] /** @@ -478,8 +568,8 @@ export const upgradeSpecObject = (spec: any) => { const produces = spec.produces || [appJson] const formats = { produces, consumes } const { paths, requestBodies } = convertPathsAndBodies(spec.paths, formats) - // TODO create a requestBodies entry for every struct used > 1x - // TODO reassign op.requestBody for all requestBodies entries + // TODO create a requestBodies entry for every struct used > 1x? + // TODO reassign op.requestBody for all requestBodies entries? const schemas = convertDefs(spec.definitions) const api = { openapi: '3.0.0', @@ -516,6 +606,7 @@ export const getLookerSpecs = async (sdk: APIMethods, apiServerUrl: string) => { return versions } +// TODO this work was duplicated in API Explorer. Need to merge and use one version. /** * Convert a Looker versions payload into API specification links * @param versions @@ -554,8 +645,9 @@ export const loadSpecs = async (sdk: APIMethods, links: SpecLinks) => { for (const spec of links) { if (isEmpty(spec.api)) { // Not parsed yet - const content = upgradeSpec(await sdk.ok(sdk.get(spec.url))) - spec.api = ApiModel.fromString(content) + const content = await sdk.ok(sdk.get(spec.url)) + const json = typeof content === 'string' ? JSON.parse(content) : content + spec.api = ApiModel.fromJson(upgradeSpecObject(json)) } } return links diff --git a/packages/sdk-codegen/src/swift.gen.ts b/packages/sdk-codegen/src/swift.gen.ts index 9320521b1..b11a2a645 100644 --- a/packages/sdk-codegen/src/swift.gen.ts +++ b/packages/sdk-codegen/src/swift.gen.ts @@ -24,7 +24,6 @@ */ -import { commentBlock } from '@looker/sdk-codegen-utils' import { Arg, EnumType, @@ -36,7 +35,7 @@ import { mayQuote, strBody, } from './sdkModels' -import { IMappedType, CodeGen } from './codeGen' +import { IMappedType, CodeGen, commentBlock } from './codeGen' export class SwiftGen extends CodeGen { codePath = './swift/' diff --git a/packages/sdk-codegen/src/typescript.gen.ts b/packages/sdk-codegen/src/typescript.gen.ts index fa829cc5a..5ea72d813 100644 --- a/packages/sdk-codegen/src/typescript.gen.ts +++ b/packages/sdk-codegen/src/typescript.gen.ts @@ -24,7 +24,6 @@ */ -import { commentBlock } from '@looker/sdk-codegen-utils' import { Arg, ArgValues, @@ -36,7 +35,13 @@ import { IType, strBody, } from './sdkModels' -import { CodeAssignment, CodeGen, IMappedType, trimInputs } from './codeGen' +import { + CodeAssignment, + CodeGen, + IMappedType, + trimInputs, + commentBlock, +} from './codeGen' /** * TypeScript code generator @@ -67,7 +72,7 @@ export class TypescriptGen extends CodeGen { useNamedArguments = false sdkFileName(baseFileName: string) { - return this.fileName(`${this.apiVersion}/${baseFileName}`) + return this.fileName(`${this.versions?.spec.key}/${baseFileName}`) } methodsPrologue(_indent: string) { diff --git a/packages/sdk-node/package.json b/packages/sdk-node/package.json index 24c87feb8..033b46fcf 100644 --- a/packages/sdk-node/package.json +++ b/packages/sdk-node/package.json @@ -1,6 +1,6 @@ { "name": "@looker/sdk-node", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker SDK Runtime for Node Library", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -38,8 +38,8 @@ "dotenv": "^8.2.0" }, "dependencies": { - "@looker/sdk": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/sdk": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "ini": "^1.3.8", "readable-stream": "^3.4.0", "request": "^2.88.0", diff --git a/packages/sdk-node/src/nodeSession.ts b/packages/sdk-node/src/nodeSession.ts index 166f1c8fe..179f8d62f 100644 --- a/packages/sdk-node/src/nodeSession.ts +++ b/packages/sdk-node/src/nodeSession.ts @@ -42,7 +42,7 @@ const strPost: HttpMethod = 'POST' const strDelete: HttpMethod = 'DELETE' export class NodeSession extends AuthSession { - private readonly apiPath: string = '/api/3.1' + private readonly apiPath: string = '/api/4.0' _authToken: AuthToken = new AuthToken() _sudoToken: AuthToken = new AuthToken() diff --git a/packages/sdk-rtl/package.json b/packages/sdk-rtl/package.json index d06c62853..dddaa9af5 100644 --- a/packages/sdk-rtl/package.json +++ b/packages/sdk-rtl/package.json @@ -1,6 +1,6 @@ { "name": "@looker/sdk-rtl", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker SDK Runtime Library", "main": "lib/index.js", "module": "lib/esm/index.js", diff --git a/packages/sdk-rtl/src/browserSession.ts b/packages/sdk-rtl/src/browserSession.ts index 4a9d38898..52397ecb8 100644 --- a/packages/sdk-rtl/src/browserSession.ts +++ b/packages/sdk-rtl/src/browserSession.ts @@ -77,16 +77,21 @@ export class BrowserSession extends OAuthSession { */ delete props.credentials - /** - * replace the headers argument with required values - * Note: using new Headers() to construct the headers breaks CORS for the Looker API. Don't know why yet - */ - props.headers = { + const headers = { /** Provide the authentication information */ Authorization: `Bearer ${this.activeToken.access_token}`, /** Identify the SDK */ [LookerAppId]: this.settings.agentTag, } + /** + * replace the headers argument with required values + * Note: using new Headers() to construct the headers breaks CORS for the Looker API. Don't know why yet + */ + if (props.headers) { + props.headers = { ...props.headers, ...headers } + } else { + props.headers = headers + } } return props diff --git a/packages/sdk-rtl/src/transport.ts b/packages/sdk-rtl/src/transport.ts index 5d4d2e9f3..e62ff47fc 100644 --- a/packages/sdk-rtl/src/transport.ts +++ b/packages/sdk-rtl/src/transport.ts @@ -428,6 +428,9 @@ export function addQueryParams(path: string, obj?: Values) { * @returns a new `Error` object with the failure message */ export function sdkError(response: any) { + if (typeof response === 'string') { + return new Error(response) + } if ('error' in response) { const error = response.error if (typeof error === 'string') { diff --git a/packages/sdk/package.json b/packages/sdk/package.json index ac5215e31..44398d74d 100644 --- a/packages/sdk/package.json +++ b/packages/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@looker/sdk", - "version": "21.0.9", + "version": "21.0.10", "description": "Looker SDK", "main": "lib/index.js", "module": "lib/esm/index.js", @@ -43,7 +43,7 @@ "@types/request-promise-native": "^1.0.17" }, "dependencies": { - "@looker/sdk-rtl": "^21.0.9", + "@looker/sdk-rtl": "^21.0.10", "ini": "^1.3.8", "readable-stream": "^3.4.0", "request": "^2.88.0", diff --git a/packages/wholly-sheet/package.json b/packages/wholly-sheet/package.json index affd3fa51..cb0810c2b 100644 --- a/packages/wholly-sheet/package.json +++ b/packages/wholly-sheet/package.json @@ -1,6 +1,6 @@ { "name": "@looker/wholly-sheet", - "version": "21.0.9", + "version": "21.0.10", "description": "Google sheets API wrapper for data table mimicking", "main": "lib/esm/index.js", "typings": "lib/index.d.ts", @@ -31,13 +31,13 @@ "watch": "yarn lerna exec --scope @looker/wholly-sheet --stream 'BABEL_ENV=build babel src --root-mode upward --out-dir lib/esm --source-maps --extensions .ts,.tsx --no-comments --watch'" }, "dependencies": { - "@looker/sdk": "^21.0.9", - "@looker/sdk-rtl": "^21.0.9", + "@looker/sdk": "^21.0.10", + "@looker/sdk-rtl": "^21.0.10", "lodash": "^4.17.20", "uuid": "^8.3.1" }, "devDependencies": { - "@looker/sdk-node": "^21.0.9", + "@looker/sdk-node": "^21.0.10", "@types/uuid": "^8.3.0", "google-auth-library": "^6.1.0" }