Last active
August 10, 2024 23:21
-
-
Save hwangbible/34170a38bf08bf0cf8410fa14f3cbf45 to your computer and use it in GitHub Desktop.
Scriptablify - lets you require('modules') in Scriptable app!
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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()); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 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
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.