Chris Anderson 2017-02-12 02:34:00 -08:00
ΠšΠΎΠΌΠΌΠΈΡ‚ 8b29819265
27 ΠΈΠ·ΠΌΠ΅Π½Ρ‘Π½Π½Ρ‹Ρ… Ρ„Π°ΠΉΠ»ΠΎΠ²: 866 Π΄ΠΎΠ±Π°Π²Π»Π΅Π½ΠΈΠΉ ΠΈ 0 ΡƒΠ΄Π°Π»Π΅Π½ΠΈΠΉ

3
.gitignore поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,3 @@
coverage/
node_modules/
npm-debug.log

57
.vscode/launch.json поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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"
}
]
}

3
.vscode/settings.json поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,3 @@
{
"typescript.tsdk": "node_modules/typescript/lib/"
}

23
.vscode/tasks.json поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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
}
]
}

21
LICENSE Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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.

39
README.md Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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)

3
bin/funcpack Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,3 @@
#!/usr/bin/env node
require('../lib/main');

53
package.json Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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"
}
}

124
sample/.funcpack/index.js Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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")
}

24
sample/.gitignore поставляСмый Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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

1
sample/host.json Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1 @@
{"id":"2218e0e3e1c84d19954575752e8823b8"}

20
sample/todo/function.json Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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"
}

17
sample/todo/index.js Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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);
};

3
sample/todo/sample.dat Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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"
}

17
sample/todos/index.js Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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);
};

3
sample/todos/sample.dat Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,3 @@
{
"name": "Azure"
}

2
src/index.ts Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,2 @@
export * from "./packhost-generator";
export * from "./webpack-runner";

58
src/main.ts Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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();

169
src/packhost-generator.ts Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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;
}

113
src/utils/fs-helper.ts Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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();
});
});
}
}

1
src/utils/index.ts Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1 @@
export * from "./fs-helper";

58
src/webpack-runner.ts Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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

20
tsconfig.json Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -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"
]
}

4
tslint.json Normal file
ΠŸΡ€ΠΎΡΠΌΠΎΡ‚Ρ€Π΅Ρ‚ΡŒ Ρ„Π°ΠΉΠ»

@ -0,0 +1,4 @@
{
"extends": "tslint:latest",
"exclude": "./node_modules"
}