Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@
},
"ignorePatterns": [
"**/*.js",
"**/*.mjs",
"**/*.cjs",
"dist",
"azure-functions-language-worker-protobuf"
]
Expand Down
74 changes: 38 additions & 36 deletions package-lock.json

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

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"@types/fs-extra": "^9.0.13",
"@types/minimist": "^1.2.2",
"@types/mocha": "^2.2.48",
"@types/mock-fs": "^4.13.1",
"@types/mock-require": "^2.0.1",
"@types/node": "^16.9.6",
"@types/semver": "^7.3.9",
Expand All @@ -26,6 +27,7 @@
"@typescript-eslint/parser": "^5.12.1",
"chai": "^4.2.0",
"chai-as-promised": "^7.1.1",
"escape-string-regexp": "^4.0.0",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-deprecation": "^1.3.2",
Expand Down Expand Up @@ -74,4 +76,4 @@
"dist"
],
"main": "dist/src/nodejsWorker.js"
}
}
32 changes: 4 additions & 28 deletions src/FunctionLoader.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import * as url from 'url';
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 { PackageJson } from './WorkerChannel';
import { nonNullProp } from './utils/nonNull';

export interface IFunctionLoader {
load(functionId: string, metadata: rpc.IRpcFunctionMetadata, packageJson: PackageJson): Promise<void>;
Expand All @@ -26,22 +27,7 @@ export class FunctionLoader implements IFunctionLoader {
if (metadata.isProxy === true) {
return;
}
const scriptFilePath = <string>(metadata && metadata.scriptFile);
let script: any;
if (this.isESModule(scriptFilePath, packageJson)) {
// IMPORTANT: pathToFileURL is only supported in Node.js version >= v10.12.0
const scriptFileUrl = url.pathToFileURL(scriptFilePath);
if (scriptFileUrl.href) {
// use eval so it doesn't get compiled into a require()
script = await eval('import(scriptFileUrl.href)');
} else {
throw new InternalException(
`'${scriptFilePath}' could not be converted to file URL (${scriptFileUrl.href})`
);
}
} else {
script = require(scriptFilePath);
}
const script: any = await loadScriptFile(nonNullProp(metadata, 'scriptFile'), packageJson);
const entryPoint = <string>(metadata && metadata.entryPoint);
const [userFunction, thisArg] = getEntryPoint(script, entryPoint);
this.#loadedFunctions[functionId] = {
Expand Down Expand Up @@ -69,16 +55,6 @@ export class FunctionLoader implements IFunctionLoader {
throw new InternalException(`Function code for '${functionId}' is not loaded and cannot be invoked.`);
}
}

isESModule(filePath: string, packageJson: PackageJson): boolean {
if (filePath.endsWith('.mjs')) {
return true;
}
if (filePath.endsWith('.cjs')) {
return false;
}
return packageJson.type === 'module';
}
}

