@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 CHANGED
@@ -22,7 +22,32 @@ job-queue --help
22
22
  jobq --help
23
23
  ```
24
24
 
25
- ## Syncing with gists
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".
@@ -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().meta({ description: "Whether to prompt before pushing or pulling a gist from GitHub." }),
38
- confirmOffline: z.boolean().optional().meta({ description: "Whether to confirm before using offline (if there are gists in config)." })
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) => {
@@ -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 { select } from '@inquirer/prompts';
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]'), 'initial pull of gist for', key);
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 ['jobqueue', 'projectpool']) {
64
+ for (const key of dataNames) {
64
65
  await data[key].sync();
65
- if (data[key].isLinked &&
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]'), 'sync push of gist for', key);
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
- console.log();
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
- const action = await select({
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
- choices: [
83
- ...actionNames.map((action) => {
84
- if (actionsDependentOnJobs.includes(action) &&
85
- data.jobqueue.data.queue.length === 0)
86
- return {
87
- name: action,
88
- value: action,
89
- disabled: '(Empty job queue)',
90
- };
91
- else if (actionsDependentOnProjects.includes(action) &&
92
- data.projectpool.data.pool.length === 0)
93
- return {
94
- name: action,
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 === 'sync') {
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) {
@@ -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
- const controller = new AbortController();
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
- continue;
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
- 'A CLI app to keep track of jobs/tasks built around a couple of JSON files.',
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uss-stargazer/job-queue",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "description": "A CLI app to keep track of jobs/tasks built around a couple of JSON files.",
5
5
  "author": "uss-stargazer",
6
6
  "license": "Apache-2.0",