Π·Π΅ΡΠΊΠ°Π»ΠΎ ΠΈΠ· https://github.com/Azure/azure-functions-pack.git
π Initial commit
This commit is contained in:
ΠΠΎΠΌΠΌΠΈΡ
8b29819265
|
@ -0,0 +1,3 @@
|
|||
coverage/
|
||||
node_modules/
|
||||
npm-debug.log
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible Node.js debug attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"args": [
|
||||
"-p", "./sample",
|
||||
"-l", "silly",
|
||||
"./sample"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {
|
||||
"NODE_ENV": "development",
|
||||
"DEBUG":"azure-functions-pack:*"
|
||||
},
|
||||
"name": "DEBUG",
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/lib/**"
|
||||
],
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceRoot}/src/main.ts",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
],
|
||||
"runtimeExecutable": null,
|
||||
"sourceMaps": true,
|
||||
"stopOnEntry": false,
|
||||
"type": "node"
|
||||
},
|
||||
{
|
||||
"args": [
|
||||
"-p", "./sample",
|
||||
"./sample"
|
||||
],
|
||||
"cwd": "${workspaceRoot}",
|
||||
"env": {
|
||||
},
|
||||
"name": "PRODUCTION",
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/lib/**"
|
||||
],
|
||||
"preLaunchTask": "build",
|
||||
"program": "${workspaceRoot}/src/main.ts",
|
||||
"request": "launch",
|
||||
"runtimeArgs": [
|
||||
"--nolazy"
|
||||
],
|
||||
"runtimeExecutable": null,
|
||||
"sourceMaps": true,
|
||||
"stopOnEntry": false,
|
||||
"type": "node"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"typescript.tsdk": "node_modules/typescript/lib/"
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
{
|
||||
"version": "0.1.0",
|
||||
"command": "npm",
|
||||
"isShellCommand": true,
|
||||
"args": ["run"],
|
||||
"showOutput": "always",
|
||||
"tasks": [
|
||||
{
|
||||
"taskName": "build",
|
||||
"isBuildCommand": true
|
||||
},
|
||||
{
|
||||
"taskName": "clean"
|
||||
},
|
||||
{
|
||||
"taskName": "lint"
|
||||
},
|
||||
{
|
||||
"taskName": "test",
|
||||
"isTestCommand": true
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 Microsoft
|
||||
|
||||
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.
|
|
@ -0,0 +1,39 @@
|
|||
# Azure Functions Pack
|
||||
|
||||
This is a tool to make it easy to package your Azure Functions Node.js Functions for optimal performance on Azure Functions.
|
||||
|
||||
:construction: This project is experimental; use with caution and be prepared for breaking changes :construction:
|
||||
|
||||
WARNING: This requires host version `1.0.10726.0` or higher.
|
||||
|
||||
## How to run
|
||||
|
||||
```
|
||||
npm install -g christopheranderson/azure-functions-pack
|
||||
funcpack
|
||||
```
|
||||
|
||||
You can then test locally using the CLI tool: `func run <myfunc>`
|
||||
|
||||
## API
|
||||
|
||||
```
|
||||
Usage: funcpack [options]
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help output usage information
|
||||
-V, --version output the version number
|
||||
-d, --debug Emits debug messages
|
||||
-p, --path <path> Path to root of Function App
|
||||
```
|
||||
|
||||
You can pass the path to the root of your project via:
|
||||
|
||||
0. Using the `-p` command: `funcpack -p ./pathToFunctionApp`
|
||||
1. Just a normal argument: `funcpack ./pathToFunctionApp`
|
||||
2. Run in the same directory: `cd ./pathToFunctionApp && funcpack`
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
|
@ -0,0 +1,3 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/main');
|
|
@ -0,0 +1,53 @@
|
|||
{
|
||||
"name": "azure-functions-pack",
|
||||
"version": "0.0.0",
|
||||
"description": "azure-functions-pack",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/christopheranderson/azure-functions-pack",
|
||||
"author": "christopheranderson",
|
||||
"bin": {
|
||||
"funcpack":"./bin/funcpack"
|
||||
},
|
||||
"keywords": [
|
||||
"azure-functions","webpack"
|
||||
],
|
||||
"files": [
|
||||
"lib"
|
||||
],
|
||||
"main": "lib/index.js",
|
||||
"typings": "lib/index.d.ts",
|
||||
"scripts": {
|
||||
"clean": "rimraf lib",
|
||||
"lint": "tslint --force --format verbose \"src/**/*.ts\"",
|
||||
"build": "npm run clean && npm run lint && echo Using TypeScript && tsc --version && tsc --pretty",
|
||||
"test": "npm run build && mocha --compilers ts:ts-node/register --recursive test/**/*-spec.ts",
|
||||
"watch": "npm run build -- --watch",
|
||||
"watch:test": "npm run test -- --watch",
|
||||
"e2etst": "npm run "
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^2.9.0",
|
||||
"debug": "^2.6.1",
|
||||
"rimraf": "^2.5.4",
|
||||
"webpack": "^2.2.1",
|
||||
"winston": "^2.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/chai": "^3.0.0",
|
||||
"@types/commander": "^2.3.31",
|
||||
"@types/debug": "0.0.29",
|
||||
"@types/mocha": "^2.0.0",
|
||||
"@types/node": "6.0.31",
|
||||
"@types/rimraf": "0.0.28",
|
||||
"@types/webpack": "^2.2.5",
|
||||
"@types/winston": "^2.2.0",
|
||||
"chai": "^3.0.0",
|
||||
"mocha": "^3.0.0",
|
||||
"ts-node": "^1.0.0",
|
||||
"tslint": "^4.0.0",
|
||||
"typescript": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,124 @@
|
|||
module.exports =
|
||||
/******/ (function(modules) { // webpackBootstrap
|
||||
/******/ // The module cache
|
||||
/******/ var installedModules = {};
|
||||
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
|
||||
/******/ // Check if module is in cache
|
||||
/******/ if(installedModules[moduleId])
|
||||
/******/ return installedModules[moduleId].exports;
|
||||
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = installedModules[moduleId] = {
|
||||
/******/ i: moduleId,
|
||||
/******/ l: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
|
||||
/******/ // Execute the module function
|
||||
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
|
||||
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.l = true;
|
||||
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
|
||||
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = modules;
|
||||
|
||||
/******/ // expose the module cache
|
||||
/******/ __webpack_require__.c = installedModules;
|
||||
|
||||
/******/ // identity function for calling harmony imports with the correct context
|
||||
/******/ __webpack_require__.i = function(value) { return value; };
|
||||
|
||||
/******/ // define getter function for harmony exports
|
||||
/******/ __webpack_require__.d = function(exports, name, getter) {
|
||||
/******/ if(!__webpack_require__.o(exports, name)) {
|
||||
/******/ Object.defineProperty(exports, name, {
|
||||
/******/ configurable: false,
|
||||
/******/ enumerable: true,
|
||||
/******/ get: getter
|
||||
/******/ });
|
||||
/******/ }
|
||||
/******/ };
|
||||
|
||||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||||
/******/ __webpack_require__.n = function(module) {
|
||||
/******/ var getter = module && module.__esModule ?
|
||||
/******/ function getDefault() { return module['default']; } :
|
||||
/******/ function getModuleExports() { return module; };
|
||||
/******/ __webpack_require__.d(getter, 'a', getter);
|
||||
/******/ return getter;
|
||||
/******/ };
|
||||
|
||||
/******/ // Object.prototype.hasOwnProperty.call
|
||||
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
|
||||
|
||||
/******/ // __webpack_public_path__
|
||||
/******/ __webpack_require__.p = "";
|
||||
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ return __webpack_require__(__webpack_require__.s = 2);
|
||||
/******/ })
|
||||
/************************************************************************/
|
||||
/******/ ([
|
||||
/* 0 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = function (context, req) {
|
||||
context.log('JavaScript HTTP trigger function processed a request.');
|
||||
|
||||
if (req.query.name || (req.body && req.body.name)) {
|
||||
res = {
|
||||
// status: 200, /* Defaults to 200 */
|
||||
body: "Hello " + (req.query.name || req.body.name)
|
||||
};
|
||||
}
|
||||
else {
|
||||
res = {
|
||||
status: 400,
|
||||
body: "Please pass a name on the query string or in the request body"
|
||||
};
|
||||
}
|
||||
context.done(null, res);
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 1 */
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
module.exports = function (context, req) {
|
||||
context.log('JavaScript HTTP trigger function processed a request.');
|
||||
|
||||
if (req.query.name || (req.body && req.body.name)) {
|
||||
res = {
|
||||
// status: 200, /* Defaults to 200 */
|
||||
body: "Hello " + (req.query.name || req.body.name)
|
||||
};
|
||||
}
|
||||
else {
|
||||
res = {
|
||||
status: 400,
|
||||
body: "Please pass a name on the query string or in the request body"
|
||||
};
|
||||
}
|
||||
context.done(null, res);
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
/* 2 */
|
||||
/***/ (function(module, exports, __webpack_require__) {
|
||||
|
||||
module.exports = {
|
||||
"todo": __webpack_require__(0),
|
||||
"todos": __webpack_require__(1)
|
||||
}
|
||||
|
||||
/***/ })
|
||||
/******/ ]);
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = {
|
||||
"todo": require("../todo/index.js"),
|
||||
"todos": require("../todos/index.js")
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
bin
|
||||
obj
|
||||
csx
|
||||
.vs
|
||||
edge
|
||||
Publish
|
||||
.vscode
|
||||
|
||||
*.user
|
||||
*.suo
|
||||
*.cscfg
|
||||
*.Cache
|
||||
project.lock.json
|
||||
|
||||
/packages
|
||||
/TestResults
|
||||
|
||||
/tools/NuGet.exe
|
||||
/App_Data
|
||||
/secrets
|
||||
/data
|
||||
.secrets
|
||||
appsettings.json
|
|
@ -0,0 +1 @@
|
|||
{"id":"2218e0e3e1c84d19954575752e8823b8"}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"disabled": false,
|
||||
"bindings": [
|
||||
{
|
||||
"authLevel": "function",
|
||||
"type": "httpTrigger",
|
||||
"direction": "in",
|
||||
"name": "req"
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"direction": "out",
|
||||
"name": "$return"
|
||||
}
|
||||
],
|
||||
"originalEntryPoint": false,
|
||||
"originalScriptFile": "index.js",
|
||||
"scriptFile": "../.funcpack/index.js",
|
||||
"entryPoint": "todo"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = function (context, req) {
|
||||
context.log('JavaScript HTTP trigger function processed a request.');
|
||||
|
||||
if (req.query.name || (req.body && req.body.name)) {
|
||||
res = {
|
||||
// status: 200, /* Defaults to 200 */
|
||||
body: "Hello " + (req.query.name || req.body.name)
|
||||
};
|
||||
}
|
||||
else {
|
||||
res = {
|
||||
status: 400,
|
||||
body: "Please pass a name on the query string or in the request body"
|
||||
};
|
||||
}
|
||||
context.done(null, res);
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "Azure"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"disabled": false,
|
||||
"bindings": [
|
||||
{
|
||||
"authLevel": "function",
|
||||
"type": "httpTrigger",
|
||||
"direction": "in",
|
||||
"name": "req"
|
||||
},
|
||||
{
|
||||
"type": "http",
|
||||
"direction": "out",
|
||||
"name": "$return"
|
||||
}
|
||||
],
|
||||
"originalEntryPoint": false,
|
||||
"originalScriptFile": "index.js",
|
||||
"scriptFile": "../.funcpack/index.js",
|
||||
"entryPoint": "todos"
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = function (context, req) {
|
||||
context.log('JavaScript HTTP trigger function processed a request.');
|
||||
|
||||
if (req.query.name || (req.body && req.body.name)) {
|
||||
res = {
|
||||
// status: 200, /* Defaults to 200 */
|
||||
body: "Hello " + (req.query.name || req.body.name)
|
||||
};
|
||||
}
|
||||
else {
|
||||
res = {
|
||||
status: 400,
|
||||
body: "Please pass a name on the query string or in the request body"
|
||||
};
|
||||
}
|
||||
context.done(null, res);
|
||||
};
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"name": "Azure"
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
export * from "./packhost-generator";
|
||||
export * from "./webpack-runner";
|
|
@ -0,0 +1,58 @@
|
|||
import * as program from "commander";
|
||||
import * as path from "path";
|
||||
import * as winston from "winston";
|
||||
import { PackhostGenerator, WebpackRunner } from "./";
|
||||
|
||||
async function runCli() {
|
||||
const p = program
|
||||
.version("0.0.1")
|
||||
.option("-d, --debug", "Emits debug messages")
|
||||
.option("-p, --path <path>", "Path to root of Function App");
|
||||
p.parse(process.argv);
|
||||
|
||||
if (program.opts().debug) {
|
||||
process.env.DEBUG = process.env.DEBUG ?
|
||||
process.env.DEBUG + ",azure-functions-pack:*" : "azure-functions-pack:*";
|
||||
}
|
||||
|
||||
// Grab the route either from the option, the argument (if there is only 1)
|
||||
let pathToRoot = "";
|
||||
try {
|
||||
pathToRoot = program.opts().path ?
|
||||
path.join(process.cwd(), program.opts().path) :
|
||||
(program.args.length === 1 ? program.args[0] : process.cwd());
|
||||
} catch (error) {
|
||||
winston.error(error);
|
||||
throw new Error("Could not determine route");
|
||||
}
|
||||
|
||||
// Create new generator object with settings
|
||||
const generator = new PackhostGenerator({
|
||||
projectRootPath: pathToRoot,
|
||||
});
|
||||
|
||||
// Attempt to generate the project
|
||||
try {
|
||||
winston.info("Generating project files/metadata");
|
||||
await generator.updateProject();
|
||||
} catch (error) {
|
||||
winston.error(error);
|
||||
throw new Error("Could not generate project");
|
||||
}
|
||||
|
||||
// Webpack
|
||||
try {
|
||||
winston.info("Webpacking project");
|
||||
await WebpackRunner.run({
|
||||
projectRootPath: pathToRoot,
|
||||
});
|
||||
} catch (error) {
|
||||
winston.error(error);
|
||||
throw new Error("Could not webpack project");
|
||||
}
|
||||
|
||||
winston.info("Complete!");
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
runCli();
|
|
@ -0,0 +1,169 @@
|
|||
|
||||
import * as debugLib from "debug";
|
||||
import * as path from "path";
|
||||
|
||||
import { FileHelper } from "./utils";
|
||||
|
||||
const debug = debugLib("azure-functions-pack:PackhostGenerator");
|
||||
|
||||
export class PackhostGenerator {
|
||||
|
||||
private functionsMap: Map<string, IFxFunction> = new Map<string, IFxFunction>();
|
||||
private options: IPackhostGeneratorOptions;
|
||||
|
||||
constructor(options: IPackhostGeneratorOptions) {
|
||||
this.options = options;
|
||||
this.options.indexFileName = this.options.indexFileName || "index.js";
|
||||
this.options.outputPath = this.options.outputPath || ".funcpack";
|
||||
debug("Created new PackhostGenerator for project at: %s", this.options.projectRootPath);
|
||||
}
|
||||
|
||||
// TODO: Should probably replace this whole class with a bunch of static methods. Don't need a class.
|
||||
public async updateProject() {
|
||||
debug("Starting update of Project");
|
||||
await this.load();
|
||||
await this.createOutputDirectory();
|
||||
await this.createHostFile();
|
||||
await this.updateFunctionJSONs();
|
||||
debug("Completed update of project");
|
||||
}
|
||||
|
||||
private async load() {
|
||||
const functions: string[] = (await FileHelper.readdir(this.options.projectRootPath))
|
||||
.filter(async (item) =>
|
||||
(await FileHelper.stat(path.resolve(this.options.projectRootPath, item))).isDirectory());
|
||||
debug("Found these directories in project root: %s", functions.join(", "));
|
||||
for (const item of functions) {
|
||||
if (await FileHelper.exists(path.resolve(this.options.projectRootPath, item, "function.json"))) {
|
||||
this.functionsMap.set(item, await this.loadFunction(item));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async loadFunction(name: string): Promise<IFxFunction> {
|
||||
let entryPoint = null;
|
||||
let scriptFile = null;
|
||||
let originalEntryPoint: string | boolean = false;
|
||||
let originalScriptFile: string | boolean = false;
|
||||
debug("Found function: %s", name);
|
||||
const fxJsonPath = path.resolve(this.options.projectRootPath, name, "function.json");
|
||||
const fxJson = await FileHelper.readFileAsJSON(fxJsonPath);
|
||||
|
||||
// TODO: Have to overwite this scriptFile setting later on. Having to use temporary setting right now.
|
||||
if (fxJson.originalScriptFile === null) {
|
||||
debug("Found originalScriptFile setting: %s", fxJson.originalScriptFile);
|
||||
scriptFile = fxJson.originalScriptFile;
|
||||
originalScriptFile = fxJson.originalScriptFile;
|
||||
} else if (fxJson.scriptFile && !fxJson.originalScriptFile) {
|
||||
scriptFile = fxJson.scriptFile;
|
||||
originalScriptFile = fxJson.scriptFile;
|
||||
} else {
|
||||
let dir: string[] = await FileHelper.readdir(path.resolve(this.options.projectRootPath, name));
|
||||
dir = dir.filter((f) => f.endsWith(".js"));
|
||||
if (dir.length === 1) {
|
||||
scriptFile = dir[0];
|
||||
} else if (dir.find((v, i, o) => {
|
||||
return v === "index.js";
|
||||
})) {
|
||||
scriptFile = "index.js";
|
||||
} else {
|
||||
debug("Function %s does not have a valid start file", name, {
|
||||
directory: dir,
|
||||
});
|
||||
throw new Error(`Function {name} does not have a valid start file`);
|
||||
}
|
||||
originalScriptFile = scriptFile;
|
||||
}
|
||||
|
||||
// TODO: improve the logic for choosing entry point - failure sure not all scenarios are covered here.
|
||||
// TODO: Have to overwrite this entryPoint later on. Using temporary setting for now.
|
||||
if (fxJson.originalEntryPoint) {
|
||||
debug("Found originalEntryPoint setting: %s", fxJson.originalEntryPoint);
|
||||
entryPoint = fxJson.originalEntryPoint;
|
||||
originalEntryPoint = fxJson.originalEntryPoint;
|
||||
} else if (fxJson.entryPoint && fxJson.originalEntryPoint !== false) {
|
||||
entryPoint = fxJson.entryPoint;
|
||||
originalEntryPoint = fxJson.entry;
|
||||
}
|
||||
|
||||
debug("Loaded function(%s) using entryPoint: %s - scriptFile: %s", name, scriptFile, entryPoint);
|
||||
return Promise.resolve({
|
||||
name,
|
||||
scriptFile,
|
||||
entryPoint,
|
||||
originalEntryPoint,
|
||||
originalScriptFile,
|
||||
});
|
||||
}
|
||||
|
||||
private async createOutputDirectory() {
|
||||
const outputDirPath = path.join(this.options.projectRootPath, this.options.outputPath);
|
||||
if (await FileHelper.exists(outputDirPath)) {
|
||||
debug("Deleting previous output directory: %s", this.options.outputPath);
|
||||
await FileHelper.rimraf(outputDirPath);
|
||||
}
|
||||
|
||||
debug("Creating output directory: %s", outputDirPath);
|
||||
await FileHelper.mkdir(outputDirPath);
|
||||
}
|
||||
|
||||
private async createHostFile() {
|
||||
debug("Generating host file");
|
||||
const exportStrings: string[] = [];
|
||||
|
||||
for (const [name, fx] of this.functionsMap) {
|
||||
const fxvar = this.safeFunctionName(fx.name);
|
||||
let exportStmt = ` "${fxvar}": require("../${fx.name}/${fx.originalScriptFile}")`;
|
||||
if (fx.entryPoint) {
|
||||
exportStmt += `.${fx.entryPoint}`;
|
||||
}
|
||||
exportStrings.push(exportStmt);
|
||||
}
|
||||
|
||||
let exportString =
|
||||
exportStrings.reduce((p, c, i, a) => p + c + ((i !== exportStrings.length - 1) ? ",\n" : "\n"), "");
|
||||
|
||||
exportString = "module.exports = {\n" + exportString + "}";
|
||||
|
||||
debug("Writing contents to host file");
|
||||
await FileHelper.writeFileUtf8(
|
||||
path.join(this.options.projectRootPath, this.options.outputPath, this.options.indexFileName),
|
||||
exportString);
|
||||
}
|
||||
|
||||
private async updateFunctionJSONs() {
|
||||
debug("Updating Function JSONS");
|
||||
for (const [name, fx] of this.functionsMap) {
|
||||
debug("Updating function(%s)", name);
|
||||
const fxJsonPath = path.resolve(this.options.projectRootPath, name, "function.json");
|
||||
const fxvar = this.safeFunctionName(fx.name);
|
||||
const fxJson = await FileHelper.readFileAsJSON(fxJsonPath);
|
||||
|
||||
// TODO: This way of keeping track of the original settings is hacky
|
||||
fxJson.originalEntryPoint = fx.originalEntryPoint;
|
||||
fxJson.originalScriptFile = fx.originalScriptFile;
|
||||
fxJson.scriptFile = `../${this.options.outputPath}/${this.options.indexFileName}`;
|
||||
fxJson.entryPoint = fxvar;
|
||||
await FileHelper.overwriteFileUtf8(fxJsonPath, JSON.stringify(fxJson, null, " "));
|
||||
}
|
||||
}
|
||||
|
||||
private safeFunctionName(name: string): string {
|
||||
return name.replace("-", "$dash");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export interface IPackhostGeneratorOptions {
|
||||
projectRootPath: string;
|
||||
outputPath?: string;
|
||||
indexFileName?: string;
|
||||
}
|
||||
|
||||
export interface IFxFunction {
|
||||
name: string;
|
||||
entryPoint: string;
|
||||
scriptFile: string;
|
||||
originalEntryPoint: string | boolean;
|
||||
originalScriptFile: string | boolean;
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
import * as fs from "fs";
|
||||
import * as rimraf from "rimraf";
|
||||
|
||||
export class FileHelper {
|
||||
public static readdir(path: string): Promise<string[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readdir(path, (err, files) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(files);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static stat(path: string): Promise<fs.Stats> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.stat(path, (err, stat) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(stat);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static readFileUtf8(path: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.readFile(path, "utf8", (err, content: string) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve(content);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static exists(path: string): Promise<boolean> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.access(path, (err) => {
|
||||
resolve(!err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static readFileAsJSON(path: string): Promise<any> {
|
||||
return new Promise<Object>(async (resolve, reject) => {
|
||||
try {
|
||||
const content = await FileHelper.readFileUtf8(path);
|
||||
resolve(JSON.parse(content));
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public static overwriteFileUtf8(path: string, content: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.truncate(path, async (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
await this.writeFileUtf8(path, content).catch(reject).then(resolve, reject);
|
||||
});
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
public static writeFileUtf8(path: string, content: string): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.writeFile(path, content, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static mkdir(path: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.mkdir(path, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static rimraf(path: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
rimraf(path, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static rename(pathOld: string, pathNew: string) {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.rename(pathOld, pathNew, (err) => {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./fs-helper";
|
|
@ -0,0 +1,58 @@
|
|||
import * as debugLib from "debug";
|
||||
import * as path from "path";
|
||||
import * as webpack from "webpack";
|
||||
import { IPackhostGeneratorOptions } from "./";
|
||||
import { FileHelper } from "./utils";
|
||||
|
||||
const debug = debugLib("azure-functions-pack:WebpackRunner");
|
||||
|
||||
export class WebpackRunner {
|
||||
public static run(options: IPackhostGeneratorOptions): Promise<any> {
|
||||
options.indexFileName = options.indexFileName || "index.js";
|
||||
options.outputPath = options.outputPath || ".funcpack";
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
|
||||
const oldPath = path.join(options.projectRootPath, options.outputPath, options.indexFileName);
|
||||
const newPath = path.join(options.projectRootPath,
|
||||
options.outputPath, "original." + options.indexFileName);
|
||||
|
||||
const outputPath = path.join(options.projectRootPath, options.outputPath, "output.js");
|
||||
|
||||
const config: webpack.Configuration = {
|
||||
entry: oldPath,
|
||||
node: {
|
||||
__dirname: false,
|
||||
__filename: false,
|
||||
},
|
||||
output: {
|
||||
filename: "output.js",
|
||||
library: "index",
|
||||
libraryTarget: "commonjs2",
|
||||
path: path.join(options.projectRootPath, options.outputPath),
|
||||
},
|
||||
target: "node",
|
||||
};
|
||||
|
||||
const compiler = webpack(config);
|
||||
debug("Started webpack");
|
||||
compiler.run(async (err, stats) => {
|
||||
debug("Webpack finished");
|
||||
if (err || stats.hasErrors()) {
|
||||
return reject(err || stats.toString({ errors: true }));
|
||||
}
|
||||
debug("\n" + stats.toString());
|
||||
|
||||
debug("Saving the original the entry file: %s -> %s", oldPath, newPath);
|
||||
if (await FileHelper.exists(newPath)) {
|
||||
await FileHelper.rimraf(newPath);
|
||||
}
|
||||
await FileHelper.rename(oldPath, newPath);
|
||||
|
||||
debug("Renaming the output file");
|
||||
await FileHelper.rename(outputPath, oldPath);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import * as chai from "chai";
|
||||
import { PackhostGenerator } from "../src/packhost-generator";
|
||||
|
||||
const expect = chai.expect;
|
||||
|
||||
// TODO: Should write some tests :3
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"noImplicitAny": true,
|
||||
"outDir": "./lib",
|
||||
"preserveConstEnums": true,
|
||||
"removeComments": true,
|
||||
"target": "es6",
|
||||
"sourceMap": true
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"**/*-spec.ts"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"extends": "tslint:latest",
|
||||
"exclude": "./node_modules"
|
||||
}
|
ΠΠ°Π³ΡΡΠ·ΠΊΠ°β¦
Π‘ΡΡΠ»ΠΊΠ° Π² Π½ΠΎΠ²ΠΎΠΉ Π·Π°Π΄Π°ΡΠ΅