function getEntryPoint(f: any, entryPoint?: string): [Function, unknown] {
Expand Down
22 changes: 4 additions & 18 deletions src/WorkerChannel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,15 @@
// Licensed under the MIT License.

import { HookCallback, HookContext } from '@azure/functions-core';
import { readJson } from 'fs-extra';
import { AzureFunctionsRpcMessages as rpc } from '../azure-functions-language-worker-protobuf/src/rpc';
import { Disposable } from './Disposable';
import { IFunctionLoader } from './FunctionLoader';
import { IEventStream } from './GrpcClient';
import { PackageJson, parsePackageJson } from './parsers/parsePackageJson';
import { ensureErrorType } from './utils/ensureErrorType';
import path = require('path');
import LogLevel = rpc.RpcLog.Level;
import LogCategory = rpc.RpcLog.RpcLogCategory;

export interface PackageJson {
type?: string;
}

export class WorkerChannel {
eventStream: IEventStream;
functionLoader: IFunctionLoader;
Expand Down Expand Up @@ -92,20 +87,11 @@ export class WorkerChannel {

async updatePackageJson(dir: string): Promise<void> {
try {
this.packageJson = await readJson(path.join(dir, 'package.json'));
this.packageJson = await parsePackageJson(dir);
} catch (err) {
const error: Error = ensureErrorType(err);
let errorMsg: string;
if (error.name === 'SyntaxError') {
errorMsg = `file is not a valid JSON: ${error.message}`;
} else if (error.message.startsWith('ENOENT')) {
errorMsg = `file does not exist.`;
} else {
errorMsg = error.message;
}
errorMsg = `Worker failed to load package.json: ${errorMsg}`;
const error = ensureErrorType(err);
this.log({
message: errorMsg,
message: `Worker failed to load package.json: ${error.message}`,
level: LogLevel.Warning,
logCategory: LogCategory.System,
});
Expand Down
41 changes: 38 additions & 3 deletions src/eventHandlers/WorkerInitHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
// Licensed under the MIT License.

import { access, constants } from 'fs';
import { pathExists } from 'fs-extra';
import * as path from 'path';
import { AzureFunctionsRpcMessages as rpc } from '../../azure-functions-language-worker-protobuf/src/rpc';
import { isError } from '../utils/ensureErrorType';
import { loadScriptFile } from '../loadScriptFile';
import { ensureErrorType, isError } from '../utils/ensureErrorType';
import { InternalException } from '../utils/InternalException';
import { systemError } from '../utils/Logger';
import { nonNullProp } from '../utils/nonNull';
import { WorkerChannel } from '../WorkerChannel';
import { EventHandler } from './EventHandler';
import LogCategory = rpc.RpcLog.RpcLogCategory;
Expand All @@ -25,6 +28,12 @@ export class WorkerInitHandler extends EventHandler<'workerInitRequest', 'worker
async handleEvent(channel: WorkerChannel, msg: rpc.IWorkerInitRequest): Promise<rpc.IWorkerInitResponse> {
const response = this.getDefaultResponse(msg);

channel.log({
message: 'Received WorkerInitRequest',
level: LogLevel.Debug,
logCategory: LogCategory.System,
});

// Validate version
const version = process.version;
if (
Expand Down Expand Up @@ -54,8 +63,34 @@ export class WorkerInitHandler extends EventHandler<'workerInitRequest', 'worker
}

logColdStartWarning(channel);
if (msg.functionAppDirectory) {
await channel.updatePackageJson(msg.functionAppDirectory);
const functionAppDirectory = nonNullProp(msg, 'functionAppDirectory');
await channel.updatePackageJson(functionAppDirectory);

const entryPointFile = channel.packageJson.main;
if (entryPointFile) {
channel.log({
message: `Loading entry point "${entryPointFile}"`,
level: LogLevel.Debug,
logCategory: LogCategory.System,
});
try {
const entryPointFullPath = path.join(functionAppDirectory, entryPointFile);
if (!(await pathExists(entryPointFullPath))) {
throw new Error(`file does not exist`);
}

await loadScriptFile(entryPointFullPath, channel.packageJson);
channel.log({
message: `Loaded entry point "${entryPointFile}"`,
level: LogLevel.Debug,
logCategory: LogCategory.System,
});
} catch (err) {
const error = ensureErrorType(err);
error.isAzureFunctionsInternalException = true;
error.message = `Worker was unable to load entry point "${entryPointFile}": ${error.message}`;
throw error;
}
}

response.capabilities = {
Expand Down
32 changes: 32 additions & 0 deletions src/loadScriptFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License.

import * as url from 'url';
import { PackageJson } from './parsers/parsePackageJson';
import { InternalException } from './utils/InternalException';

export async function loadScriptFile(filePath: string, packageJson: PackageJson): Promise<unknown> {
let script: unknown;
if (isESModule(filePath, packageJson)) {
const fileUrl = url.pathToFileURL(filePath);
if (fileUrl.href) {
// use eval so it doesn't get compiled into a require()
script = await eval('import(fileUrl.href)');
} else {
throw new InternalException(`'${filePath}' could not be converted to file URL (${fileUrl.href})`);
}
} else {
script = require(filePath);
}
return script;
}

export function isESModule(filePath: string, packageJson: PackageJson): boolean {
if (filePath.endsWith('.mjs')) {
return true;
}
if (filePath.endsWith('.cjs')) {
return false;
}
return packageJson.type === 'module';
}
Loading