chore(cli): improve missing values, skipping prompts and overwriting files
This commit is contained in:
parent
56e53e2a33
commit
75df36acc3
4 changed files with 105 additions and 54 deletions
|
@ -11,7 +11,7 @@ import { handleExitError } from '../lib/handlers.js';
|
||||||
process.on('unhandledRejection', (err) => handleExitError(err, 'Unhandled rejection'));
|
process.on('unhandledRejection', (err) => handleExitError(err, 'Unhandled rejection'));
|
||||||
process.on('uncaughtException', (err) => handleExitError(err, 'Uncaught exception'));
|
process.on('uncaughtException', (err) => handleExitError(err, 'Uncaught exception'));
|
||||||
|
|
||||||
program.name('coryd').description('🪄 Run commands, download things and have fun.').version('1.0.0');
|
program.name('coryd').description('🪄 Run commands, download things and have fun.').version('1.1.0');
|
||||||
program.command('init').description('Initialize CLI and populate required config.').action(initConfig);
|
program.command('init').description('Initialize CLI and populate required config.').action(initConfig);
|
||||||
program.command('run [script]').description('Run site scripts and commands.').action(runRootScript);
|
program.command('run [script]').description('Run site scripts and commands.').action(runRootScript);
|
||||||
program.command('download').description('Download, name and store image assets.').action(downloadAsset);
|
program.command('download').description('Download, name and store image assets.').action(downloadAsset);
|
||||||
|
|
|
@ -17,36 +17,69 @@ const downloadImage = (url, dest) => new Promise((resolve, reject) => {
|
||||||
file.on('finish', () => file.close(resolve));
|
file.on('finish', () => file.close(resolve));
|
||||||
}).on('error', reject);
|
}).on('error', reject);
|
||||||
});
|
});
|
||||||
const isValidTMDBUrl = (val) =>
|
const isValidTMDBUrl = (val) => /^https:\/\/image\.tmdb\.org\/t\/p\//.test(val) || '❌ Must be a valid TMDB image url';
|
||||||
/^https:\/\/image\.tmdb\.org\/t\/p\//.test(val) || '❌ Must be a valid TMDB image url';
|
const overwriteImageDownloadPrompt = async (url, finalPath, fileName) => {
|
||||||
|
await fs.ensureDir(path.dirname(finalPath));
|
||||||
|
|
||||||
|
if (await fs.pathExists(finalPath)) {
|
||||||
|
const { overwrite } = await inquirer.prompt({
|
||||||
|
type: 'confirm',
|
||||||
|
name: 'overwrite',
|
||||||
|
message: `${fileName} already exists. Overwrite?`,
|
||||||
|
default: false
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!overwrite) {
|
||||||
|
console.log(`⚠️ Skipped existing file: ${fileName}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await downloadImage(url, finalPath);
|
||||||
|
console.log(`✅ Saved to: ${finalPath}`);
|
||||||
|
};
|
||||||
|
|
||||||
export const downloadWatchingImages = async () => {
|
export const downloadWatchingImages = async () => {
|
||||||
const config = await loadConfig();
|
const config = await loadConfig();
|
||||||
const { mediaType } = await inquirer.prompt([{
|
const { mediaType } = await inquirer.prompt({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'mediaType',
|
name: 'mediaType',
|
||||||
message: 'Movie or a show?',
|
message: 'Movie or a show?',
|
||||||
choices: Object.keys(config.mediaPaths)
|
choices: Object.keys(config.mediaPaths)
|
||||||
}]);
|
});
|
||||||
const { tmdbId } = await inquirer.prompt([{
|
const { tmdbId } = await inquirer.prompt({
|
||||||
name: 'tmdbId',
|
name: 'tmdbId',
|
||||||
message: 'Enter the TMDB ID:'
|
message: 'Enter the TMDB ID:'
|
||||||
}]);
|
});
|
||||||
|
|
||||||
|
if (!tmdbId) {
|
||||||
|
console.warn('⚠️ TMDB ID is required.')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const { posterUrl, backdropUrl } = await inquirer.prompt([{
|
const { posterUrl, backdropUrl } = await inquirer.prompt([{
|
||||||
name: 'posterUrl',
|
name: 'posterUrl',
|
||||||
message: 'Enter the poster url:',
|
message: 'Enter the poster url:',
|
||||||
validate: isValidTMDBUrl
|
validate: (val) => {
|
||||||
|
if (!val) return true;
|
||||||
|
return isValidTMDBUrl(val);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'backdropUrl',
|
name: 'backdropUrl',
|
||||||
message: 'Enter the backdrop url:',
|
message: 'Enter the backdrop url:',
|
||||||
validate: isValidTMDBUrl
|
validate: (val) => {
|
||||||
|
if (!val) return true;
|
||||||
|
return isValidTMDBUrl(val);
|
||||||
|
}
|
||||||
}]);
|
}]);
|
||||||
const types = [{ type: 'poster', url: posterUrl },
|
const types = [{ type: 'poster', url: posterUrl }, { type: 'backdrop', url: backdropUrl }];
|
||||||
{ type: 'backdrop', url: backdropUrl }];
|
|
||||||
|
|
||||||
for (const { type, url } of types) {
|
for (const { type, url } of types) {
|
||||||
const fileName = `${type}-${tmdbId}.jpg`;
|
if (!url) continue;
|
||||||
|
|
||||||
|
const ext = path.extname(new URL(url).pathname) || '.jpg';
|
||||||
|
const fileName = `${type}-${tmdbId}${ext}`;
|
||||||
const targetSubPath = config.mediaPaths[mediaType][type];
|
const targetSubPath = config.mediaPaths[mediaType][type];
|
||||||
|
|
||||||
if (!targetSubPath) {
|
if (!targetSubPath) {
|
||||||
|
@ -57,20 +90,23 @@ export const downloadWatchingImages = async () => {
|
||||||
const targetDir = path.join(config.storageDir, targetSubPath);
|
const targetDir = path.join(config.storageDir, targetSubPath);
|
||||||
const finalPath = path.join(targetDir, fileName);
|
const finalPath = path.join(targetDir, fileName);
|
||||||
|
|
||||||
await fs.ensureDir(targetDir);
|
await overwriteImageDownloadPrompt(url, finalPath, fileName);
|
||||||
await downloadImage(url, finalPath);
|
|
||||||
|
|
||||||
console.log(`✅ Saved ${type} to: ${finalPath}`);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const downloadArtistImage = async () => {
|
export const downloadArtistImage = async () => {
|
||||||
const config = await loadConfig();
|
const config = await loadConfig();
|
||||||
const { artistName, imageUrl } = await inquirer.prompt([{
|
const { artistName } = await inquirer.prompt({
|
||||||
name: 'artistName',
|
name: 'artistName',
|
||||||
message: 'Enter the artist name:'
|
message: 'Enter the artist name:'
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
if (!artistName) {
|
||||||
|
console.warn('⚠️ Artist name is required.')
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { imageUrl } = await inquirer.prompt({
|
||||||
name: 'imageUrl',
|
name: 'imageUrl',
|
||||||
message: 'Enter the artist image url:',
|
message: 'Enter the artist image url:',
|
||||||
validate: (val) => {
|
validate: (val) => {
|
||||||
|
@ -81,29 +117,41 @@ export const downloadArtistImage = async () => {
|
||||||
return '❌ Must be a valid url.';
|
return '❌ Must be a valid url.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]);
|
});
|
||||||
|
|
||||||
const sanitizedName = sanitizeMediaString(artistName);
|
const sanitizedName = sanitizeMediaString(artistName);
|
||||||
const fileName = `${sanitizedName}.jpg`;
|
const ext = path.extname(new URL(imageUrl).pathname) || '.jpg';
|
||||||
|
const fileName = `${sanitizedName}${ext}`;
|
||||||
const targetDir = path.join(config.storageDir, 'artists');
|
const targetDir = path.join(config.storageDir, 'artists');
|
||||||
const finalPath = path.join(targetDir, fileName);
|
const finalPath = path.join(targetDir, fileName);
|
||||||
|
|
||||||
await fs.ensureDir(targetDir);
|
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
|
||||||
await downloadImage(imageUrl, finalPath);
|
|
||||||
|
|
||||||
console.log(`✅ Saved artist image to: ${finalPath}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const downloadAlbumImage = async () => {
|
export const downloadAlbumImage = async () => {
|
||||||
const config = await loadConfig();
|
const config = await loadConfig();
|
||||||
const { artistName, albumName, imageUrl } = await inquirer.prompt([{
|
|
||||||
|
const { artistName } = await inquirer.prompt({
|
||||||
name: 'artistName',
|
name: 'artistName',
|
||||||
message: 'Enter the artist name:'
|
message: 'Enter the artist name:'
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
if (!artistName) {
|
||||||
|
console.warn('⚠️ Artist name is required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { albumName } = await inquirer.prompt({
|
||||||
name: 'albumName',
|
name: 'albumName',
|
||||||
message: 'Enter the album name:'
|
message: 'Enter the album name:'
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
if (!albumName) {
|
||||||
|
console.warn('⚠️ Album name is required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { imageUrl } = await inquirer.prompt({
|
||||||
name: 'imageUrl',
|
name: 'imageUrl',
|
||||||
message: 'Enter the album image url:',
|
message: 'Enter the album image url:',
|
||||||
validate: (val) => {
|
validate: (val) => {
|
||||||
|
@ -114,31 +162,36 @@ export const downloadAlbumImage = async () => {
|
||||||
return '❌ Must be a valid url.';
|
return '❌ Must be a valid url.';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]);
|
});
|
||||||
|
|
||||||
const artistSlug = sanitizeMediaString(artistName);
|
const artistSlug = sanitizeMediaString(artistName);
|
||||||
const albumSlug = sanitizeMediaString(albumName);
|
const albumSlug = sanitizeMediaString(albumName);
|
||||||
const fileName = `${artistSlug}-${albumSlug}.jpg`;
|
const ext = path.extname(new URL(imageUrl).pathname) || '.jpg';
|
||||||
|
const fileName = `${artistSlug}-${albumSlug}${ext}`;
|
||||||
const targetDir = path.join(config.storageDir, config.albumPath);
|
const targetDir = path.join(config.storageDir, config.albumPath);
|
||||||
const finalPath = path.join(targetDir, fileName);
|
const finalPath = path.join(targetDir, fileName);
|
||||||
|
|
||||||
await fs.ensureDir(targetDir);
|
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
|
||||||
await downloadImage(imageUrl, finalPath);
|
};
|
||||||
|
|
||||||
console.log(`✅ Saved album image to: ${finalPath}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
export const downloadBookImage = async () => {
|
export const downloadBookImage = async () => {
|
||||||
const config = await loadConfig();
|
const config = await loadConfig();
|
||||||
const { isbn, bookTitle, imageUrl } = await inquirer.prompt([{
|
const { isbn } = await inquirer.prompt({
|
||||||
name: 'isbn',
|
name: 'isbn',
|
||||||
message: 'Enter the ISBN (no spaces):',
|
message: 'Enter the ISBN (no spaces):',
|
||||||
validate: (val) => /^[a-zA-Z0-9-]+$/.test(val) || 'ISBN must contain only letters, numbers, or hyphens'
|
validate: (val) => /^[a-zA-Z0-9-]+$/.test(val) || 'ISBN must contain only letters, numbers, or hyphens'
|
||||||
},
|
});
|
||||||
{
|
const { bookTitle } = await inquirer.prompt({
|
||||||
name: 'bookTitle',
|
name: 'bookTitle',
|
||||||
message: 'Enter the book title:'
|
message: 'Enter the book title:'
|
||||||
},
|
});
|
||||||
{
|
|
||||||
|
if (!bookTitle) {
|
||||||
|
console.warn('⚠️ Book title is required.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { imageUrl } = await inquirer.prompt({
|
||||||
name: 'imageUrl',
|
name: 'imageUrl',
|
||||||
message: 'Enter the book cover image URL:',
|
message: 'Enter the book cover image URL:',
|
||||||
validate: (val) => {
|
validate: (val) => {
|
||||||
|
@ -149,26 +202,24 @@ export const downloadBookImage = async () => {
|
||||||
return 'Must be a valid URL';
|
return 'Must be a valid URL';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]);
|
});
|
||||||
const titleSlug = sanitizeMediaString(bookTitle);
|
const titleSlug = sanitizeMediaString(bookTitle);
|
||||||
const fileName = `${isbn}-${titleSlug}.jpg`;
|
const ext = path.extname(new URL(imageUrl).pathname) || '.jpg';
|
||||||
|
const fileName = `${isbn}-${titleSlug}${ext}`;
|
||||||
const targetDir = path.join(config.storageDir, config.bookPath);
|
const targetDir = path.join(config.storageDir, config.bookPath);
|
||||||
const finalPath = path.join(targetDir, fileName);
|
const finalPath = path.join(targetDir, fileName);
|
||||||
|
|
||||||
await fs.ensureDir(targetDir);
|
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
|
||||||
await downloadImage(imageUrl, finalPath);
|
|
||||||
|
|
||||||
console.log(`✅ Saved book cover to: ${finalPath}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const downloadAsset = async () => {
|
export const downloadAsset = async () => {
|
||||||
const { type } = await inquirer.prompt([{
|
const { type } = await inquirer.prompt({
|
||||||
type: 'list',
|
type: 'list',
|
||||||
name: 'type',
|
name: 'type',
|
||||||
message: 'What type of asset are you downloading?',
|
message: 'What type of asset are you downloading?',
|
||||||
choices: ['movie/show', 'artist', 'album', 'book']
|
choices: ['movie/show', 'artist', 'album', 'book']
|
||||||
}]);
|
});
|
||||||
|
|
||||||
if (type === 'artist') {
|
if (type === 'artist') {
|
||||||
await downloadArtistImage();
|
await downloadArtistImage();
|
||||||
|
|
4
cli/package-lock.json
generated
4
cli/package-lock.json
generated
|
@ -1,12 +1,12 @@
|
||||||
{
|
{
|
||||||
"name": "coryd",
|
"name": "coryd",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "coryd",
|
"name": "coryd",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"chalk": "^5.4.1",
|
"chalk": "^5.4.1",
|
||||||
"commander": "^14.0.0",
|
"commander": "^14.0.0",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "coryd",
|
"name": "coryd",
|
||||||
"version": "1.0.0",
|
"version": "1.1.0",
|
||||||
"description": "The CLI for my site that helps manage assets and other changes.",
|
"description": "The CLI for my site to run scripts, manage and download assets.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"bin": {
|
"bin": {
|
||||||
"coryd": "./bin/index.js"
|
"coryd": "./bin/index.js"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue