Skip to content

Instantly share code, notes, and snippets.

@hwangbible
Last active December 6, 2023 21:59
Show Gist options
  • Star 21 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save hwangbible/34170a38bf08bf0cf8410fa14f3cbf45 to your computer and use it in GitHub Desktop.
Save hwangbible/34170a38bf08bf0cf8410fa14f3cbf45 to your computer and use it in GitHub Desktop.
Scriptablify - lets you require('modules') in Scriptable app!
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: gray; icon-glyph: magic;
// Defaults to the latest version and no automatic update; if the file exists, just use it
const moment = await require('moment');
// Use any SemVer options to specify a version you want
// Refer to the calculator here: https://semver.npmjs.com/
const lodash = await require('lodash@^3.9.1');
// Pass the second parameter to auto-update or force-download to satisfy the version specified
const d3 = await require('d3@>5.3.0', true);
console.log(moment());
// Variables used by Scriptable.
// These must be at the very top of the file. Do not edit.
// icon-color: gray; icon-glyph: magic;
const fm = FileManager.iCloud();
const dir = fm.documentsDirectory();
const downloadIfNeeded = async (pkg, isAutoUpdateOn) => {
let name = getPackageName(pkg);
let filePath = fm.joinPath(dir, name + '.js');
let isInstalled = await isFound(filePath);
// If the package exists and autoupdate is off, stop checking further
if (isInstalled && !isAutoUpdateOn) {
console.log(`'${name}' is already installed, and autoupdate is disabled! Proceeding to import from disk...`);
return;
}
// Get the package information which satisfies the given semver range
let versionInfo = await getStatus(pkg);
let versions = versionInfo.satisfied;
let version = versionInfo.highest;
// Download the newer version if necessary
if (isInstalled && isAutoUpdateOn) {
let installedVersion = await getInstalledVersion(name);
// Check if the installed version satisfies the semver range
if (versions.includes(installedVersion)) {
console.log(`'${name}@${installedVersion}' satisfies the requested version. Good to go!`);
return;
} else {
console.log(`'${name}@${installedVersion}' doesn't match the version requested. Reinstalling '${version}' now...`);
}
} else {
console.log(`'${name}' was never installed previously. Downloading now...`);
}
// Download the package source and save to disk
let source = await getPackageSource(pkg);
savePackageToDisk(name, version, source);
};
const getInstalledVersion = async name => {
// Read the version from {package}.ver
let filePath = fm.joinPath(dir, name + '.ver');
let version;
if (isFound(filePath)) {
let content = fm.readString(filePath);
if (/^\d+\.\d+\.\d+$/g.test(content)) {
version = content;
}
}
console.log(`The installed version of '${name}' is ${version}.`);
return version;
};
const getPackageSource = async pkg => {
// Get the standalone package source from wzrd.in
let request = new Request(`https://wzrd.in/standalone/${encodeURIComponent(pkg)}`);
let response = await request.loadString();
return response;
};
const getPackageName = pkg => {
return pkg.split('@')[0];
};
const getStatus = async pkg => {
// Retrieve the information about the package
let request = new Request(`https://wzrd.in/status/${encodeURIComponent(pkg)}`);
let response = await request.loadJSON();
// Fail if the response is not good
if (response.statusCode >= 400 || response.ok === false) {
throw response.message;
}
// Fail if the semver did not satisfy any versions available on npm
// Otherwise, sort the versions in descending order
let versions = response.builds && Object.keys(response.builds);
if (versions.length < 1) {
throw `'${pkg}' did not satisfy any versions available on npm!`;
} else {
versions.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }));
}
// Get all the satisfied versions and the highest version
let result = {
highest: versions[0],
satisfied: versions,
};
return result;
};
const isFound = async filePath => {
// Check if the package is already downloaded
if (fm.fileExists(filePath)) {
return true;
}
// Sync with iCloud and check again
await syncFileWithiCloud(filePath);
if (fm.fileExists(filePath)) {
return true;
}
return false;
};
const savePackageToDisk = (name, version, source) => {
// Write the package source and version info to disk
let filename = fm.joinPath(dir, name);
let jsFilePath = filename + '.js';
let versionFilePath = filename + '.ver';
let pkg = `${name}@${version}`;
tryWriteFile(jsFilePath, source, pkg);
tryWriteFile(versionFilePath, version, pkg);
console.log(`Successfully installed ${name}@${version}!`);
};
const syncFileWithiCloud = async filePath => {
// Try to sync with iCloud in case the package exists only on iCloud
try {
console.log(`Attempting to sync with iCloud just in case...`);
await fm.downloadFileFromiCloud(filePath);
console.log(`Finished syncing ${filePath}`);
} catch (err) {
console.log(`${filePath} does not exist on iCloud.`);
}
};
const tryWriteFile = (path, content, pkg) => {
// Sometimes wzrd.in is acting up and the file content is undefined.
// So, here is a little trick to let you know what's going on.
try {
console.log(`Saving ${pkg} to disk at ${path}...`);
fm.writeString(path, content);
} catch (err) {
throw `The package source from 'https://wzrd.in/standalone/${pkg}' is probably corrupted! Try with the different patch version.`;
}
};
module.exports = async (pkg, isAutoUpdateOn = false) => {
let name = getPackageName(pkg);
await downloadIfNeeded(pkg, isAutoUpdateOn);
return importModule(`${name}`);
};
@stevopritchard
Copy link

Hi,

This looks fantastic, but I cannot get your example to work!

Is ‘example.js’ supposed to be used in Scriptable? It doesn’t import from ‘scriptablify.js’ and the ‘require’ object doesn’t exist either.

@dfoverdx
Copy link

@stevopritchard Looks like scriptable has a builtin function for importing modules called importModule(). (I didn't immediately see this, either.)

@hwangbible Awesome! Saves me a lot of time!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment