Skip to content

Commit

Permalink
Use newly separated programming model package
Browse files Browse the repository at this point in the history
  • Loading branch information
ejizba committed Jul 15, 2022
1 parent 07e0fc4 commit 61fab91
Show file tree
Hide file tree
Showing 16 changed files with 684 additions and 430 deletions.
26 changes: 0 additions & 26 deletions azure-pipelines/build.yml
Expand Up @@ -68,38 +68,12 @@ jobs:
publishVstsFeed: 'e6a70c92-4128-439f-8012-382fe78d6396/f37f760c-aebd-443e-9714-ce725cd427df'
allowPackageConflicts: true
displayName: 'Push NuGet package to the AzureFunctionsPreRelease feed'
# In order for the SBOM to be accurate, we want to explicitly specify the components folder, but the types package doesn't have any components
# We'll create an empty folder that _would_ store the components if it had any
- bash: |
mkdir types/node_modules
displayName: 'mkdir types/node_modules'
- task: CopyFiles@2
displayName: 'Copy types files to staging'
inputs:
sourceFolder: '$(Build.SourcesDirectory)/types'
contents: |
index.d.ts
LICENSE
package.json
README.md
targetFolder: '$(Build.ArtifactStagingDirectory)/types'
cleanTargetFolder: true
- task: ManifestGeneratorTask@0
displayName: 'Generate SBOM for types'
inputs:
BuildDropPath: '$(Build.ArtifactStagingDirectory)/types'
BuildComponentPath: '$(Build.SourcesDirectory)/types/node_modules'
PackageName: 'Azure Functions Type Definitions'
- script: npm pack
displayName: 'npm pack types'
workingDirectory: '$(Build.ArtifactStagingDirectory)/types'
- task: CopyFiles@2
displayName: 'Copy packages to staging drop folder'
inputs:
sourceFolder: '$(Build.ArtifactStagingDirectory)'
contents: |
worker/*.nupkg
types/*.tgz
targetFolder: '$(Build.ArtifactStagingDirectory)/drop'
cleanTargetFolder: true
- task: PublishPipelineArtifact@1
Expand Down
53 changes: 22 additions & 31 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 4 additions & 7 deletions package.json
Expand Up @@ -5,13 +5,12 @@
"description": "Microsoft Azure Functions NodeJS Worker",
"license": "(MIT OR Apache-2.0)",
"dependencies": {
"@azure/functions": "file:../js-framework/azure-functions-3.4.0.tgz",
"@grpc/grpc-js": "^1.2.7",
"@grpc/proto-loader": "^0.6.4",
"blocked-at": "^1.2.0",
"fs-extra": "^10.0.1",
"long": "^4.0.0",
"minimist": "^1.2.5",
"uuid": "^8.3.0"
"minimist": "^1.2.5"
},
"devDependencies": {
"@types/blocked-at": "^1.0.1",
Expand Down Expand Up @@ -49,8 +48,6 @@
"sinon": "^7.0.0",
"ts-node": "^3.3.0",
"typescript": "^4.5.5",
"typescript3": "npm:typescript@~3.7.0",
"typescript4": "npm:typescript@~4.0.0",
"webpack": "^5.72.1",
"webpack-cli": "^4.8.0"
},
Expand All @@ -64,13 +61,13 @@
},
"scripts": {
"clean": "rimraf dist && rimraf azure-functions-language-worker-protobuf/src/rpc*",
"build": "rimraf dist && npm run gen && shx mkdir -p dist/azure-functions-language-worker-protobuf/src && shx cp azure-functions-language-worker-protobuf/src/rpc.* dist/azure-functions-language-worker-protobuf/src/. && node ./node_modules/typescript/bin/tsc",
"build": "rimraf dist && npm run gen && shx mkdir -p dist/azure-functions-language-worker-protobuf/src && shx cp azure-functions-language-worker-protobuf/src/rpc.* dist/azure-functions-language-worker-protobuf/src/. && tsc",
"gen": "node scripts/generateProtos.js",
"test": "mocha -r ts-node/register \"./test/**/*.ts\" --reporter mocha-multi-reporters --reporter-options configFile=test/mochaReporterOptions.json",
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"updateVersion": "ts-node ./scripts/updateVersion.ts",
"watch": "node ./node_modules/typescript/bin/tsc --watch",
"watch": "tsc --watch",
"webpack": "webpack --mode production"
},
"files": [
Expand Down
31 changes: 2 additions & 29 deletions scripts/updateVersion.ts
Expand Up @@ -9,8 +9,6 @@ import * as semver from 'semver';

const repoRoot = path.join(__dirname, '..');
const packageJsonPath = path.join(repoRoot, 'package.json');
const typesRoot = path.join(repoRoot, 'types');
const typesPackageJsonPath = path.join(typesRoot, 'package.json');
const nuspecPath = path.join(repoRoot, 'Worker.nuspec');
const nuspecVersionRegex = /<version>(.*)\$prereleaseSuffix\$<\/version>/i;
const constantsPath = path.join(repoRoot, 'src', 'constants.ts');
Expand All @@ -24,9 +22,6 @@ if (args.validate) {
} else {
console.log(`This script can be used to either update the version of the worker or validate that the repo is in a valid state with regards to versioning.
NOTE: For the types package, only the major & minor version need to match the worker. We follow the same pattern as DefinitelyTyped as described here:
https://github.com/DefinitelyTyped/DefinitelyTyped#how-do-definitely-typed-package-versions-relate-to-versions-of-the-corresponding-library
Example usage:
npm run updateVersion -- --version 3.3.0
Expand All @@ -39,35 +34,21 @@ function validateVersion() {
const packageJson = readJSONSync(packageJsonPath);
const packageJsonVersion = packageJson.version;

const typesPackageJson = readJSONSync(typesPackageJsonPath);
const typesPackageJsonVersion = typesPackageJson.version;

const nuspecVersion = getVersion(nuspecPath, nuspecVersionRegex);

const constantsVersion = getVersion(constantsPath, constantsVersionRegex);

console.log('Found the following versions:');
console.log(`- package.json: ${packageJsonVersion}`);
console.log(`- types/package.json: ${typesPackageJsonVersion}`);
console.log(`- Worker.nuspec: ${nuspecVersion}`);
console.log(`- src/constants.ts: ${constantsVersion}`);

const parsedVersion = semver.parse(packageJsonVersion);
const parsedTypesVersion = semver.parse(typesPackageJsonVersion);

if (
!packageJsonVersion ||
!nuspecVersion ||
!constantsVersion ||
!typesPackageJsonVersion ||
!parsedVersion ||
!parsedTypesVersion
) {

if (!packageJsonVersion || !nuspecVersion || !constantsVersion || !parsedVersion) {
throw new Error('Failed to detect valid versions in all expected files');
} else if (nuspecVersion !== packageJsonVersion || constantsVersion !== packageJsonVersion) {
throw new Error(`Worker versions do not match.`);
} else if (parsedVersion.major !== parsedTypesVersion.major || parsedVersion.minor !== parsedTypesVersion.minor) {
throw new Error(`Types package does not match the major/minor version of the worker.`);
} else {
console.log('Versions match! 🎉');
}
Expand All @@ -84,15 +65,7 @@ function getVersion(filePath: string, regex: RegExp): string {

function updateVersion(newVersion: string) {
updatePackageJsonVersion(repoRoot, newVersion);

if (newVersion.endsWith('.0')) {
updatePackageJsonVersion(typesRoot, newVersion);
} else {
console.log(`Skipping types/package.json because this is a patch version.`);
}

updateVersionByRegex(nuspecPath, nuspecVersionRegex, newVersion);

updateVersionByRegex(constantsPath, constantsVersionRegex, newVersion);
}

Expand Down
55 changes: 26 additions & 29 deletions src/FunctionLoader.ts
@@ -1,63 +1,60 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import { FunctionCallback } from '@azure/functions-core';
import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc';
import { FunctionInfo } from './FunctionInfo';
import { loadScriptFile } from './loadScriptFile';
import { PackageJson } from './parsers/parsePackageJson';
import { InternalException } from './utils/InternalException';
import { nonNullProp } from './utils/nonNull';

export interface IFunctionLoader {
load(functionId: string, metadata: rpc.IRpcFunctionMetadata, packageJson: PackageJson): Promise<void>;
getInfo(functionId: string): FunctionInfo;
getFunc(functionId: string): Function;
getRpcMetadata(functionId: string): rpc.IRpcFunctionMetadata;
getCallback(functionId: string): FunctionCallback;
}

export class FunctionLoader implements IFunctionLoader {
#loadedFunctions: {
[k: string]: {
info: FunctionInfo;
func: Function;
thisArg: unknown;
};
} = {};
interface LoadedFunction {
metadata: rpc.IRpcFunctionMetadata;
callback: FunctionCallback;
thisArg: unknown;
}

export class FunctionLoader<TContext = unknown> {
#loadedFunctions: { [k: string]: LoadedFunction | undefined } = {};

async load(functionId: string, metadata: rpc.IRpcFunctionMetadata, packageJson: PackageJson): Promise<void> {
if (metadata.isProxy === true) {
return;
}
const script: any = await loadScriptFile(nonNullProp(metadata, 'scriptFile'), packageJson);
const entryPoint = <string>(metadata && metadata.entryPoint);
const [userFunction, thisArg] = getEntryPoint(script, entryPoint);
this.#loadedFunctions[functionId] = {
info: new FunctionInfo(metadata),
func: userFunction,
thisArg,
};
const [callback, thisArg] = getEntryPoint(script, entryPoint);
this.#loadedFunctions[functionId] = { metadata, callback, thisArg };
}

getInfo(functionId: string): FunctionInfo {
const loadedFunction = this.#loadedFunctions[functionId];
if (loadedFunction && loadedFunction.info) {
return loadedFunction.info;
} else {
throw new InternalException(`Function info for '${functionId}' is not loaded and cannot be invoked.`);
}
getRpcMetadata(functionId: string): rpc.IRpcFunctionMetadata {
const loadedFunction = this.#getLoadedFunction(functionId);
return loadedFunction.metadata;
}

getCallback(functionId: string): FunctionCallback<TContext> {
const loadedFunction = this.#getLoadedFunction(functionId);
// `bind` is necessary to set the `this` arg, but it's also nice because it makes a clone of the function, preventing this invocation from affecting future invocations
return loadedFunction.callback.bind(loadedFunction.thisArg);
}

getFunc(functionId: string): Function {
#getLoadedFunction(functionId: string): LoadedFunction {
const loadedFunction = this.#loadedFunctions[functionId];
if (loadedFunction && loadedFunction.func) {
// `bind` is necessary to set the `this` arg, but it's also nice because it makes a clone of the function, preventing this invocation from affecting future invocations
return loadedFunction.func.bind(loadedFunction.thisArg);
if (loadedFunction) {
return loadedFunction;
} else {
throw new InternalException(`Function code for '${functionId}' is not loaded and cannot be invoked.`);
}
}
}

function getEntryPoint(f: any, entryPoint?: string): [Function, unknown] {
function getEntryPoint(f: any, entryPoint?: string): [FunctionCallback, unknown] {
let thisArg: unknown;
if (f !== null && typeof f === 'object') {
thisArg = f;
Expand Down
3 changes: 2 additions & 1 deletion src/WorkerChannel.ts
@@ -1,7 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import { HookCallback, HookContext, HookData } from '@azure/functions-core';
import { HookCallback, HookContext, HookData, ProgrammingModel } from '@azure/functions-core';
import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc';
import { Disposable } from './Disposable';
import { IFunctionLoader } from './FunctionLoader';
Expand All @@ -27,6 +27,7 @@ export class WorkerChannel {
* this hook data is limited to the app-level scope and persisted only for app-level hooks
*/
appLevelOnlyHookData: HookData = {};
programmingModel?: ProgrammingModel<unknown>;
#preInvocationHooks: HookCallback[] = [];
#postInvocationHooks: HookCallback[] = [];
#appStartHooks: HookCallback[] = [];
Expand Down
12 changes: 0 additions & 12 deletions src/constants.ts
Expand Up @@ -2,15 +2,3 @@
// Licensed under the MIT License.

export const version = '3.4.0';

export enum HeaderName {
contentType = 'content-type',
contentDisposition = 'content-disposition',
}

export enum MediaType {
multipartForm = 'multipart/form-data',
urlEncodedForm = 'application/x-www-form-urlencoded',
octetStream = 'application/octet-stream',
json = 'application/json',
}

0 comments on commit 61fab91

Please sign in to comment.