directify-cli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.gitattributes +2 -0
- package/README.md +297 -0
- package/package.json +32 -0
- package/src/commands/articles.js +193 -0
- package/src/commands/auth.js +54 -0
- package/src/commands/categories.js +146 -0
- package/src/commands/config.js +27 -0
- package/src/commands/directories.js +35 -0
- package/src/commands/fields.js +41 -0
- package/src/commands/listings.js +243 -0
- package/src/commands/tags.js +138 -0
- package/src/index.js +29 -0
- package/src/utils/api.js +94 -0
- package/src/utils/output.js +65 -0
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { api, resolveDirectory } from '../utils/api.js';
|
|
3
|
+
import { printTable, printJson, printSuccess, printError } from '../utils/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
|
|
6
|
+
const categories = new Command('categories')
|
|
7
|
+
.alias('cats')
|
|
8
|
+
.description('Manage categories');
|
|
9
|
+
|
|
10
|
+
categories
|
|
11
|
+
.command('list')
|
|
12
|
+
.alias('ls')
|
|
13
|
+
.description('List all categories')
|
|
14
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
15
|
+
.option('--json', 'Output as JSON')
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
const spinner = ora('Fetching categories...').start();
|
|
18
|
+
try {
|
|
19
|
+
const dir = resolveDirectory(opts);
|
|
20
|
+
const data = await api.get(`/directories/${dir}/categories`);
|
|
21
|
+
spinner.stop();
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
printJson(data.data || data);
|
|
24
|
+
} else {
|
|
25
|
+
printTable(data.data || data, [
|
|
26
|
+
{ key: 'id', label: 'ID' },
|
|
27
|
+
{ key: 'title', label: 'Title', maxWidth: 30 },
|
|
28
|
+
{ key: 'slug', label: 'Slug', maxWidth: 30 },
|
|
29
|
+
{ key: 'is_active', label: 'Active' },
|
|
30
|
+
{ key: 'order', label: 'Order' },
|
|
31
|
+
]);
|
|
32
|
+
}
|
|
33
|
+
} catch (err) {
|
|
34
|
+
spinner.stop();
|
|
35
|
+
printError(err.message);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
categories
|
|
41
|
+
.command('get <id>')
|
|
42
|
+
.description('Get a specific category')
|
|
43
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
44
|
+
.option('--json', 'Output as JSON')
|
|
45
|
+
.action(async (id, opts) => {
|
|
46
|
+
try {
|
|
47
|
+
const dir = resolveDirectory(opts);
|
|
48
|
+
const data = await api.get(`/directories/${dir}/categories/${id}`);
|
|
49
|
+
printJson(data.data || data);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
printError(err.message);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
categories
|
|
57
|
+
.command('create')
|
|
58
|
+
.description('Create a new category')
|
|
59
|
+
.requiredOption('--title <title>', 'Category title')
|
|
60
|
+
.option('--slug <slug>', 'URL slug')
|
|
61
|
+
.option('--description <text>', 'Description')
|
|
62
|
+
.option('--content <text>', 'Content (markdown)')
|
|
63
|
+
.option('--icon <icon>', 'Icon (emoji or URL)')
|
|
64
|
+
.option('--parent-id <id>', 'Parent category ID')
|
|
65
|
+
.option('--order <n>', 'Sort order', parseInt)
|
|
66
|
+
.option('--inactive', 'Create as inactive')
|
|
67
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
68
|
+
.action(async (opts) => {
|
|
69
|
+
const spinner = ora('Creating category...').start();
|
|
70
|
+
try {
|
|
71
|
+
const dir = resolveDirectory(opts);
|
|
72
|
+
const body = {
|
|
73
|
+
title: opts.title,
|
|
74
|
+
...(opts.slug && { slug: opts.slug }),
|
|
75
|
+
...(opts.description && { description: opts.description }),
|
|
76
|
+
...(opts.content && { content: opts.content }),
|
|
77
|
+
...(opts.icon && { icon: opts.icon }),
|
|
78
|
+
...(opts.parentId && { parent_id: parseInt(opts.parentId) }),
|
|
79
|
+
...(opts.order !== undefined && { order: opts.order }),
|
|
80
|
+
is_active: !opts.inactive,
|
|
81
|
+
};
|
|
82
|
+
const data = await api.post(`/directories/${dir}/categories`, body);
|
|
83
|
+
spinner.stop();
|
|
84
|
+
printSuccess(`Category created: ${data.data?.title || data.title} (ID: ${data.data?.id || data.id})`);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
spinner.stop();
|
|
87
|
+
printError(err.message);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
categories
|
|
93
|
+
.command('update <id>')
|
|
94
|
+
.description('Update a category')
|
|
95
|
+
.option('--title <title>', 'Category title')
|
|
96
|
+
.option('--slug <slug>', 'URL slug')
|
|
97
|
+
.option('--description <text>', 'Description')
|
|
98
|
+
.option('--content <text>', 'Content')
|
|
99
|
+
.option('--icon <icon>', 'Icon')
|
|
100
|
+
.option('--parent-id <id>', 'Parent category ID')
|
|
101
|
+
.option('--order <n>', 'Sort order', parseInt)
|
|
102
|
+
.option('--active <bool>', 'Active status (true/false)')
|
|
103
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
104
|
+
.action(async (id, opts) => {
|
|
105
|
+
const spinner = ora('Updating category...').start();
|
|
106
|
+
try {
|
|
107
|
+
const dir = resolveDirectory(opts);
|
|
108
|
+
const body = {};
|
|
109
|
+
if (opts.title) body.title = opts.title;
|
|
110
|
+
if (opts.slug) body.slug = opts.slug;
|
|
111
|
+
if (opts.description) body.description = opts.description;
|
|
112
|
+
if (opts.content) body.content = opts.content;
|
|
113
|
+
if (opts.icon) body.icon = opts.icon;
|
|
114
|
+
if (opts.parentId) body.parent_id = parseInt(opts.parentId);
|
|
115
|
+
if (opts.order !== undefined) body.order = opts.order;
|
|
116
|
+
if (opts.active !== undefined) body.is_active = opts.active === 'true';
|
|
117
|
+
|
|
118
|
+
const data = await api.put(`/directories/${dir}/categories/${id}`, body);
|
|
119
|
+
spinner.stop();
|
|
120
|
+
printSuccess(`Category updated: ${data.data?.title || data.title}`);
|
|
121
|
+
} catch (err) {
|
|
122
|
+
spinner.stop();
|
|
123
|
+
printError(err.message);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
categories
|
|
129
|
+
.command('delete <id>')
|
|
130
|
+
.description('Delete a category')
|
|
131
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
132
|
+
.action(async (id, opts) => {
|
|
133
|
+
const spinner = ora('Deleting category...').start();
|
|
134
|
+
try {
|
|
135
|
+
const dir = resolveDirectory(opts);
|
|
136
|
+
await api.delete(`/directories/${dir}/categories/${id}`);
|
|
137
|
+
spinner.stop();
|
|
138
|
+
printSuccess(`Category ${id} deleted.`);
|
|
139
|
+
} catch (err) {
|
|
140
|
+
spinner.stop();
|
|
141
|
+
printError(err.message);
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
export default categories;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getDefaultDirectory, setDefaultDirectory } from '../utils/api.js';
|
|
3
|
+
import { printSuccess, printInfo } from '../utils/output.js';
|
|
4
|
+
|
|
5
|
+
const config = new Command('config').description('Manage CLI configuration');
|
|
6
|
+
|
|
7
|
+
config
|
|
8
|
+
.command('set-directory <id>')
|
|
9
|
+
.description('Set the default directory ID (used when --directory is not specified)')
|
|
10
|
+
.action((id) => {
|
|
11
|
+
setDefaultDirectory(id);
|
|
12
|
+
printSuccess(`Default directory set to ${id}`);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
config
|
|
16
|
+
.command('get-directory')
|
|
17
|
+
.description('Show the current default directory ID')
|
|
18
|
+
.action(() => {
|
|
19
|
+
const dir = getDefaultDirectory();
|
|
20
|
+
if (dir) {
|
|
21
|
+
printInfo(`Default directory: ${dir}`);
|
|
22
|
+
} else {
|
|
23
|
+
printInfo('No default directory set. Use: directify config set-directory <id>');
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export default config;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { api } from '../utils/api.js';
|
|
3
|
+
import { printTable, printJson, printError } from '../utils/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
|
|
6
|
+
const directories = new Command('directories')
|
|
7
|
+
.alias('dirs')
|
|
8
|
+
.description('List your directories');
|
|
9
|
+
|
|
10
|
+
directories
|
|
11
|
+
.command('list')
|
|
12
|
+
.alias('ls')
|
|
13
|
+
.description('List all directories you own')
|
|
14
|
+
.option('--json', 'Output as JSON')
|
|
15
|
+
.action(async (opts) => {
|
|
16
|
+
const spinner = ora('Fetching directories...').start();
|
|
17
|
+
try {
|
|
18
|
+
const data = await api.get('/directories');
|
|
19
|
+
spinner.stop();
|
|
20
|
+
if (opts.json) {
|
|
21
|
+
printJson(data.data || data);
|
|
22
|
+
} else {
|
|
23
|
+
printTable(data.data || data, [
|
|
24
|
+
{ key: 'id', label: 'ID' },
|
|
25
|
+
{ key: 'name', label: 'Name', maxWidth: 40 },
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
} catch (err) {
|
|
29
|
+
spinner.stop();
|
|
30
|
+
printError(err.message);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export default directories;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { api, resolveDirectory } from '../utils/api.js';
|
|
3
|
+
import { printTable, printJson, printError } from '../utils/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
|
|
6
|
+
const fields = new Command('fields').description('List custom fields');
|
|
7
|
+
|
|
8
|
+
fields
|
|
9
|
+
.command('list')
|
|
10
|
+
.alias('ls')
|
|
11
|
+
.description('List all custom fields for a directory')
|
|
12
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
13
|
+
.option('--json', 'Output as JSON')
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
const spinner = ora('Fetching custom fields...').start();
|
|
16
|
+
try {
|
|
17
|
+
const dir = resolveDirectory(opts);
|
|
18
|
+
const data = await api.get(`/directories/${dir}/custom-fields`);
|
|
19
|
+
spinner.stop();
|
|
20
|
+
const items = data.data || data;
|
|
21
|
+
if (opts.json) {
|
|
22
|
+
printJson(items);
|
|
23
|
+
} else {
|
|
24
|
+
printTable(items, [
|
|
25
|
+
{ key: 'id', label: 'ID' },
|
|
26
|
+
{ key: 'name', label: 'Name', maxWidth: 25 },
|
|
27
|
+
{ key: 'label', label: 'Label', maxWidth: 25 },
|
|
28
|
+
{ key: 'type', label: 'Type' },
|
|
29
|
+
{ key: 'is_required', label: 'Required' },
|
|
30
|
+
{ key: 'filterable', label: 'Filterable' },
|
|
31
|
+
{ key: 'show_on_card', label: 'On Card' },
|
|
32
|
+
]);
|
|
33
|
+
}
|
|
34
|
+
} catch (err) {
|
|
35
|
+
spinner.stop();
|
|
36
|
+
printError(err.message);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export default fields;
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { api, resolveDirectory } from '../utils/api.js';
|
|
3
|
+
import { printTable, printJson, printSuccess, printError } from '../utils/output.js';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
import ora from 'ora';
|
|
6
|
+
|
|
7
|
+
const listings = new Command('listings').description('Manage listings (projects)');
|
|
8
|
+
|
|
9
|
+
listings
|
|
10
|
+
.command('list')
|
|
11
|
+
.alias('ls')
|
|
12
|
+
.description('List all listings (paginated)')
|
|
13
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
14
|
+
.option('--page <n>', 'Page number', '1')
|
|
15
|
+
.option('--json', 'Output as JSON')
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
const spinner = ora('Fetching listings...').start();
|
|
18
|
+
try {
|
|
19
|
+
const dir = resolveDirectory(opts);
|
|
20
|
+
const data = await api.get(`/directories/${dir}/projects?page=${opts.page}`);
|
|
21
|
+
spinner.stop();
|
|
22
|
+
if (opts.json) {
|
|
23
|
+
printJson(data);
|
|
24
|
+
} else {
|
|
25
|
+
const items = data.data || data;
|
|
26
|
+
printTable(items, [
|
|
27
|
+
{ key: 'id', label: 'ID' },
|
|
28
|
+
{ key: 'name', label: 'Name', maxWidth: 35 },
|
|
29
|
+
{ key: 'slug', label: 'Slug', maxWidth: 25 },
|
|
30
|
+
{ key: 'is_active', label: 'Active' },
|
|
31
|
+
{ key: 'is_featured', label: 'Featured' },
|
|
32
|
+
]);
|
|
33
|
+
if (data.meta) {
|
|
34
|
+
console.log(`\nPage ${data.meta.current_page} of ${data.meta.last_page} (${data.meta.total} total)`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (err) {
|
|
38
|
+
spinner.stop();
|
|
39
|
+
printError(err.message);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
listings
|
|
45
|
+
.command('get <id>')
|
|
46
|
+
.description('Get a specific listing')
|
|
47
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
48
|
+
.action(async (id, opts) => {
|
|
49
|
+
try {
|
|
50
|
+
const dir = resolveDirectory(opts);
|
|
51
|
+
const data = await api.get(`/directories/${dir}/projects/${id}`);
|
|
52
|
+
printJson(data.data || data);
|
|
53
|
+
} catch (err) {
|
|
54
|
+
printError(err.message);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
listings
|
|
60
|
+
.command('create')
|
|
61
|
+
.description('Create a new listing')
|
|
62
|
+
.requiredOption('--name <name>', 'Listing name')
|
|
63
|
+
.option('--url <url>', 'Website URL')
|
|
64
|
+
.option('--slug <slug>', 'URL slug')
|
|
65
|
+
.option('--description <text>', 'Short description')
|
|
66
|
+
.option('--content <text>', 'Full content (markdown)')
|
|
67
|
+
.option('--image-url <url>', 'Cover image URL')
|
|
68
|
+
.option('--logo-url <url>', 'Logo URL')
|
|
69
|
+
.option('--phone <number>', 'Phone number')
|
|
70
|
+
.option('--email <email>', 'Email address')
|
|
71
|
+
.option('--address <address>', 'Physical address')
|
|
72
|
+
.option('--lat <n>', 'Latitude', parseFloat)
|
|
73
|
+
.option('--lng <n>', 'Longitude', parseFloat)
|
|
74
|
+
.option('--categories <ids>', 'Category IDs (comma-separated)')
|
|
75
|
+
.option('--tags <ids>', 'Tag IDs (comma-separated)')
|
|
76
|
+
.option('--featured', 'Mark as featured')
|
|
77
|
+
.option('--inactive', 'Create as inactive')
|
|
78
|
+
.option('--field <key=value...>', 'Custom field values (repeatable)', collect, [])
|
|
79
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
80
|
+
.action(async (opts) => {
|
|
81
|
+
const spinner = ora('Creating listing...').start();
|
|
82
|
+
try {
|
|
83
|
+
const dir = resolveDirectory(opts);
|
|
84
|
+
const body = { name: opts.name };
|
|
85
|
+
if (opts.url) body.url = opts.url;
|
|
86
|
+
if (opts.slug) body.slug = opts.slug;
|
|
87
|
+
if (opts.description) body.description = opts.description;
|
|
88
|
+
if (opts.content) body.content = opts.content;
|
|
89
|
+
if (opts.imageUrl) body.image_url = opts.imageUrl;
|
|
90
|
+
if (opts.logoUrl) body.logo_url = opts.logoUrl;
|
|
91
|
+
if (opts.phone) body.phone_number = opts.phone;
|
|
92
|
+
if (opts.email) body.email = opts.email;
|
|
93
|
+
if (opts.address) body.address = opts.address;
|
|
94
|
+
if (opts.lat) body.latitude = opts.lat;
|
|
95
|
+
if (opts.lng) body.longitude = opts.lng;
|
|
96
|
+
if (opts.categories) body.categories = opts.categories.split(',').map(Number);
|
|
97
|
+
if (opts.tags) body.tags = opts.tags.split(',').map(Number);
|
|
98
|
+
if (opts.featured) body.is_featured = true;
|
|
99
|
+
if (opts.inactive) body.is_active = false;
|
|
100
|
+
|
|
101
|
+
// Custom fields
|
|
102
|
+
for (const field of opts.field) {
|
|
103
|
+
const [key, ...rest] = field.split('=');
|
|
104
|
+
body[key] = rest.join('=');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const data = await api.post(`/directories/${dir}/projects`, body);
|
|
108
|
+
spinner.stop();
|
|
109
|
+
const result = data.data || data;
|
|
110
|
+
printSuccess(`Listing created: ${result.name} (ID: ${result.id})`);
|
|
111
|
+
} catch (err) {
|
|
112
|
+
spinner.stop();
|
|
113
|
+
printError(err.message);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
listings
|
|
119
|
+
.command('update <id>')
|
|
120
|
+
.description('Update a listing')
|
|
121
|
+
.option('--name <name>', 'Listing name')
|
|
122
|
+
.option('--url <url>', 'Website URL')
|
|
123
|
+
.option('--slug <slug>', 'URL slug')
|
|
124
|
+
.option('--description <text>', 'Short description')
|
|
125
|
+
.option('--content <text>', 'Full content (markdown)')
|
|
126
|
+
.option('--image-url <url>', 'Cover image URL')
|
|
127
|
+
.option('--logo-url <url>', 'Logo URL')
|
|
128
|
+
.option('--phone <number>', 'Phone number')
|
|
129
|
+
.option('--email <email>', 'Email address')
|
|
130
|
+
.option('--address <address>', 'Physical address')
|
|
131
|
+
.option('--lat <n>', 'Latitude', parseFloat)
|
|
132
|
+
.option('--lng <n>', 'Longitude', parseFloat)
|
|
133
|
+
.option('--categories <ids>', 'Category IDs (comma-separated)')
|
|
134
|
+
.option('--tags <ids>', 'Tag IDs (comma-separated)')
|
|
135
|
+
.option('--featured <bool>', 'Featured status (true/false)')
|
|
136
|
+
.option('--active <bool>', 'Active status (true/false)')
|
|
137
|
+
.option('--field <key=value...>', 'Custom field values (repeatable)', collect, [])
|
|
138
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
139
|
+
.action(async (id, opts) => {
|
|
140
|
+
const spinner = ora('Updating listing...').start();
|
|
141
|
+
try {
|
|
142
|
+
const dir = resolveDirectory(opts);
|
|
143
|
+
const body = {};
|
|
144
|
+
if (opts.name) body.name = opts.name;
|
|
145
|
+
if (opts.url) body.url = opts.url;
|
|
146
|
+
if (opts.slug) body.slug = opts.slug;
|
|
147
|
+
if (opts.description) body.description = opts.description;
|
|
148
|
+
if (opts.content) body.content = opts.content;
|
|
149
|
+
if (opts.imageUrl) body.image_url = opts.imageUrl;
|
|
150
|
+
if (opts.logoUrl) body.logo_url = opts.logoUrl;
|
|
151
|
+
if (opts.phone) body.phone_number = opts.phone;
|
|
152
|
+
if (opts.email) body.email = opts.email;
|
|
153
|
+
if (opts.address) body.address = opts.address;
|
|
154
|
+
if (opts.lat) body.latitude = opts.lat;
|
|
155
|
+
if (opts.lng) body.longitude = opts.lng;
|
|
156
|
+
if (opts.categories) body.categories = opts.categories.split(',').map(Number);
|
|
157
|
+
if (opts.tags) body.tags = opts.tags.split(',').map(Number);
|
|
158
|
+
if (opts.featured !== undefined) body.is_featured = opts.featured === 'true';
|
|
159
|
+
if (opts.active !== undefined) body.is_active = opts.active === 'true';
|
|
160
|
+
|
|
161
|
+
for (const field of opts.field) {
|
|
162
|
+
const [key, ...rest] = field.split('=');
|
|
163
|
+
body[key] = rest.join('=');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const data = await api.put(`/directories/${dir}/projects/${id}`, body);
|
|
167
|
+
spinner.stop();
|
|
168
|
+
printSuccess(`Listing updated: ${data.data?.name || data.name}`);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
spinner.stop();
|
|
171
|
+
printError(err.message);
|
|
172
|
+
process.exit(1);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
listings
|
|
177
|
+
.command('delete <id>')
|
|
178
|
+
.description('Delete a listing')
|
|
179
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
180
|
+
.action(async (id, opts) => {
|
|
181
|
+
const spinner = ora('Deleting listing...').start();
|
|
182
|
+
try {
|
|
183
|
+
const dir = resolveDirectory(opts);
|
|
184
|
+
await api.delete(`/directories/${dir}/projects/${id}`);
|
|
185
|
+
spinner.stop();
|
|
186
|
+
printSuccess(`Listing ${id} deleted.`);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
spinner.stop();
|
|
189
|
+
printError(err.message);
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
listings
|
|
195
|
+
.command('exists')
|
|
196
|
+
.description('Check if a listing with a given URL exists')
|
|
197
|
+
.requiredOption('--url <url>', 'URL to check')
|
|
198
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
199
|
+
.action(async (opts) => {
|
|
200
|
+
try {
|
|
201
|
+
const dir = resolveDirectory(opts);
|
|
202
|
+
const data = await api.post(`/directories/${dir}/projects/exists`, { url: opts.url });
|
|
203
|
+
printSuccess(data.message);
|
|
204
|
+
} catch (err) {
|
|
205
|
+
printError(err.message);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
listings
|
|
211
|
+
.command('bulk-create')
|
|
212
|
+
.description('Bulk create listings from a JSON file')
|
|
213
|
+
.requiredOption('--file <path>', 'Path to JSON file with listings array')
|
|
214
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
215
|
+
.action(async (opts) => {
|
|
216
|
+
const spinner = ora('Creating listings...').start();
|
|
217
|
+
try {
|
|
218
|
+
const dir = resolveDirectory(opts);
|
|
219
|
+
const content = readFileSync(opts.file, 'utf-8');
|
|
220
|
+
const listings = JSON.parse(content);
|
|
221
|
+
const body = { listings: Array.isArray(listings) ? listings : listings.listings };
|
|
222
|
+
|
|
223
|
+
const data = await api.post(`/directories/${dir}/projects/bulk`, body);
|
|
224
|
+
spinner.stop();
|
|
225
|
+
printSuccess(`Created: ${data.total_created}, Errors: ${data.total_errors}`);
|
|
226
|
+
if (data.errors && data.errors.length > 0) {
|
|
227
|
+
console.log('\nErrors:');
|
|
228
|
+
for (const err of data.errors) {
|
|
229
|
+
printError(` [${err.index}] ${err.name}: ${err.error}`);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
} catch (err) {
|
|
233
|
+
spinner.stop();
|
|
234
|
+
printError(err.message);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
function collect(value, previous) {
|
|
240
|
+
return previous.concat([value]);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export default listings;
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { api, resolveDirectory } from '../utils/api.js';
|
|
3
|
+
import { printTable, printJson, printSuccess, printError } from '../utils/output.js';
|
|
4
|
+
import ora from 'ora';
|
|
5
|
+
|
|
6
|
+
const tags = new Command('tags').description('Manage tags');
|
|
7
|
+
|
|
8
|
+
tags
|
|
9
|
+
.command('list')
|
|
10
|
+
.alias('ls')
|
|
11
|
+
.description('List all tags')
|
|
12
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
13
|
+
.option('--json', 'Output as JSON')
|
|
14
|
+
.action(async (opts) => {
|
|
15
|
+
const spinner = ora('Fetching tags...').start();
|
|
16
|
+
try {
|
|
17
|
+
const dir = resolveDirectory(opts);
|
|
18
|
+
const data = await api.get(`/directories/${dir}/tags`);
|
|
19
|
+
spinner.stop();
|
|
20
|
+
if (opts.json) {
|
|
21
|
+
printJson(data.data || data);
|
|
22
|
+
} else {
|
|
23
|
+
printTable(data.data || data, [
|
|
24
|
+
{ key: 'id', label: 'ID' },
|
|
25
|
+
{ key: 'title', label: 'Title', maxWidth: 30 },
|
|
26
|
+
{ key: 'slug', label: 'Slug', maxWidth: 30 },
|
|
27
|
+
{ key: 'is_active', label: 'Active' },
|
|
28
|
+
]);
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {
|
|
31
|
+
spinner.stop();
|
|
32
|
+
printError(err.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
tags
|
|
38
|
+
.command('get <id>')
|
|
39
|
+
.description('Get a specific tag')
|
|
40
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
41
|
+
.action(async (id, opts) => {
|
|
42
|
+
try {
|
|
43
|
+
const dir = resolveDirectory(opts);
|
|
44
|
+
const data = await api.get(`/directories/${dir}/tags/${id}`);
|
|
45
|
+
printJson(data.data || data);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
printError(err.message);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
tags
|
|
53
|
+
.command('create')
|
|
54
|
+
.description('Create a new tag')
|
|
55
|
+
.requiredOption('--title <title>', 'Tag title')
|
|
56
|
+
.option('--slug <slug>', 'URL slug')
|
|
57
|
+
.option('--color <hex>', 'Background color (hex)')
|
|
58
|
+
.option('--text-color <hex>', 'Text color (hex)')
|
|
59
|
+
.option('--icon <icon>', 'Icon')
|
|
60
|
+
.option('--heroicon <name>', 'Heroicon name')
|
|
61
|
+
.option('--inactive', 'Create as inactive')
|
|
62
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
63
|
+
.action(async (opts) => {
|
|
64
|
+
const spinner = ora('Creating tag...').start();
|
|
65
|
+
try {
|
|
66
|
+
const dir = resolveDirectory(opts);
|
|
67
|
+
const body = {
|
|
68
|
+
title: opts.title,
|
|
69
|
+
...(opts.slug && { slug: opts.slug }),
|
|
70
|
+
...(opts.color && { color: opts.color }),
|
|
71
|
+
...(opts.textColor && { text_color: opts.textColor }),
|
|
72
|
+
...(opts.icon && { icon: opts.icon }),
|
|
73
|
+
...(opts.heroicon && { heroicon: opts.heroicon }),
|
|
74
|
+
is_active: !opts.inactive,
|
|
75
|
+
};
|
|
76
|
+
const data = await api.post(`/directories/${dir}/tags`, body);
|
|
77
|
+
spinner.stop();
|
|
78
|
+
printSuccess(`Tag created: ${data.data?.title || data.title} (ID: ${data.data?.id || data.id})`);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
spinner.stop();
|
|
81
|
+
printError(err.message);
|
|
82
|
+
process.exit(1);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
tags
|
|
87
|
+
.command('update <id>')
|
|
88
|
+
.description('Update a tag')
|
|
89
|
+
.option('--title <title>', 'Tag title')
|
|
90
|
+
.option('--slug <slug>', 'URL slug')
|
|
91
|
+
.option('--color <hex>', 'Background color')
|
|
92
|
+
.option('--text-color <hex>', 'Text color')
|
|
93
|
+
.option('--icon <icon>', 'Icon')
|
|
94
|
+
.option('--heroicon <name>', 'Heroicon name')
|
|
95
|
+
.option('--active <bool>', 'Active status (true/false)')
|
|
96
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
97
|
+
.action(async (id, opts) => {
|
|
98
|
+
const spinner = ora('Updating tag...').start();
|
|
99
|
+
try {
|
|
100
|
+
const dir = resolveDirectory(opts);
|
|
101
|
+
const body = {};
|
|
102
|
+
if (opts.title) body.title = opts.title;
|
|
103
|
+
if (opts.slug) body.slug = opts.slug;
|
|
104
|
+
if (opts.color) body.color = opts.color;
|
|
105
|
+
if (opts.textColor) body.text_color = opts.textColor;
|
|
106
|
+
if (opts.icon) body.icon = opts.icon;
|
|
107
|
+
if (opts.heroicon) body.heroicon = opts.heroicon;
|
|
108
|
+
if (opts.active !== undefined) body.is_active = opts.active === 'true';
|
|
109
|
+
|
|
110
|
+
const data = await api.put(`/directories/${dir}/tags/${id}`, body);
|
|
111
|
+
spinner.stop();
|
|
112
|
+
printSuccess(`Tag updated: ${data.data?.title || data.title}`);
|
|
113
|
+
} catch (err) {
|
|
114
|
+
spinner.stop();
|
|
115
|
+
printError(err.message);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
tags
|
|
121
|
+
.command('delete <id>')
|
|
122
|
+
.description('Delete a tag')
|
|
123
|
+
.option('-d, --directory <id>', 'Directory ID')
|
|
124
|
+
.action(async (id, opts) => {
|
|
125
|
+
const spinner = ora('Deleting tag...').start();
|
|
126
|
+
try {
|
|
127
|
+
const dir = resolveDirectory(opts);
|
|
128
|
+
await api.delete(`/directories/${dir}/tags/${id}`);
|
|
129
|
+
spinner.stop();
|
|
130
|
+
printSuccess(`Tag ${id} deleted.`);
|
|
131
|
+
} catch (err) {
|
|
132
|
+
spinner.stop();
|
|
133
|
+
printError(err.message);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
export default tags;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import auth from './commands/auth.js';
|
|
5
|
+
import config from './commands/config.js';
|
|
6
|
+
import directories from './commands/directories.js';
|
|
7
|
+
import categories from './commands/categories.js';
|
|
8
|
+
import tags from './commands/tags.js';
|
|
9
|
+
import fields from './commands/fields.js';
|
|
10
|
+
import listings from './commands/listings.js';
|
|
11
|
+
import articles from './commands/articles.js';
|
|
12
|
+
|
|
13
|
+
const program = new Command();
|
|
14
|
+
|
|
15
|
+
program
|
|
16
|
+
.name('directify')
|
|
17
|
+
.description('Official CLI for Directify - manage your directory websites from the command line.')
|
|
18
|
+
.version('1.0.0');
|
|
19
|
+
|
|
20
|
+
program.addCommand(auth);
|
|
21
|
+
program.addCommand(config);
|
|
22
|
+
program.addCommand(directories);
|
|
23
|
+
program.addCommand(categories);
|
|
24
|
+
program.addCommand(tags);
|
|
25
|
+
program.addCommand(fields);
|
|
26
|
+
program.addCommand(listings);
|
|
27
|
+
program.addCommand(articles);
|
|
28
|
+
|
|
29
|
+
program.parse();
|