feat(cli): add support for repeated directus tasks
This commit is contained in:
parent
8a12e83b13
commit
1f9e2d856f
11 changed files with 601 additions and 5 deletions
186
cli/lib/tasks/addPost.js
Normal file
186
cli/lib/tasks/addPost.js
Normal file
|
@ -0,0 +1,186 @@
|
|||
import inquirer from 'inquirer';
|
||||
import { loadConfig } from '../config.js';
|
||||
import { initDirectusClient, createItem, searchItems } from '../directus/client.js';
|
||||
import { promptForMultipleRelations } from '../directus/relationHelpers.js';
|
||||
|
||||
const ASSOCIATED_MEDIA_TYPES = ['artists', 'books', 'movies', 'shows', 'genres'];
|
||||
const BLOCK_COLLECTIONS = [
|
||||
'youtube_player',
|
||||
'github_banner',
|
||||
'npm_banner',
|
||||
'rss_banner',
|
||||
'calendar_banner',
|
||||
'forgejo_banner'
|
||||
];
|
||||
|
||||
export const addPost = async () => {
|
||||
const config = await loadConfig();
|
||||
|
||||
initDirectusClient(config);
|
||||
|
||||
const { title, description, content, featured } = await inquirer.prompt([{
|
||||
name: 'title',
|
||||
message: '📝 Title:',
|
||||
validate: input => !!input || 'Title is required'
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
message: '🗒 Description:',
|
||||
default: ''
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
message: '📄 Content:',
|
||||
default: ''
|
||||
},
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'featured',
|
||||
message: '⭐ Featured?',
|
||||
default: false
|
||||
}]);
|
||||
|
||||
let tagIds = [];
|
||||
|
||||
while (true) {
|
||||
const { query } = await inquirer.prompt({
|
||||
name: 'query',
|
||||
message: '🏷 Search for tags (or leave blank to finish):',
|
||||
});
|
||||
const trimmedQuery = query.trim();
|
||||
|
||||
if (!trimmedQuery) break;
|
||||
|
||||
const tags = await searchItems('tags', trimmedQuery);
|
||||
|
||||
if (!tags.length) {
|
||||
console.warn(`⚠️ No tags found matching "${trimmedQuery}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { selected } = await inquirer.prompt({
|
||||
type: 'checkbox',
|
||||
name: 'selected',
|
||||
message: '✔ Select tags to add:',
|
||||
choices: tags.map(tag => ({ name: tag.name, value: tag.id }))
|
||||
});
|
||||
|
||||
tagIds.push(...selected);
|
||||
|
||||
const { again } = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'again',
|
||||
message: 'Search and select more tags?',
|
||||
default: false,
|
||||
});
|
||||
|
||||
if (!again) break;
|
||||
}
|
||||
|
||||
const selectedBlocks = [];
|
||||
const { includeBlocks } = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'includeBlocks',
|
||||
message: '➕ Add blocks?',
|
||||
default: false
|
||||
});
|
||||
|
||||
if (includeBlocks) {
|
||||
while (true) {
|
||||
const { collection } = await inquirer.prompt({
|
||||
type: 'list',
|
||||
name: 'collection',
|
||||
message: '🧱 Choose a block collection (or Cancel to finish):',
|
||||
choices: [...BLOCK_COLLECTIONS, new inquirer.Separator(), 'Cancel']
|
||||
});
|
||||
|
||||
if (collection === 'Cancel') break;
|
||||
|
||||
const { query } = await inquirer.prompt({
|
||||
name: 'query',
|
||||
message: `🔍 Search ${collection}:`
|
||||
});
|
||||
const results = await searchItems(collection, query);
|
||||
|
||||
if (!results.length) {
|
||||
console.warn(`⚠️ No items found in "${collection}" matching "${query}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { itemId } = await inquirer.prompt({
|
||||
type: 'list',
|
||||
name: 'itemId',
|
||||
message: `Select an item from ${collection}:`,
|
||||
choices: results.map(item => ({
|
||||
name: item.title || item.name || item.id,
|
||||
value: item.id
|
||||
}))
|
||||
});
|
||||
|
||||
selectedBlocks.push({ collection, item: itemId });
|
||||
|
||||
const { again } = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'again',
|
||||
message: '➕ Add another block?',
|
||||
default: false
|
||||
});
|
||||
|
||||
if (!again) break;
|
||||
}
|
||||
}
|
||||
|
||||
const associatedMediaPayload = {};
|
||||
const { includeMedia } = await inquirer.prompt({
|
||||
type: 'confirm',
|
||||
name: 'includeMedia',
|
||||
message: '➕ Add associated media?',
|
||||
default: false
|
||||
});
|
||||
|
||||
if (includeMedia) {
|
||||
for (const mediaType of ASSOCIATED_MEDIA_TYPES) {
|
||||
const { query } = await inquirer.prompt({
|
||||
name: 'query',
|
||||
message: `🔎 Search for ${mediaType} to associate (or leave blank to skip):`
|
||||
});
|
||||
|
||||
if (!query.trim()) continue;
|
||||
|
||||
const matches = await searchItems(mediaType, query.trim());
|
||||
|
||||
if (!matches.length) {
|
||||
console.warn(`⚠️ No ${mediaType} found matching "${query.trim()}"`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const { selected } = await inquirer.prompt({
|
||||
type: 'checkbox',
|
||||
name: 'selected',
|
||||
message: `✔ Select ${mediaType} to associate:`,
|
||||
choices: matches.map(m => ({
|
||||
name: m.name_string || m.title || m.name || m.label || m.id,
|
||||
value: m.id
|
||||
}))
|
||||
});
|
||||
|
||||
if (selected.length) associatedMediaPayload[`${mediaType}`] = selected.map(id => ({ [`${mediaType}_id`]: id }));
|
||||
}
|
||||
}
|
||||
|
||||
const media = await promptForMultipleRelations('media', 'Associated media');
|
||||
const payload = {
|
||||
title,
|
||||
description,
|
||||
content,
|
||||
featured,
|
||||
date: new Date().toISOString(),
|
||||
post_tags: tagIds.map(tagId => ({ tags_id: tagId })),
|
||||
blocks: selectedBlocks,
|
||||
...associatedMediaPayload
|
||||
};
|
||||
|
||||
await createItem('posts', payload);
|
||||
|
||||
console.log('✅ Post created successfully.');
|
||||
};
|
Loading…
Add table
Add a link
Reference in a new issue