@uss-stargazer/job-queue 0.0.2 → 0.0.4
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/README.md +27 -1
- package/dist/data/config.js +3 -2
- package/dist/data/projectpool.js +9 -0
- package/dist/index.js +49 -35
- package/dist/utils/promptUser.js +10 -23
- package/index.js +6 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -22,7 +22,32 @@ job-queue --help
|
|
|
22
22
|
jobq --help
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
##
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
***TLDR***: The CLI tries to be user friendly, so just use it...
|
|
28
|
+
|
|
29
|
+
It's just based on a few files: config.json, jobqueue.json, and projectpool.json. Strives to be
|
|
30
|
+
a bit like Git in the sense that it opens data structures in the user's editor for updating data.
|
|
31
|
+
|
|
32
|
+
### JSON schemas
|
|
33
|
+
|
|
34
|
+
It's recommended that you use an editor or editor plugin with some sort of intellisense for JSON
|
|
35
|
+
schemas, as it makes it much easier to work with.
|
|
36
|
+
|
|
37
|
+
If this isn't available, the expected structures can be found in the Zod schemas in
|
|
38
|
+
`src/data/$TARGET.ts` (at the top of each).
|
|
39
|
+
|
|
40
|
+
### Config options
|
|
41
|
+
|
|
42
|
+
- `editor`: string to edit file when filepath is appended. If not specified, is infered from
|
|
43
|
+
environment vars or from your Git configuration, if you have that installed.
|
|
44
|
+
- `confirmGistUpdates`: whether it should check before pushing or pulling gists from GitHub, if you
|
|
45
|
+
have any set up (see [here](#syncing-with-gists)).
|
|
46
|
+
- `confirmOffline`: whether to check before going into offline mode when there are gists configured
|
|
47
|
+
and `github.com` cannot be resolved.
|
|
48
|
+
- `showBanner`: whether to show ASCII art banner on start
|
|
49
|
+
|
|
50
|
+
### Syncing with gists
|
|
26
51
|
|
|
27
52
|
`job-queue` allows you to sync data files with a gist/gists on your GitHub account. This provides
|
|
28
53
|
cloud storage and makes the program more portable.
|
|
@@ -48,6 +73,7 @@ To link to a gist, provide the gist ID and a GitHub access token in config.json,
|
|
|
48
73
|
|
|
49
74
|
Your gist ID can be copied easily from the URL; usually something like `https://gist.github.com/USERNAME/GIST_ID`.
|
|
50
75
|
|
|
76
|
+
|
|
51
77
|
## Sample Workflow
|
|
52
78
|
|
|
53
79
|
- _\[Daemon\]_ Spontaneous, not fleshed out ideas get immediately added to the project pool as "inactive".
|
package/dist/data/config.js
CHANGED
|
@@ -34,8 +34,9 @@ export const ConfigSchema = z.object({
|
|
|
34
34
|
]),
|
|
35
35
|
schemas: NonemptyString.meta({ title: "Schemas directory", description: `Path to directory containing: ${jsonSchemaNames.map(base => `${base}.schema.json`).join(", ")}.` }).transform((schemasDir) => Object.fromEntries(jsonSchemaNames.map((base) => [base, pathToFileURL(path.resolve(schemasDir, `${base}.schema.json`)).href]))),
|
|
36
36
|
editor: NonemptyString.optional().meta({ title: "Editor command", description: "Command to run editor. Will be run like `<editor> /some/data.json` so make sure it waits." }),
|
|
37
|
-
confirmGistUpdates: z.boolean().optional().
|
|
38
|
-
confirmOffline: z.boolean().optional().
|
|
37
|
+
confirmGistUpdates: z.boolean().optional().describe("Whether to prompt before pushing or pulling a gist from GitHub."),
|
|
38
|
+
confirmOffline: z.boolean().optional().describe("Whether to confirm before using offline (if there are gists in config)."),
|
|
39
|
+
showBanner: z.boolean().optional().describe("Show ASCII art banner on start.")
|
|
39
40
|
});
|
|
40
41
|
export const toInputConfig = (outputData) => {
|
|
41
42
|
const schemaDir = Object.values(outputData.schemas).reduce((schemaDir, schemaPath) => {
|
package/dist/data/projectpool.js
CHANGED
|
@@ -65,5 +65,14 @@ export const updateProject = async (project, projectPool, jobQueue, config) => {
|
|
|
65
65
|
await jobQueue.sync();
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
|
+
if (userDeletedProject && jobsReferencingProject.length > 0) {
|
|
69
|
+
const deleteJobReferences = await confirm({
|
|
70
|
+
message: `You are deleting this project. Would you like to delete all referencing jobs, too?`,
|
|
71
|
+
});
|
|
72
|
+
if (deleteJobReferences) {
|
|
73
|
+
jobQueue.data.queue = jobQueue.data.queue.filter((job) => job.project !== project.name);
|
|
74
|
+
await jobQueue.sync();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
68
77
|
return userDeletedProject ? 'deleted' : updatedProject;
|
|
69
78
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import clear from 'clear';
|
|
3
3
|
import figlet from 'figlet';
|
|
4
|
-
import {
|
|
4
|
+
import { search } from '@inquirer/prompts';
|
|
5
5
|
import { ExitPromptError, Separator } from '@inquirer/core';
|
|
6
6
|
import actions, { actionNames, actionsDependentOnJobs, actionsDependentOnProjects, } from './actions.js';
|
|
7
7
|
import { getConfig, getDataPathFromConfig } from './data/config.js';
|
|
8
8
|
import { getJobQueue } from './data/jobqueue.js';
|
|
9
9
|
import { getProjectPool } from './data/projectpool.js';
|
|
10
|
-
import { editData } from './data/index.js';
|
|
10
|
+
import { dataNames, editData } from './data/index.js';
|
|
11
11
|
import { simpleDeepCompare } from './utils/index.js';
|
|
12
12
|
import dns from 'dns/promises';
|
|
13
13
|
import { inquirerConfirm } from './utils/promptUser.js';
|
|
14
|
+
import { isGistData } from './utils/gistData.js';
|
|
14
15
|
async function loadData(overrideConfigDir, overrideConfig, previousData) {
|
|
15
16
|
const { config, path: configPath, hadToCreate: autoCreateOtherFiles, } = await getConfig(overrideConfigDir, overrideConfig);
|
|
16
17
|
const data = {
|
|
@@ -44,7 +45,7 @@ async function loadData(overrideConfigDir, overrideConfig, previousData) {
|
|
|
44
45
|
});
|
|
45
46
|
if (!config.data.confirmGistUpdates ||
|
|
46
47
|
(await inquirerConfirm(`Pull gist for ${key}?`))) {
|
|
47
|
-
console.log(chalk.blue('[i]'), '
|
|
48
|
+
console.log(chalk.blue('[i]'), 'pulling', key);
|
|
48
49
|
await data[key].pull();
|
|
49
50
|
}
|
|
50
51
|
}
|
|
@@ -60,60 +61,73 @@ async function loadData(overrideConfigDir, overrideConfig, previousData) {
|
|
|
60
61
|
return data;
|
|
61
62
|
}
|
|
62
63
|
async function syncData(data) {
|
|
63
|
-
for (const key of
|
|
64
|
+
for (const key of dataNames) {
|
|
64
65
|
await data[key].sync();
|
|
65
|
-
if (data[key]
|
|
66
|
+
if (isGistData(data[key]) &&
|
|
67
|
+
data[key].isLinked &&
|
|
66
68
|
(!data.config.data.confirmGistUpdates ||
|
|
67
69
|
(await inquirerConfirm(`Push gist for ${key}?`)))) {
|
|
68
|
-
console.log(chalk.blue('[i]'), '
|
|
70
|
+
console.log(chalk.blue('[i]'), 'pushing', key);
|
|
69
71
|
await data[key].push();
|
|
70
72
|
}
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
75
|
export default async function main(overrideConfigDir, overrideConfig) {
|
|
74
|
-
clear();
|
|
75
|
-
console.log(chalk.yellow(figlet.textSync('JobQueue', { horizontalLayout: 'full' })));
|
|
76
76
|
let data = await loadData(overrideConfigDir, overrideConfig);
|
|
77
|
-
|
|
77
|
+
if (data.config.data.showBanner ?? true) {
|
|
78
|
+
if (data.config.data.showBanner === undefined) {
|
|
79
|
+
clear();
|
|
80
|
+
data.config.data.showBanner = false;
|
|
81
|
+
}
|
|
82
|
+
console.log(chalk.yellow(figlet.textSync('JobQueue', { horizontalLayout: 'full' })));
|
|
83
|
+
}
|
|
78
84
|
try {
|
|
79
85
|
while (true) {
|
|
80
|
-
|
|
86
|
+
console.log();
|
|
87
|
+
const actionChoices = actionNames.map((action) => {
|
|
88
|
+
if (actionsDependentOnJobs.includes(action) &&
|
|
89
|
+
data.jobqueue.data.queue.length === 0)
|
|
90
|
+
return {
|
|
91
|
+
name: action,
|
|
92
|
+
value: action,
|
|
93
|
+
disabled: '(Empty job queue)',
|
|
94
|
+
};
|
|
95
|
+
else if (actionsDependentOnProjects.includes(action) &&
|
|
96
|
+
data.projectpool.data.pool.length === 0)
|
|
97
|
+
return {
|
|
98
|
+
name: action,
|
|
99
|
+
value: action,
|
|
100
|
+
disabled: '(Empty project pool)',
|
|
101
|
+
};
|
|
102
|
+
else
|
|
103
|
+
return { name: action, value: action };
|
|
104
|
+
});
|
|
105
|
+
const action = await search({
|
|
81
106
|
message: 'Select action',
|
|
82
|
-
|
|
83
|
-
...
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
value: action,
|
|
96
|
-
disabled: '(Empty project pool)',
|
|
97
|
-
};
|
|
98
|
-
else
|
|
99
|
-
return { name: action, value: action };
|
|
100
|
-
}),
|
|
101
|
-
new Separator(),
|
|
102
|
-
{ name: 'editData', value: 'editData' },
|
|
103
|
-
{ name: 'sync', value: 'sync' },
|
|
104
|
-
],
|
|
107
|
+
source: (partialAction) => {
|
|
108
|
+
const partialSet = new Set([...(partialAction?.toLowerCase() ?? [])]);
|
|
109
|
+
const filteredActions = partialSet.size === 0
|
|
110
|
+
? actionChoices
|
|
111
|
+
: actionChoices.filter((action) => partialSet.isSubsetOf(new Set([...action.name.toLowerCase()])));
|
|
112
|
+
const otherActions = [
|
|
113
|
+
{ name: 'editData', value: 'editData' },
|
|
114
|
+
{ name: 'syncData', value: 'syncData' },
|
|
115
|
+
];
|
|
116
|
+
return filteredActions.length > 0
|
|
117
|
+
? [...filteredActions, new Separator(), ...otherActions]
|
|
118
|
+
: otherActions.filter((action) => partialSet.isSubsetOf(new Set([...action.name.toLowerCase()])));
|
|
119
|
+
},
|
|
105
120
|
pageSize: actionNames.length + 3,
|
|
106
121
|
});
|
|
107
122
|
if (action === 'editData') {
|
|
108
123
|
await editData(data, data.configPath);
|
|
109
124
|
data = await loadData(overrideConfigDir, overrideConfig, data);
|
|
110
125
|
}
|
|
111
|
-
else if (action === '
|
|
126
|
+
else if (action === 'syncData') {
|
|
112
127
|
await syncData(data);
|
|
113
128
|
}
|
|
114
129
|
else
|
|
115
130
|
await actions[action](data);
|
|
116
|
-
console.log();
|
|
117
131
|
}
|
|
118
132
|
}
|
|
119
133
|
catch (error) {
|
package/dist/utils/promptUser.js
CHANGED
|
@@ -18,28 +18,7 @@ export const haveUserUpdateContents = async (contents, options, check) => {
|
|
|
18
18
|
});
|
|
19
19
|
const originalContents = await fs.readFile(file.path, { encoding: 'utf8' });
|
|
20
20
|
while (true) {
|
|
21
|
-
|
|
22
|
-
const signal = controller.signal;
|
|
23
|
-
const editorPromise = editInteractively(file.path, contents, options?.editor, options?.tooltips);
|
|
24
|
-
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
25
|
-
const abortPromise = confirm({ message: 'Type `y` to abort...' }, { signal })
|
|
26
|
-
.finally(() => clearNLines(1))
|
|
27
|
-
.then(async (shouldAbort) => {
|
|
28
|
-
if (shouldAbort) {
|
|
29
|
-
if (file.cleanup)
|
|
30
|
-
await file.cleanup();
|
|
31
|
-
else
|
|
32
|
-
await fs.writeFile(file.path, originalContents, {
|
|
33
|
-
encoding: 'utf8',
|
|
34
|
-
});
|
|
35
|
-
throw new AbortError('User aborted action');
|
|
36
|
-
}
|
|
37
|
-
return undefined;
|
|
38
|
-
});
|
|
39
|
-
contents = await Promise.race([editorPromise, abortPromise]);
|
|
40
|
-
controller.abort();
|
|
41
|
-
if (typeof contents !== 'string')
|
|
42
|
-
return undefined;
|
|
21
|
+
contents = await editInteractively(file.path, contents, options?.editor, options?.tooltips);
|
|
43
22
|
if (check) {
|
|
44
23
|
const error = check(contents);
|
|
45
24
|
if (error === 'pass') {
|
|
@@ -47,7 +26,15 @@ export const haveUserUpdateContents = async (contents, options, check) => {
|
|
|
47
26
|
}
|
|
48
27
|
else if (typeof error === 'object') {
|
|
49
28
|
console.log(chalk.red(`${options?.errorHead}:`), error.errMessage);
|
|
50
|
-
|
|
29
|
+
if (!(await inquirerConfirm('Try again?'))) {
|
|
30
|
+
if (file.cleanup)
|
|
31
|
+
await file.cleanup();
|
|
32
|
+
else
|
|
33
|
+
await fs.writeFile(file.path, originalContents, {
|
|
34
|
+
encoding: 'utf8',
|
|
35
|
+
});
|
|
36
|
+
throw new AbortError('User aborted action');
|
|
37
|
+
}
|
|
51
38
|
}
|
|
52
39
|
}
|
|
53
40
|
}
|
package/index.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { Command } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
import pkg from './package.json' with { type: 'json' };
|
|
5
7
|
import main from './dist/index.js';
|
|
6
8
|
|
|
7
9
|
const program = new Command();
|
|
8
10
|
|
|
9
11
|
program
|
|
10
12
|
.name('job-queue')
|
|
11
|
-
.description(
|
|
12
|
-
|
|
13
|
-
)
|
|
14
|
-
.version('0.0.0')
|
|
13
|
+
.description(pkg.description)
|
|
14
|
+
.version(pkg.version)
|
|
15
15
|
.option(
|
|
16
16
|
'-c, --config <path>',
|
|
17
17
|
'path to directory containing config.json (uses new system app dir by default)',
|
|
@@ -36,6 +36,7 @@ program
|
|
|
36
36
|
});
|
|
37
37
|
|
|
38
38
|
await main(configDir, { ...options })
|
|
39
|
+
.finally(() => console.log()) // Seperation line
|
|
39
40
|
.then(() => {
|
|
40
41
|
console.log(chalk.cyanBright('🖖 Live long and prosper...'));
|
|
41
42
|
process.exit();
|