@makefully/dutifully 1.21.2 → 1.22.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/CHANGELOG.md +4 -0
- package/README.md +36 -0
- package/cheer.js +4 -1
- package/package.json +1 -1
- package/src/helpers/filterJobs.js +118 -0
- package/src/index.js +9 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 1.22.0
|
|
6
|
+
|
|
7
|
+
- Select a subset of service listings via optional job `id` and top-level `only` (`cheer -service packfully -only heroes,ui`).
|
|
8
|
+
|
|
5
9
|
## 1.21.2
|
|
6
10
|
|
|
7
11
|
- Yap poll uses `/status` then download; while pending, tails `/logs` for live Cheerfully worker output.
|
package/README.md
CHANGED
|
@@ -42,6 +42,41 @@ cheerfully({
|
|
|
42
42
|
});
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
+
### Selecting listings (`id` + `only`)
|
|
46
|
+
|
|
47
|
+
Each service entry in `cheerfully.json` is an array of jobs (listings). Give any job an optional `id` string (unique within that service) to target it without running the whole array:
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
{
|
|
51
|
+
"packfully": [{
|
|
52
|
+
"id": "heroes",
|
|
53
|
+
"src": "./sprites/heroes/",
|
|
54
|
+
"output": "./atlases/",
|
|
55
|
+
"options": { "prefix": "heroes" }
|
|
56
|
+
}, {
|
|
57
|
+
"id": "ui",
|
|
58
|
+
"src": "./sprites/ui/",
|
|
59
|
+
"output": "./atlases/",
|
|
60
|
+
"options": { "prefix": "ui" }
|
|
61
|
+
}]
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Then run a subset from the CLI or from Node:
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
cheer -service packfully -only heroes,ui
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
cheerfully({
|
|
73
|
+
service: 'packfully',
|
|
74
|
+
only: ['heroes', 'ui'] // or only: 'heroes,ui'
|
|
75
|
+
});
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Omit `only` to run every listing for that service (unchanged). Jobs without an `id` still run in that case; they cannot be selected when `only` is set. Unknown or duplicate ids fail with a clear error. Per-job difference skipping still applies to whichever listings you select. The `id` field is for client selection; Cheerfully ignores it on the server.
|
|
79
|
+
|
|
45
80
|
### Say (spoken notifications)
|
|
46
81
|
|
|
47
82
|
`say` generates short spoken phrases through Cheerfully's ElevenLabs service, caches the audio under `.cache/tts/`, and plays it locally via `node-web-audio-api`. Use it from npm scripts instead of wiring ElevenLabs into the consuming project:
|
|
@@ -118,6 +153,7 @@ This image-to-atlas service packs PNG/JPG/WebP sprites into texture atlases and
|
|
|
118
153
|
```javascript
|
|
119
154
|
{
|
|
120
155
|
"packfully": [{
|
|
156
|
+
"id": "packed",
|
|
121
157
|
"format": "texturepacker",
|
|
122
158
|
"src": "./example/sprites/",
|
|
123
159
|
"output": "./example/atlases/",
|
package/cheer.js
CHANGED
package/package.json
CHANGED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Select a subset of cheerfully.json job listings by optional job `id`.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {unknown} only
|
|
7
|
+
* @returns {string[]|null} null means no filter; non-empty array means filter
|
|
8
|
+
* @throws {Error} when `only` is present but yields no ids (e.g. bare `-only`)
|
|
9
|
+
*/
|
|
10
|
+
const normalizeOnly = (only) => {
|
|
11
|
+
if (only === undefined || only === null || only === false) {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (only === true) {
|
|
16
|
+
throw new Error('`-only` requires one or more job ids (e.g. -only heroes,ui).');
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let raw;
|
|
20
|
+
|
|
21
|
+
if (typeof only === 'string') {
|
|
22
|
+
raw = only.split(',');
|
|
23
|
+
} else if (Array.isArray(only)) {
|
|
24
|
+
raw = only;
|
|
25
|
+
} else {
|
|
26
|
+
throw new Error(`Invalid \`only\` value: expected string or string[], got ${typeof only}.`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const
|
|
30
|
+
ids = raw
|
|
31
|
+
.map((id) => String(id ?? '').trim())
|
|
32
|
+
.filter((id) => id.length > 0);
|
|
33
|
+
|
|
34
|
+
if (!ids.length) {
|
|
35
|
+
throw new Error('`-only` requires one or more job ids (e.g. -only heroes,ui).');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return ids;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build a map from job id → original array index. Hard-errors on duplicate ids.
|
|
43
|
+
*
|
|
44
|
+
* @param {object[]} configs
|
|
45
|
+
* @returns {Map<string, number>}
|
|
46
|
+
*/
|
|
47
|
+
const indexJobsById = (configs) => {
|
|
48
|
+
const
|
|
49
|
+
byId = new Map();
|
|
50
|
+
|
|
51
|
+
for (let i = 0; i < configs.length; i++) {
|
|
52
|
+
const
|
|
53
|
+
job = configs[i],
|
|
54
|
+
jobId = job?.id;
|
|
55
|
+
|
|
56
|
+
if (jobId === undefined || jobId === null || jobId === '') {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const
|
|
61
|
+
key = String(jobId);
|
|
62
|
+
|
|
63
|
+
if (byId.has(key)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`Duplicate job id "${key}" at indices ${byId.get(key)} and ${i}. Job ids must be unique within a service.`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
byId.set(key, i);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return byId;
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @param {object[]} configs
|
|
77
|
+
* @param {string[]|null} onlyIds from normalizeOnly
|
|
78
|
+
* @returns {{jobs: object[], indices: number[]}}
|
|
79
|
+
*/
|
|
80
|
+
const filterJobsById = (configs, onlyIds) => {
|
|
81
|
+
if (!onlyIds) {
|
|
82
|
+
return {
|
|
83
|
+
jobs: configs,
|
|
84
|
+
indices: configs.map((_, i) => i)
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const
|
|
89
|
+
byId = indexJobsById(configs),
|
|
90
|
+
known = [...byId.keys()].sort(),
|
|
91
|
+
jobs = [],
|
|
92
|
+
indices = [];
|
|
93
|
+
|
|
94
|
+
for (const wanted of onlyIds) {
|
|
95
|
+
if (!byId.has(wanted)) {
|
|
96
|
+
const
|
|
97
|
+
knownList = known.length ? known.join(', ') : '(none)';
|
|
98
|
+
|
|
99
|
+
throw new Error(
|
|
100
|
+
`Unknown job id "${wanted}". Known ids: ${knownList}.`
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const
|
|
105
|
+
index = byId.get(wanted);
|
|
106
|
+
|
|
107
|
+
jobs.push(configs[index]);
|
|
108
|
+
indices.push(index);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return {jobs, indices};
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
normalizeOnly,
|
|
116
|
+
filterJobsById,
|
|
117
|
+
indexJobsById
|
|
118
|
+
};
|
package/src/index.js
CHANGED
|
@@ -17,15 +17,19 @@ const
|
|
|
17
17
|
translate: require('./classes/Translate')
|
|
18
18
|
},
|
|
19
19
|
getJSON = require('./helpers/getJSON'),
|
|
20
|
+
{normalizeOnly, filterJobsById} = require('./helpers/filterJobs'),
|
|
20
21
|
send = async function (contents) {
|
|
21
22
|
const
|
|
22
23
|
{id, service} = contents,
|
|
23
|
-
configs = Array.isArray(contents[service]) ? contents[service] : [contents[service]]
|
|
24
|
+
configs = Array.isArray(contents[service]) ? contents[service] : [contents[service]],
|
|
25
|
+
onlyIds = normalizeOnly(contents.only),
|
|
26
|
+
{jobs, indices} = filterJobsById(configs, onlyIds),
|
|
27
|
+
{length} = configs;
|
|
24
28
|
|
|
25
|
-
for (let
|
|
29
|
+
for (let i = 0; i < jobs.length; i++) {
|
|
26
30
|
const
|
|
27
|
-
config =
|
|
28
|
-
|
|
31
|
+
config = jobs[i],
|
|
32
|
+
index = indices[i];
|
|
29
33
|
|
|
30
34
|
if (!config) {
|
|
31
35
|
console.warn(`Empty configuration for "${service}" service.`);
|
|
@@ -58,7 +62,7 @@ const cheer = async (cmdArgs) => {
|
|
|
58
62
|
config = await getJSON('./cheerfully.json') ?? {},
|
|
59
63
|
env = await getJSON('./env-cheerfully.json') ?? {};
|
|
60
64
|
|
|
61
|
-
send({
|
|
65
|
+
await send({
|
|
62
66
|
id: `${package.name}-${package.version}`,
|
|
63
67
|
package,
|
|
64
68
|
...config,
|