diff --git a/__tests__/plugin.test.ts b/__tests__/plugin.test.ts index a8d68e5c..e6e1a2f1 100644 --- a/__tests__/plugin.test.ts +++ b/__tests__/plugin.test.ts @@ -1,9 +1,13 @@ import TypeScriptPlugin from '../src'; -import { ServerlessTSInstance, ServerlessTSFunctionMap } from '../src/types'; +import { + ServerlessTSInstance, + ServerlessTSFunctionMap, + ServerlessTSProviderRuntimes, +} from '../src/types'; const createInstance = ( slsFunctions?: ServerlessTSFunctionMap, - globalRuntime?: string, + globalRuntime?: ServerlessTSProviderRuntimes, ): ServerlessTSInstance => ({ cli: { log: jest.fn(), @@ -16,9 +20,7 @@ const createInstance = ( name: 'aws', runtime: globalRuntime, }, - functions: slsFunctions ? slsFunctions : {}, - getFunction: jest.fn(), - getAllFunctions: jest.fn(), + functions: slsFunctions ?? {}, }, pluginManager: { spawn: jest.fn(), @@ -69,7 +71,9 @@ describe('functions', () => { const slsInstance = createInstance(functions, 'nodejs10.x'); const plugin = new TypeScriptPlugin(slsInstance, {}); - expect(Object.values(plugin.functions).map((fn) => fn.handler)).toEqual([ + expect( + Object.values(plugin.functions ?? {}).map((fn) => fn.handler), + ).toEqual([ 'tests/assets/hello.handler', 'tests/assets/world.handler', 'tests/assets/jsfile.create', @@ -82,9 +86,9 @@ describe('functions', () => { const slsInstance = createInstance(functions, 'python2.7'); const plugin = new TypeScriptPlugin(slsInstance, {}); - expect(Object.values(plugin.functions).map((fn) => fn.handler)).toEqual([ - 'tests/assets/world.handler', - ]); + expect( + Object.values(plugin.functions ?? {}).map((fn) => fn.handler), + ).toEqual(['tests/assets/world.handler']); }); }); @@ -93,7 +97,9 @@ describe('functions', () => { const slsInstance = createInstance(functions); const plugin = new TypeScriptPlugin(slsInstance, {}); - expect(Object.values(plugin.functions).map((fn) => fn.handler)).toEqual([ + expect( + Object.values(plugin.functions ?? {}).map((fn) => fn.handler), + ).toEqual([ 'tests/assets/hello.handler', 'tests/assets/world.handler', 'tests/assets/jsfile.create', diff --git a/package.json b/package.json index aae6f9a5..522eefd5 100644 --- a/package.json +++ b/package.json @@ -46,11 +46,11 @@ "node": ">=10.0.0" }, "devDependencies": { + "@serverless/typescript": "2.14.0", "@types/fs-extra": "9.0.4", "@types/jest": "26.0.16", "@types/lodash": "4.14.165", "@types/node": "14.14.10", - "@types/serverless": "1.78.12", "@typescript-eslint/eslint-plugin": "4.9.0", "@typescript-eslint/parser": "4.9.0", "eslint": "7.15.0", diff --git a/src/index.ts b/src/index.ts index d37865ca..b4dec6f7 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,11 +2,12 @@ import * as path from 'path'; import * as fse from 'fs-extra'; import _ from 'lodash'; import globby from 'globby'; +import { unwatchFile, watchFile, Stats } from 'fs-extra'; import { + ServerlessTSFunctionMap, ServerlessTSInstance, ServerlessTSOptions, - ServerlessTSFunction, } from './types'; import { extractFileNames, @@ -15,7 +16,6 @@ import { getSourceFiles, getFiles, } from './utils'; -import { unwatchFile, watchFile, Stats } from 'fs-extra'; const SERVERLESS_FOLDER = '.serverless'; const BUILD_FOLDER = '.build'; @@ -97,17 +97,18 @@ class TypeScriptPlugin { }; } - get functions(): { [key: string]: ServerlessTSFunction } { - const { options } = this; - const { service } = this.serverless; + get functions(): ServerlessTSFunctionMap { + const options = this.options; + const service = this.serverless.service; - const allFunctions = options.function - ? { - [options.function]: service.functions[options.function], - } - : service.functions; + const allFunctions = + options.function && service.functions + ? { + [options.function]: service.functions[options.function], + } + : service.functions; - if (Object.keys(allFunctions).length === 0) { + if (Object.keys(allFunctions ?? {}).length === 0) { throw new Error('There are no functions to package/deploy!'); } // Ensure we only handle runtimes that support Typescript @@ -213,18 +214,19 @@ class TypeScriptPlugin { */ async copyExtras(): Promise { this.serverless.cli.log('Copying Extras...'); - const { service } = this.serverless; - // include any "extras" from the "include" section - for await (const fn of Object.values(service.functions)) { - this.copyIncludes(fn.package.include); + const service = this.serverless.service; + // include any "extras" from the "include" section. The copy should be + // awaited for each extra before continuing. + for (const fn of Object.values(service.functions ?? {})) { + await this.copyIncludes(fn.package?.include); } if (this.serverless.package?.include) { - this.copyIncludes(this.serverless.package.include); + await this.copyIncludes(this.serverless.package.include); } this.serverless.cli.log('Finished Copying Extras'); } - private async copyIncludes(include: string[]): Promise { + private async copyIncludes(include: string[] | undefined): Promise { if (!include) return; const files = await globby(include); @@ -292,35 +294,34 @@ class TypeScriptPlugin { * packaging preferences. */ async moveArtifacts(): Promise { - const { service } = this.serverless; + const service = this.serverless.service; await fse.copy( path.join(this.originalServicePath, BUILD_FOLDER, SERVERLESS_FOLDER), path.join(this.originalServicePath, SERVERLESS_FOLDER), ); - if (this.options.function) { + if (this.options.function && service.functions) { const fn = service.functions[this.options.function]; - const artifact = fn.package.artifact; - if (artifact) { + if (fn.package?.artifact) { fn.package.artifact = path.join( this.originalServicePath, SERVERLESS_FOLDER, - path.basename(artifact), + path.basename(fn.package.artifact), ); } return; } if (this.serverless.package?.individually) { - const functionNames = Object.keys(this.functions); + const functionNames = Object.keys(this.functions ?? {}); functionNames.forEach((name) => { - const artifact = service.functions[name].package.artifact; - if (artifact) { - service.functions[name].package.artifact = path.join( + const pkg = service.functions?.[name].package; + if (pkg?.artifact) { + pkg.artifact = path.join( this.originalServicePath, SERVERLESS_FOLDER, - path.basename(artifact), + path.basename(pkg.artifact), ); } }); diff --git a/src/types.ts b/src/types.ts index 87f6c82c..aa20b6a2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,6 +1,7 @@ -import { FunctionDefinition } from 'serverless'; +import { AWS } from '@serverless/typescript'; -export type ServerlessTSFunctionMap = Record; +export type ServerlessTSFunctionMap = AWS['functions']; +export type ServerlessTSProviderRuntimes = AWS['provider']['runtime']; export interface ServerlessTSInstance { cli: { @@ -14,11 +15,8 @@ export interface ServerlessTSInstance { pluginManager: ServerlessTSPluginManager; } -export interface ServerlessTSService { - provider: { - name: string; - runtime?: string; - }; +export interface ServerlessTSService + extends Pick { custom?: { /** * Custom options to be passed to this plugin. @@ -32,9 +30,6 @@ export interface ServerlessTSService { exclude?: string[]; }; }; - functions: ServerlessTSFunctionMap; - getAllFunctions(): string[]; - getFunction(functionName: string): FunctionDefinition; } /** CLI Options */ @@ -49,13 +44,6 @@ export interface ServerlessTSOptions { tsconfigFilePath?: string; } -export interface ServerlessTSFunction - extends Pick { - handler: string; - package: ServerlessTSPackage; - runtime?: string; -} - /** Optional deployment packaging configuration */ export interface ServerlessTSPackage { /** Specify the directories and files which should be included in the deployment package */ diff --git a/src/utils.ts b/src/utils.ts index 793bb31d..b83c3364 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -13,8 +13,7 @@ import * as fse from 'fs-extra'; import _ from 'lodash'; import * as path from 'path'; import globby from 'globby'; - -import { ServerlessTSFunction } from './types'; +import { ServerlessTSFunctionMap } from './types'; export const makeDefaultTypescriptConfig = (): CompilerOptions => { const defaultTypescriptConfig: CompilerOptions = { @@ -34,7 +33,7 @@ export const makeDefaultTypescriptConfig = (): CompilerOptions => { export const extractFileNames = ( cwd: string, provider: string, - functions?: { [key: string]: ServerlessTSFunction }, + functions?: ServerlessTSFunctionMap, ): string[] => { // The Google provider will use the entrypoint not from the definition of the // handler function, but instead from the package.json:main field, or via a @@ -69,15 +68,16 @@ export const extractFileNames = ( return _.values(functions) .map((fn) => fn.handler) .map((h) => { - const fnName = _.last(h.split('.')); + const fnName = _.last(h?.split('.')); if (!fnName) { throw new Error( `Couldn't get exported function name; missing name after '.' in ${h}`, ); } - const fnNameLastAppearanceIndex = h.lastIndexOf(fnName); + // TODO: the handler name should exist. + const fnNameLastAppearanceIndex = h?.lastIndexOf(fnName); // replace only last instance to allow the same name for file and handler - const fileName = h.substring(0, fnNameLastAppearanceIndex); + const fileName = h?.substring(0, fnNameLastAppearanceIndex); // Check if the .ts files exists. If so return that to watch if (fse.existsSync(path.join(cwd, fileName + 'ts'))) { diff --git a/yarn.lock b/yarn.lock index b7491fee..c9cc33fc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -503,6 +503,11 @@ "@nodelib/fs.scandir" "2.1.3" fastq "^1.6.0" +"@serverless/typescript@^2.14.0": + version "2.14.0" + resolved "https://registry.yarnpkg.com/@serverless/typescript/-/typescript-2.14.0.tgz#79b04fd1d62622394bdc7c29e1d2f07f4ba91561" + integrity sha512-nL8jolUVHi6yxTwCxOymMsun2mtsG2osOYtSmirjyRllJZBQ79BESjoMPNmKR0ZDWyxjzPLP7rTe7XtTVeZvJA== + "@sinonjs/commons@^1.7.0": version "1.8.1" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.1.tgz#e7df00f98a203324f6dc7cc606cad9d4a8ab2217" @@ -621,11 +626,6 @@ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.1.5.tgz#b6ab3bba29e16b821d84e09ecfaded462b816b00" integrity sha512-UEyp8LwZ4Dg30kVU2Q3amHHyTn1jEdhCIE59ANed76GaT1Vp76DD3ZWSAxgCrw6wJ0TqeoBpqmfUHiUDPs//HQ== -"@types/serverless@1.78.12": - version "1.78.12" - resolved "https://registry.yarnpkg.com/@types/serverless/-/serverless-1.78.12.tgz#bd2d7bc448a482cc55822d3185a0669fde83171e" - integrity sha512-uUq3dg4PlRg1/n8pyKfCxOw9K9w/Gp2FPQMvgBJklCx6zIc9Dskpx23n1YLnSJ0k98Ri8boMu6ns6Eugap+muQ== - "@types/stack-utils@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.0.tgz#7036640b4e21cc2f259ae826ce843d277dad8cff"