@chriswiegman/hugo-tools 0.2.0 → 0.2.2
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 +18 -0
- package/README.md +12 -1
- package/package.json +6 -5
- package/src/add-tags.js +103 -100
- package/src/book.mjs +643 -542
- package/src/book.test.mjs +660 -566
- package/src/config-init.js +9 -9
- package/src/config.js +22 -20
- package/src/draft.js +44 -42
- package/src/draft.test.mjs +32 -27
- package/src/extract-tags.js +184 -173
- package/src/extract-tags.test.mjs +56 -43
- package/src/pick-image.js +111 -97
- package/src/pick-image.test.mjs +57 -46
- package/src/publish.js +363 -317
- package/src/publish.test.mjs +242 -201
- package/src/vscode.js +68 -65
package/src/config-init.js
CHANGED
|
@@ -12,18 +12,18 @@ const path = require('path');
|
|
|
12
12
|
const { requireHugoSite, DEFAULTS } = require('./config');
|
|
13
13
|
|
|
14
14
|
function main() {
|
|
15
|
-
|
|
15
|
+
requireHugoSite();
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
const dest = path.join(process.cwd(), '.hugo-tools.json');
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
if (fs.existsSync(dest)) {
|
|
20
|
+
process.stderr.write('Error: .hugo-tools.json already exists.\n');
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
fs.writeFileSync(dest, JSON.stringify(DEFAULTS, null, 2) + '\n', 'utf8');
|
|
25
|
+
console.log(`Created ${dest}`);
|
|
26
|
+
console.log('Edit the values to match your Hugo site layout.');
|
|
27
27
|
}
|
|
28
28
|
|
|
29
29
|
main();
|
package/src/config.js
CHANGED
|
@@ -4,38 +4,40 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
|
|
6
6
|
const DEFAULTS = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
7
|
+
timezone: 'America/Chicago',
|
|
8
|
+
postsDir: 'content/posts',
|
|
9
|
+
draftsDir: 'content/drafts',
|
|
10
|
+
notesDir: 'content/notes',
|
|
11
|
+
booksDir: 'content/books',
|
|
12
|
+
imagesDir: 'assets/images',
|
|
13
|
+
vscodeDir: '.vscode',
|
|
14
14
|
};
|
|
15
15
|
|
|
16
16
|
let override = {};
|
|
17
17
|
const configPath = path.join(process.cwd(), '.hugo-tools.json');
|
|
18
|
+
|
|
18
19
|
try {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
if (fs.existsSync(configPath)) {
|
|
21
|
+
override = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
22
|
+
}
|
|
22
23
|
} catch (e) {
|
|
23
|
-
|
|
24
|
+
process.stderr.write(`Warning: could not read .hugo-tools.json: ${e.message}\n`);
|
|
24
25
|
}
|
|
25
26
|
|
|
26
27
|
const HUGO_CONFIGS = [
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
'hugo.toml', 'hugo.yaml', 'hugo.yml', 'hugo.json',
|
|
29
|
+
'config.toml', 'config.yaml', 'config.yml', 'config.json',
|
|
30
|
+
'config',
|
|
30
31
|
];
|
|
31
32
|
|
|
32
33
|
function requireHugoSite() {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
const cwd = process.cwd();
|
|
35
|
+
const found = HUGO_CONFIGS.some((name) => fs.existsSync(path.join(cwd, name)));
|
|
36
|
+
|
|
37
|
+
if (!found) {
|
|
38
|
+
process.stderr.write('Error: no Hugo config file found. Run this command from the root of your Hugo site.\n');
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
39
41
|
}
|
|
40
42
|
|
|
41
43
|
module.exports = { ...DEFAULTS, ...override, requireHugoSite, DEFAULTS };
|
package/src/draft.js
CHANGED
|
@@ -17,61 +17,63 @@ const { execFileSync } = require('child_process');
|
|
|
17
17
|
const config = require('./config');
|
|
18
18
|
|
|
19
19
|
function timestampFilename() {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
20
|
+
const now = new Date();
|
|
21
|
+
const parts = new Intl.DateTimeFormat('en-CA', {
|
|
22
|
+
timeZone: config.timezone,
|
|
23
|
+
year: 'numeric',
|
|
24
|
+
month: '2-digit',
|
|
25
|
+
day: '2-digit',
|
|
26
|
+
hour: '2-digit',
|
|
27
|
+
minute: '2-digit',
|
|
28
|
+
second: '2-digit',
|
|
29
|
+
hour12: false,
|
|
30
|
+
}).formatToParts(now);
|
|
31
|
+
const get = (type) => parts.find((p) => p.type === type).value;
|
|
32
|
+
let hour = get('hour');
|
|
33
|
+
|
|
34
|
+
if (hour === '24') hour = '00';
|
|
35
|
+
|
|
36
|
+
return `${get('year')}${get('month')}${get('day')}-${hour}${get('minute')}${get('second')}`;
|
|
35
37
|
}
|
|
36
38
|
|
|
37
39
|
function buildArchetype() {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
40
|
+
return [
|
|
41
|
+
'---',
|
|
42
|
+
'title: ""',
|
|
43
|
+
'description: ""',
|
|
44
|
+
'draft: true',
|
|
45
|
+
'images:',
|
|
46
|
+
' -',
|
|
47
|
+
'categories:',
|
|
48
|
+
' -',
|
|
49
|
+
'tags:',
|
|
50
|
+
' -',
|
|
51
|
+
'---',
|
|
52
|
+
'',
|
|
53
|
+
].join('\n');
|
|
52
54
|
}
|
|
53
55
|
|
|
54
56
|
function main() {
|
|
55
|
-
|
|
57
|
+
config.requireHugoSite();
|
|
56
58
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
const stamp = timestampFilename();
|
|
60
|
+
const draftsDir = path.join(process.cwd(), config.draftsDir);
|
|
61
|
+
const destPath = path.join(draftsDir, `${stamp}.md`);
|
|
60
62
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
+
fs.mkdirSync(draftsDir, { recursive: true });
|
|
64
|
+
fs.writeFileSync(destPath, buildArchetype(), 'utf8');
|
|
63
65
|
|
|
64
|
-
|
|
66
|
+
console.log(destPath);
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
try {
|
|
69
|
+
execFileSync('code', [destPath], { stdio: 'ignore' });
|
|
70
|
+
} catch {
|
|
71
|
+
// VS Code not available — silently continue
|
|
72
|
+
}
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
if (require.main === module) {
|
|
74
|
-
|
|
76
|
+
main();
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
module.exports = { buildArchetype, timestampFilename };
|
package/src/draft.test.mjs
CHANGED
|
@@ -10,42 +10,45 @@ const { buildArchetype, timestampFilename } = require('./draft.js');
|
|
|
10
10
|
// ---------------------------------------------------------------------------
|
|
11
11
|
|
|
12
12
|
test('buildArchetype: starts with front matter delimiter', () => {
|
|
13
|
-
|
|
13
|
+
assert.ok(buildArchetype().startsWith('---\n'));
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
test('buildArchetype: contains blank title', () => {
|
|
17
|
-
|
|
17
|
+
assert.ok(buildArchetype().includes('title: ""'));
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
test('buildArchetype: contains blank description', () => {
|
|
21
|
-
|
|
21
|
+
assert.ok(buildArchetype().includes('description: ""'));
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
test('buildArchetype: sets draft true', () => {
|
|
25
|
-
|
|
25
|
+
assert.ok(buildArchetype().includes('draft: true'));
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
test('buildArchetype: contains images placeholder', () => {
|
|
29
|
-
|
|
30
|
-
|
|
29
|
+
const out = buildArchetype();
|
|
30
|
+
|
|
31
|
+
assert.ok(out.includes('images:\n -'));
|
|
31
32
|
});
|
|
32
33
|
|
|
33
34
|
test('buildArchetype: contains categories placeholder', () => {
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
const out = buildArchetype();
|
|
36
|
+
|
|
37
|
+
assert.ok(out.includes('categories:\n -'));
|
|
36
38
|
});
|
|
37
39
|
|
|
38
40
|
test('buildArchetype: contains tags placeholder', () => {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
+
const out = buildArchetype();
|
|
42
|
+
|
|
43
|
+
assert.ok(out.includes('tags:\n -'));
|
|
41
44
|
});
|
|
42
45
|
|
|
43
46
|
test('buildArchetype: does not contain a date key', () => {
|
|
44
|
-
|
|
47
|
+
assert.ok(!buildArchetype().includes('date:'));
|
|
45
48
|
});
|
|
46
49
|
|
|
47
50
|
test('buildArchetype: ends with closing delimiter and trailing newline', () => {
|
|
48
|
-
|
|
51
|
+
assert.ok(buildArchetype().endsWith('---\n'));
|
|
49
52
|
});
|
|
50
53
|
|
|
51
54
|
// ---------------------------------------------------------------------------
|
|
@@ -53,25 +56,27 @@ test('buildArchetype: ends with closing delimiter and trailing newline', () => {
|
|
|
53
56
|
// ---------------------------------------------------------------------------
|
|
54
57
|
|
|
55
58
|
test('timestampFilename: matches YYYYMMDD-HHMMSS format', () => {
|
|
56
|
-
|
|
59
|
+
assert.match(timestampFilename(), /^\d{8}-\d{6}$/);
|
|
57
60
|
});
|
|
58
61
|
|
|
59
62
|
test('timestampFilename: date portion is a plausible date', () => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
const stamp = timestampFilename();
|
|
64
|
+
const year = parseInt(stamp.slice(0, 4), 10);
|
|
65
|
+
const month = parseInt(stamp.slice(4, 6), 10);
|
|
66
|
+
const day = parseInt(stamp.slice(6, 8), 10);
|
|
67
|
+
|
|
68
|
+
assert.ok(year >= 2024);
|
|
69
|
+
assert.ok(month >= 1 && month <= 12);
|
|
70
|
+
assert.ok(day >= 1 && day <= 31);
|
|
67
71
|
});
|
|
68
72
|
|
|
69
73
|
test('timestampFilename: time portion is a plausible time', () => {
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
74
|
+
const stamp = timestampFilename();
|
|
75
|
+
const hour = parseInt(stamp.slice(9, 11), 10);
|
|
76
|
+
const minute = parseInt(stamp.slice(11, 13), 10);
|
|
77
|
+
const second = parseInt(stamp.slice(13, 15), 10);
|
|
78
|
+
|
|
79
|
+
assert.ok(hour >= 0 && hour <= 23);
|
|
80
|
+
assert.ok(minute >= 0 && minute <= 59);
|
|
81
|
+
assert.ok(second >= 0 && second <= 59);
|
|
77
82
|
});
|