Skip to content

Commit

Permalink
Merge pull request #29 from horihiro/bundleAsExecutable
Browse files Browse the repository at this point in the history
feature: Deploy an executable
  • Loading branch information
horihiro authored Apr 23, 2021
2 parents f83574c + c2ced2d commit f874f6a
Showing 1 changed file with 93 additions and 37 deletions.
130 changes: 93 additions & 37 deletions denofunc.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
const { args } = Deno;
import {
parse,
readZip,
Expand All @@ -7,31 +6,49 @@ import {
walk,
} from "./deps.ts";

const shouldBundle = false;
const baseExecutableFileName = "worker";
const bundleFileName = "worker.bundle.js";
const commonDenoOptions = ["--allow-env", "--allow-net", "--allow-read"]
const parsedArgs = parse(Deno.args);

if (parsedArgs["help"]) {
if (parsedArgs._[0] === "help") {
printHelp();
Deno.exit();
}

if (args.length >= 1 && args[0] === "init") {
const templateDownloadBranch: string | undefined = args[1];
if (parsedArgs._.length >= 1 && parsedArgs._[0] === "init") {
const templateDownloadBranch: string | undefined = parsedArgs?._[1]?.toString();
await initializeFromTemplate(templateDownloadBranch);
} else if (
args.length === 1 && args[0] === "start" ||
args.length === 2 && `${args[0]} ${args[1]}` === "host start"
parsedArgs._.length === 1 && parsedArgs._[0] === "start" ||
parsedArgs._.length === 2 && parsedArgs._.join(' ') === "host start"
) {
await generateFunctions();
await createJSBundle();
await runFunc("start");
} else if (args[0] === "publish" && (args.length === 2 || args.length === 4 && args[2] === "--slot" )) {
const platform = await getAppPlatform(args[1], args[3]);
await updateHostJson(platform);
await downloadBinary(platform);
} else if (parsedArgs._[0] === "publish" && parsedArgs._.length === 2) {
const bundleStyle = parsedArgs["bundle-style"] || "executable";
if (!["executable", "jsbundle", "none"].includes(bundleStyle)) {
console.error(`The value \`${parsedArgs["bundle-style"]}\` of \`--bundle-style\` option is not acceptable.`)
Deno.exit(1);
}
const appName = parsedArgs._[1].toString();
const slotName = parsedArgs["slot"]?.toString();
const platform = await getAppPlatform(appName, slotName);
if (!["windows", "linux"].includes(platform)) {
console.error(`The value \`${platform}\` for the function app \`${appName + (slotName ? `/${slotName}` : "")}\` is not valid.`);
Deno.exit(1);
}
await updateHostJson(platform, bundleStyle);
await generateFunctions();
await createJSBundle();
await publishApp(args[1], args[3]);

if (bundleStyle === "executable") {
await generateExecutable(platform);
} else {
await downloadBinary(platform);
if (bundleStyle === "jsbundle") await createJSBundle();
}
await publishApp(appName, slotName);
} else {
printHelp();
}
Expand Down Expand Up @@ -67,14 +84,39 @@ async function listFiles(dir: string) {
return files;
}

async function generateExecutable(platformArg?: string) {
try {
await Deno.remove('./bin', { recursive: true });
await Deno.remove(`./${bundleFileName}`);
} catch { }

const platform = platformArg || Deno.build.os;
await Deno.mkdir(`./bin/${platform}`, { recursive: true });

const cmd = [
"deno",
"compile",
"--unstable",
"--lite",
...commonDenoOptions,
"--output",
`./bin/${platform}/${baseExecutableFileName}`,
...(['windows', 'linux'].includes(platform)
? ['--target', platform === 'windows' ? 'x86_64-pc-windows-msvc' : 'x86_64-unknown-linux-gnu']
: []
),
"worker.ts"
];
console.info(`Running command: ${cmd.join(" ")}`);
const generateProcess = Deno.run({ cmd });
await generateProcess.status();
}

async function createJSBundle() {
if (shouldBundle) {
const bundleFileName = "worker.bundle.js";
const cmd = ["deno", "bundle", "--unstable", "worker.ts", bundleFileName];
console.info(`Running command: ${cmd.join(" ")}`);
const generateProcess = Deno.run({ cmd });
await generateProcess.status();
}
const cmd = ["deno", "bundle", "--unstable", "worker.ts", bundleFileName];
console.info(`Running command: ${cmd.join(" ")}`);
const generateProcess = Deno.run({ cmd });
await generateProcess.status();
}

async function getAppPlatform(appName: string, slotName?: string): Promise<string> {
Expand All @@ -84,7 +126,7 @@ async function getAppPlatform(appName: string, slotName?: string): Promise<strin
"resource",
"list",
"--resource-type",
`Microsoft.web/sites${slotName ? "/slots": ""}`,
`Microsoft.web/sites${slotName ? "/slots" : ""}`,
"-o",
"json",
];
Expand All @@ -102,7 +144,7 @@ async function getAppPlatform(appName: string, slotName?: string): Promise<strin
const resource = resources.find((resource: any) =>
resource.name === (appName + (slotName ? `/${slotName}` : ""))
);

const azFunctionAppSettingsCmd = [
"az",
"functionapp",
Expand Down Expand Up @@ -133,21 +175,30 @@ async function getAppPlatform(appName: string, slotName?: string): Promise<strin
}
}

async function updateHostJson(platform: string) {
// update `defaultExecutablePath` in host.json
async function updateHostJson(platform: string, bundleStyle: string) {
// update `defaultExecutablePath` and `arguments` in host.json
const hostJsonPath = "./host.json";
if (!(await fileExists(hostJsonPath))) {
throw new Error(`\`${hostJsonPath}\` not found`);
}

const hostJSON: any = await readJson(hostJsonPath);
hostJSON.customHandler.description.defaultExecutablePath = platform === "windows"
? "D:\\home\\site\\wwwroot\\bin\\windows\\deno.exe"
: "/home/site/wwwroot/bin/linux/deno",
await writeJson(hostJsonPath, hostJSON); // returns a promise
if (!hostJSON.customHandler) hostJSON.customHandler = {};
hostJSON.customHandler.description = {
defaultExecutablePath: `bin/${platform}/${bundleStyle === "executable" ? baseExecutableFileName : "deno"}${platform === "windows" ? ".exe" : ""}`,
arguments: bundleStyle === "executable"
? []
: [
"run",
...commonDenoOptions,
bundleStyle === "jsbundle" ? bundleFileName : "worker.ts"
]
};

await writeJson(hostJsonPath, hostJSON); // returns a promise
}

function writeJson(path: string, data: object): void {
function writeJson(path: string, data: object): void {
Deno.writeTextFileSync(path, JSON.stringify(data, null, 2));
}

Expand All @@ -173,12 +224,14 @@ async function downloadBinary(platform: string) {
await Deno.remove(entry);
}
}
try {
await Deno.remove(`./${bundleFileName}`);
} catch { }

const binZipPath = `${binDir}/deno.zip`;
if (!(await fileExists(binPath))) {
const downloadUrl =
`https://github.com/denoland/deno/releases/download/v${Deno.version.deno}/deno-x86_64-${
archive[platform]
`https://github.com/denoland/deno/releases/download/v${Deno.version.deno}/deno-x86_64-${archive[platform]
}.zip`;
console.info(`Downloading deno binary from: ${downloadUrl} ...`);
// download deno binary (that gets deployed to Azure)
Expand Down Expand Up @@ -250,9 +303,7 @@ async function generateFunctions() {
cmd: [
"deno",
"run",
"--allow-net",
"--allow-env",
"--allow-read",
...commonDenoOptions,
"--allow-write",
"--unstable",
"--no-check",
Expand Down Expand Up @@ -285,8 +336,7 @@ async function runWithRetry(
} catch (ex) {
if (Deno.build.os === "windows") {
console.info(
`Could not start ${
runOptions.cmd[0]
`Could not start ${runOptions.cmd[0]
} from path, searching for executable...`,
);
const whereCmd = ["where.exe", backupCommand];
Expand Down Expand Up @@ -358,7 +408,13 @@ denofunc init
denofunc start
Generate functions artifacts and start Azure Functions Core Tools
denofunc publish <function_app_name> [--slot <slot_name>]
denofunc publish <function_app_name> [options]
Publish to Azure
options:
--slot <slot_name> Specify name of the deployment slot
--bundle-style executable|jsbundle|none Select bundle style on deployment
executable(default): Bundle as one executable
jsbundle: Bundle as one javascript worker & Deno runtime
none: No bundle
`);
}

0 comments on commit f874f6a

Please sign in to comment.