forked from career-ops-hq/career-ops
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapplication-artifacts.mjs
More file actions
162 lines (148 loc) · 5.91 KB
/
Copy pathapplication-artifacts.mjs
File metadata and controls
162 lines (148 loc) · 5.91 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#!/usr/bin/env node
/**
* Resolve and initialize one application-scoped artifact directory.
*
* Generated CVs are intentionally kept under output/ because they are user
* artifacts. The directory key is stable for a report/company/role tuple so
* the JD, source CV, tailored CV, PDF, and reuse decision stay together.
*/
import { mkdirSync, writeFileSync } from 'fs';
import { join, resolve } from 'path';
import { parseArgs } from 'util';
import { validateFlags } from './lib/cli-flags.mjs';
import { isMainModule } from './lib/is-main-module.mjs';
const DEFAULT_OUTPUT_ROOT = resolve('output');
const DECISIONS = new Set(['reuse', 'reuse-with-edits', 'regenerate']);
/** Convert a user-facing label into a safe, readable path segment. */
export function slugifySegment(value, fallback = 'application') {
const slug = String(value ?? '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-+|-+$/g, '');
return slug || fallback;
}
/** Return all stable paths belonging to one application artifact bundle. */
export function applicationArtifactPaths({ reportNum, company, role, version = 1, root = DEFAULT_OUTPUT_ROOT }) {
if (!/^\d+$/.test(String(reportNum ?? ''))) {
throw new Error('reportNum must be a numeric report number');
}
if (!/^\d+$/.test(String(version ?? '')) || Number(version) < 1) {
throw new Error('version must be a positive integer');
}
const key = `${String(reportNum).padStart(3, '0')}-${slugifySegment(company)}-${slugifySegment(role, 'role')}`;
const applicationRoot = join(resolve(root), key);
const tailoredRoot = join(applicationRoot, 'cv', 'tailored', `v${String(version).padStart(3, '0')}`);
return {
key,
root: applicationRoot,
jd: {
current: join(applicationRoot, 'jd', 'current.md'),
previous: join(applicationRoot, 'jd', 'previous.md'),
},
cv: {
source: {
html: join(applicationRoot, 'cv', 'source', 'original.html'),
pdf: join(applicationRoot, 'cv', 'source', 'original.pdf'),
},
tailored: {
root: tailoredRoot,
html: join(tailoredRoot, 'cv.html'),
pdf: join(tailoredRoot, 'cv.pdf'),
changes: join(tailoredRoot, 'changes.md'),
},
},
decision: {
reuse: join(applicationRoot, 'decision', 'reuse.json'),
},
};
}
/** Create the JD and CV subdirectories for an application bundle. */
export function ensureApplicationArtifactDirs(paths) {
for (const directory of [
join(paths.root, 'jd'),
join(paths.root, 'cv', 'source'),
paths.cv.tailored.root,
join(paths.root, 'decision'),
]) mkdirSync(directory, { recursive: true });
return paths;
}
/** Write an auditable CV reuse decision beside the application artifacts. */
export function writeReuseDecision(paths, {
decision,
score = null,
sourceCv = null,
currentJd = null,
previousSource = null,
changedSections = [],
userOverride = false,
}) {
if (!DECISIONS.has(decision)) throw new Error(`decision must be one of: ${[...DECISIONS].join(', ')}`);
if (!Array.isArray(changedSections)) throw new Error('changedSections must be an array');
ensureApplicationArtifactDirs(paths);
const record = {
schema_version: 1,
decision,
score,
source_cv: sourceCv,
current_jd: currentJd,
previous_source: previousSource,
changed_sections: changedSections,
user_override: Boolean(userOverride),
recorded_at: new Date().toISOString(),
};
writeFileSync(paths.decision.reuse, `${JSON.stringify(record, null, 2)}\n`);
return record;
}
// --help was swallowed by parseArgs's strict mode and reported as an unknown
// option (#2774), so the built-in help flag looked like a typo. Handled via
// lib/cli-flags.mjs's validateFlags() (#2775), which rejects unrecognized
// flags before --help so `--help --bogus` still errors.
//
// requireOperand is opted in because this script has no value validation of
// its own to say anything better: without it `--report --help` consumed the
// next token, printed usage and exited 0 (the #2961 class), so a malformed
// flag went unreported.
const KNOWN_FLAGS = ['--report', '--company', '--role', '--version', '--root', '--init', '--help', '-h'];
// Every flag except --init takes its value as the next argv token.
const VALUE_FLAGS = ['--report', '--company', '--role', '--version', '--root'];
const USAGE = `Usage:
node application-artifacts.mjs --report N --company NAME --role ROLE [--version N] [--root DIR] [--init]
Prints the resolved artifact paths for one application as JSON. The directory
key is stable for a report/company/role tuple, so the JD, source CV, tailored
CV, PDF and reuse decision stay together.
--report N report number the bundle belongs to (required)
--company NAME company name (required)
--role ROLE role title (required)
--version N tailored-CV version (default: 1)
--root DIR output root (default: output)
--init create the directories as well as printing the paths
--help, -h show this message`;
async function main() {
validateFlags(process.argv.slice(2), KNOWN_FLAGS, USAGE, { valueFlags: VALUE_FLAGS, requireOperand: true });
const { values } = parseArgs({
options: {
report: { type: 'string' },
company: { type: 'string' },
role: { type: 'string' },
version: { type: 'string', default: '1' },
root: { type: 'string' },
init: { type: 'boolean' },
},
strict: true,
});
if (!values.report || !values.company || !values.role) {
console.error(USAGE);
process.exitCode = 1;
return;
}
const paths = applicationArtifactPaths({ reportNum: values.report, company: values.company, role: values.role, version: values.version, root: values.root });
if (values.init) ensureApplicationArtifactDirs(paths);
console.log(JSON.stringify(paths, null, 2));
}
if (isMainModule(import.meta.url)) {
main().catch((error) => {
console.error(`application-artifacts: ${error.message}`);
process.exitCode = 1;
});
}