Skip to content

Instantly share code, notes, and snippets.

@goldzulu
Last active February 7, 2021 04:17
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save goldzulu/5adae817b33533e43d0db6205caa257d to your computer and use it in GitHub Desktop.
Save goldzulu/5adae817b33533e43d0db6205caa257d to your computer and use it in GitHub Desktop.
node js executable script to change the lambda endpoints from command line E.g. Usage: node ./updateSkillEndpoint.js --url https://letmypeoplesleep.com ... or if you have ngrok running, can use node ./updateSkillEndpoint.js --ngrok ... you can optionally attached tunnelName to the --ngrok param if you have multiple tunnels.
#! /usr/bin/env node
// node.js script to update the endpoint at the ADC to a local endpoint
// Originally written by @Voicetechguy1 twitch.tv/goldzulu
// This file is to be placed at the root of the Alexa Skill Project
// currently works only for ASK2 CLI
// Get the arguments from the command line and use the default below if none is given
// e.g. node ./updateSkillEndpoint.js --url https://letmypeoplesleep.com
// if you are using ngrok and ngrok is running before running this script
// the script can automatically detect the public url
// node ./updateSkillEndpoint.js --ngrok
// if you have more than one tunnel defined in ngrok config file
// node ./updateSkillEndpoint.js --ngrok <tunnelName>
let endpointUrl = "https://letmypeoplesleep.com";
const win32flag = process.platform === "win32" ? true : false;
const pathSep = win32flag ? "\\" : "/";
var myArgs = process.argv.slice(2);
// general function to validate url
function validURL(str) {
var pattern = new RegExp(
"^(https?:\\/\\/)?" + // protocol
"((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|" + // domain name
"((\\d{1,3}\\.){3}\\d{1,3}))" + // OR ip (v4) address
"(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*" + // port and path
"(\\?[;&a-z\\d%_.~+=-]*)?" + // query string
"(\\#[-a-z\\d_]*)?$",
"i"
); // fragment locator
return !!pattern.test(str);
}
if (myArgs.length >= 2 && (myArgs[0] === "--url" || myArgs[0] === "-u")) {
if (validURL(myArgs[1])) {
endpointUrl = myArgs[1];
} else {
console.error("Invalid https url.");
return false;
}
}
// Change the below to the skill local endpoint
// this would be the default used
// PART 1: Grab the SkillID
// use the get-skill-id.js file (now embedded in here) to get the skill id for the current alexa skill project
// check the blog at https://dzone.com/articles/syncing-local-alexa-skills-json-files-with-alexa-d
const fs = require("fs-sync");
fs.defaultEncoding = "utf-8";
const path = require("path");
// The skill id extraction code is from a previous blog post and can extract both ASK1
// and ASK2 structured skillid
// in theory doesnt matter as V1 is not supported in this script as is
const CONFIG_FILE = ".ask" + pathSep + "ask-states.json";
const CONFIG_FILE_V1 = ".ask" + pathSep + "config";
// Break the path into an array of folders and filter out
// the empty entry for the leading '/'
const folders = win32flag
? process.cwd().split(pathSep).filter(Boolean)
: process.env.PWD.split(pathSep).filter(Boolean);
// Locate the ASK Config file
const findAskConfigFile = async function (folders) {
// The ask cli downloads skills into a folder and
// writes the .ask/config or ..ask/ask-states.json (v2) there. There should
// never be one at your root.
if (folders.length <= 0) {
throw new Error("No config file found!");
}
const directory = folders.join(pathSep);
const askConfigFile = win32flag
? path.join(directory, CONFIG_FILE)
: pathSep + path.join(directory, CONFIG_FILE);
const askConfigFileV1 = win32flag
? path.join(directory, CONFIG_FILE_V1)
: pathSep + path.join(directory, CONFIG_FILE_V1);
console.log("scanning... " + askConfigFile);
if (fs.exists(askConfigFile)) {
return await askConfigFile;
} else {
if (fs.exists(askConfigFileV1)) {
return await askConfigFileV1;
}
}
// if .ask/config or .ask/ask-states.json (v2) doesn't exist in the current directory
// look in the one above by removing the last item from
// folders and call findAskConfigFile again. Recursion.
folders.pop();
return await findAskConfigFile(folders);
};
// Get the skillId extracted from the skill config file
async function getSkillId() {
let configFile = await findAskConfigFile(folders);
if (configFile) {
let obj = fs.readJSON(configFile);
if (obj.profiles) {
return obj.profiles.default.skillId;
} else {
return obj.deploy_settings.default.skill_id;
}
}
}
// PART 2: Below is the code related to updating the actual endpoint
const exec = require("child_process").exec;
const currentSkillManifest = require("./skill-package/skill.json");
// A functuon to execute a shell command and return back result via
// a callback
async function execute(command, callback) {
console.log("command: " + command);
await exec(command, function async(error, stdout, stderr) {
if (error) {
return Promise.reject("Error executing command");
}
callback(error, stdout, stderr);
});
}
function getCurrentSkillManifest(error, output, stderr) {
skill_manifest = JSON.parse(output);
let endpoint = skill_manifest.manifest.apis.custom.endpoint;
if (error) console.log("error: " + error);
else console.log("Previous endpoint was: " + JSON.stringify(endpoint));
}
function updateSkillManifest(error, output, stderr) {
if (error) console.log("Update status: " + error);
else console.log("Update status: " + output);
}
async function main() {
let gskillid = await getSkillId();
let command = win32flag
? `ask smapi get-skill-manifest -s ${gskillid} -g "development"`
: `ask smapi get-skill-manifest -s ${gskillid} -g 'development'`;
if (myArgs.length >= 1 && (myArgs[0] === "--ngrok" || myArgs[0] === "-n")) {
const fetch = require("node-fetch");
let namedTunnel = "/command_line";
if (myArgs.length >= 2) namedTunnel = "/" + myArgs[1];
fetch("http://localhost:4040/api/tunnels" + namedTunnel)
.then((res) => res.json())
.then((json) => {
console.log(json);
return json;
})
.then((secureTunnel) => {
// execute the commands
execute(command, getCurrentSkillManifest);
console.log(secureTunnel);
console.log("Updating to new endpoint : " + secureTunnel.public_url);
return secureTunnel.public_url;
})
.then((endpointUrl) => {
// create a new endpoint using the endpoint specified
// certificate is set to Wildcard as per most local debugging
// tutorials suggestions
const newLocalEndpoint = {
sslCertificateType: "Wildcard",
uri: `${endpointUrl}`,
};
currentSkillManifest.manifest.apis.custom.endpoint = newLocalEndpoint;
let updateCommand = win32flag
? `ask smapi update-skill-manifest -s ${gskillid} -g "development" --manifest "${JSON.stringify(
currentSkillManifest
).replace(/"/g, '\\"')}"`
: `ask smapi update-skill-manifest -s ${gskillid} -g 'development' --manifest '${JSON.stringify(
currentSkillManifest
)}'`;
execute(updateCommand, updateSkillManifest);
})
.catch((err) => {
if (err.code === "ECONNREFUSED") {
return console.error("Error: Looks like you're not running ngrok.");
}
console.error(err);
});
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment