@shopify/cli-kit
Version:
A set of utilities, interfaces, and models that are common across all the platform features
115 lines • 4.16 kB
JavaScript
import { AbortError, BugError } from './error.js';
import { fileHasWritePermissions, unixFileIsOwnedByCurrentUser } from './fs.js';
import { dirname } from './path.js';
import Config from 'conf';
/**
* A wrapper around the `conf` package that provides a strongly-typed interface
* for accessing the local storage.
*/
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export class LocalStorage {
constructor(options) {
this.config = new Config(options);
}
/**
* Get a value from the local storage.
*
* @param key - The key to get.
* @returns The value.
* @throws AbortError if a permission error occurs.
* @throws BugError if an unexpected error occurs.
*/
get(key) {
try {
return this.config.get(key);
// eslint-disable-next-line no-catch-all/no-catch-all
}
catch (error) {
this.handleError(error, 'get');
}
}
/**
* Set a value in the local storage.
*
* @param key - The key to set.
* @param value - The value to set.
* @throws AbortError if a permission error occurs.
* @throws BugError if an unexpected error occurs.
*/
set(key, value) {
try {
this.config.set(key, value);
// eslint-disable-next-line no-catch-all/no-catch-all
}
catch (error) {
this.handleError(error, 'set');
}
}
/**
* Delete a value from the local storage.
*
* @param key - The key to delete.
* @throws AbortError if a permission error occurs.
* @throws BugError if an unexpected error occurs.
*/
delete(key) {
try {
this.config.delete(key);
// eslint-disable-next-line no-catch-all/no-catch-all
}
catch (error) {
this.handleError(error, 'delete');
}
}
/**
* Clear the local storage (delete all values).
*
* @throws AbortError if a permission error occurs.
* @throws BugError if an unexpected error occurs.
*/
clear() {
try {
this.config.clear();
// eslint-disable-next-line no-catch-all/no-catch-all
}
catch (error) {
this.handleError(error, 'clear');
}
}
/**
* Handle errors from config operations.
* If the error is permission-related, throw an AbortError with helpful hints.
* Otherwise, throw a BugError.
*
* @param error - The error that occurred.
* @param operation - The operation that failed.
* @throws AbortError if the error is permission-related.
* @throws BugError if the error is not permission-related.
*/
handleError(error, operation) {
if (this.isPermissionError()) {
throw new AbortError(`Failed to access local storage (${operation}): ${error}`, this.tryMessage());
}
else {
throw new BugError(`Unexpected error while accessing local storage at ${this.config.path} (${operation}): ${error}`);
}
}
isPermissionError() {
const canAccessFile = fileHasWritePermissions(this.config.path);
const canAccessFolder = fileHasWritePermissions(dirname(this.config.path));
const ownsFile = unixFileIsOwnedByCurrentUser(this.config.path);
return !canAccessFile || !canAccessFolder || ownsFile === false;
}
tryMessage() {
const ownsFile = unixFileIsOwnedByCurrentUser(this.config.path);
const ownsFolder = unixFileIsOwnedByCurrentUser(dirname(this.config.path));
const message = [`Check that you have write permissions for`, { filePath: this.config.path }];
if (ownsFile === false || ownsFolder === false) {
message.push('- The file is owned by a different user. This typically happens when Shopify CLI was previously run with elevated permissions (e.g., sudo).');
}
message.push('\n\nTo resolve this, remove the Shopify CLI preferences folder:');
message.push({ command: `rm -rf ${dirname(this.config.path)}` });
return message;
}
}
//# sourceMappingURL=local-storage.js.map