job-pro 1.0.15 → 1.0.16
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/dist/apply.js +17 -0
- package/dist/index.js +81 -2
- package/package.json +1 -1
package/dist/apply.js
CHANGED
|
@@ -59,6 +59,23 @@ const TEMPLATE = {
|
|
|
59
59
|
// nationality: "China",
|
|
60
60
|
},
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* Read profile.json as-is, returning whatever is there.
|
|
64
|
+
* Skips the loadProfile() validation so callers (like `profile lint`)
|
|
65
|
+
* can inspect partial / broken profiles instead of getting a flat fail.
|
|
66
|
+
*/
|
|
67
|
+
export function loadProfileRaw() {
|
|
68
|
+
if (!existsSync(PROFILE_PATH)) {
|
|
69
|
+
return { ok: false, path: PROFILE_PATH, message: `profile not found at ${PROFILE_PATH}` };
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const raw = readFileSync(PROFILE_PATH, "utf8");
|
|
73
|
+
return { ok: true, path: PROFILE_PATH, profile: JSON.parse(raw) };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
return { ok: false, path: PROFILE_PATH, message: `could not parse ${PROFILE_PATH}: ${err instanceof Error ? err.message : err}` };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
62
79
|
export function loadProfile() {
|
|
63
80
|
if (!existsSync(PROFILE_PATH)) {
|
|
64
81
|
return {
|
package/dist/index.js
CHANGED
|
@@ -50,7 +50,7 @@ import * as geely from "./geely.js";
|
|
|
50
50
|
import * as webank from "./webank.js";
|
|
51
51
|
import * as horizonrobotics from "./horizonrobotics.js";
|
|
52
52
|
import * as cambricon from "./cambricon.js";
|
|
53
|
-
import { loadProfile, loadSession, profileTemplate, saveProfile, stageApplication, submitApplication, executeFeishu3Step, executeMokaApply, executeBeisenWecruit, executeBeisenITalent, executeCdpRealBrowser, buildFormTemplate, applyFormFile, promptUnansweredFields, formatStaged, } from "./apply.js";
|
|
53
|
+
import { loadProfile, loadProfileRaw, loadSession, profileTemplate, saveProfile, stageApplication, submitApplication, executeFeishu3Step, executeMokaApply, executeBeisenWecruit, executeBeisenITalent, executeCdpRealBrowser, buildFormTemplate, applyFormFile, promptUnansweredFields, formatStaged, } from "./apply.js";
|
|
54
54
|
import { createInterface } from "node:readline";
|
|
55
55
|
import { memoryList, memoryGet, memorySet, memoryEvent, memoryClear, } from "./memory.js";
|
|
56
56
|
import { writeFileSync, mkdirSync, existsSync, readdirSync, statSync } from "node:fs";
|
|
@@ -125,6 +125,8 @@ USAGE
|
|
|
125
125
|
write ~/.jobpro/profile.json
|
|
126
126
|
--interactive fills it via prompts.
|
|
127
127
|
job-pro profile show print the loaded profile
|
|
128
|
+
job-pro profile lint validate format of every field
|
|
129
|
+
(exits 1 on any FAIL — scriptable)
|
|
128
130
|
job-pro find <keyword> search ALL 50 companies in parallel
|
|
129
131
|
[--limit N] [--companies a,b,c]
|
|
130
132
|
[--timeout ms] [--apply-ready]
|
|
@@ -1238,7 +1240,84 @@ async function main() {
|
|
|
1238
1240
|
console.log(JSON.stringify(r.profile, null, 2));
|
|
1239
1241
|
return;
|
|
1240
1242
|
}
|
|
1241
|
-
|
|
1243
|
+
if (sub === "lint") {
|
|
1244
|
+
const compact = args.includes("--compact");
|
|
1245
|
+
const r = loadProfileRaw();
|
|
1246
|
+
if (!r.ok) {
|
|
1247
|
+
console.error(r.message);
|
|
1248
|
+
process.exit(1);
|
|
1249
|
+
}
|
|
1250
|
+
const p = r.profile;
|
|
1251
|
+
const findings = [];
|
|
1252
|
+
// first_name / last_name
|
|
1253
|
+
for (const k of ["first_name", "last_name"]) {
|
|
1254
|
+
const v = (p[k] ?? "").trim();
|
|
1255
|
+
if (!v)
|
|
1256
|
+
findings.push({ level: "FAIL", check: k, message: "missing" });
|
|
1257
|
+
else
|
|
1258
|
+
findings.push({ level: "PASS", check: k, message: v });
|
|
1259
|
+
}
|
|
1260
|
+
// email
|
|
1261
|
+
const email = (p.email ?? "").trim();
|
|
1262
|
+
if (!email)
|
|
1263
|
+
findings.push({ level: "FAIL", check: "email", message: "missing" });
|
|
1264
|
+
else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))
|
|
1265
|
+
findings.push({ level: "FAIL", check: "email", message: `"${email}" doesn't look like a valid address` });
|
|
1266
|
+
else
|
|
1267
|
+
findings.push({ level: "PASS", check: "email", message: email });
|
|
1268
|
+
// phone
|
|
1269
|
+
const phone = (p.phone ?? "").trim();
|
|
1270
|
+
if (!phone)
|
|
1271
|
+
findings.push({ level: "FAIL", check: "phone", message: "missing" });
|
|
1272
|
+
else {
|
|
1273
|
+
const digitCount = phone.replace(/\D/g, "").length;
|
|
1274
|
+
if (digitCount < 7)
|
|
1275
|
+
findings.push({ level: "FAIL", check: "phone", message: `"${phone}" has ${digitCount} digit(s); need 7+` });
|
|
1276
|
+
else if (!phone.startsWith("+"))
|
|
1277
|
+
findings.push({ level: "WARN", check: "phone", message: `"${phone}" missing country code (recommended for non-anon adapters; e.g. +86 / +1)` });
|
|
1278
|
+
else
|
|
1279
|
+
findings.push({ level: "PASS", check: "phone", message: phone });
|
|
1280
|
+
}
|
|
1281
|
+
// resume_path
|
|
1282
|
+
const rp = (p.resume_path ?? "").trim();
|
|
1283
|
+
if (!rp)
|
|
1284
|
+
findings.push({ level: "FAIL", check: "resume_path", message: "missing" });
|
|
1285
|
+
else if (!existsSync(rp))
|
|
1286
|
+
findings.push({ level: "FAIL", check: "resume_path", message: `file not found: ${rp}` });
|
|
1287
|
+
else {
|
|
1288
|
+
const lower = rp.toLowerCase();
|
|
1289
|
+
if (!/\.(pdf|docx?|md|txt|rtf)$/i.test(lower))
|
|
1290
|
+
findings.push({ level: "WARN", check: "resume_path", message: `unusual extension: ${rp} (most ATS expect .pdf or .docx)` });
|
|
1291
|
+
else
|
|
1292
|
+
findings.push({ level: "PASS", check: "resume_path", message: rp });
|
|
1293
|
+
}
|
|
1294
|
+
// custom
|
|
1295
|
+
const customCount = Object.keys(p.custom ?? {}).length;
|
|
1296
|
+
if (customCount > 0) {
|
|
1297
|
+
const emptyValues = Object.entries(p.custom ?? {})
|
|
1298
|
+
.filter(([, v]) => typeof v !== "string" || v.trim() === "")
|
|
1299
|
+
.map(([k]) => k);
|
|
1300
|
+
if (emptyValues.length > 0)
|
|
1301
|
+
findings.push({ level: "WARN", check: "custom", message: `${emptyValues.length} empty value(s): ${emptyValues.slice(0, 5).join(", ")}` });
|
|
1302
|
+
else
|
|
1303
|
+
findings.push({ level: "PASS", check: "custom", message: `${customCount} answer(s)` });
|
|
1304
|
+
}
|
|
1305
|
+
const fails = findings.filter((f) => f.level === "FAIL").length;
|
|
1306
|
+
const warns = findings.filter((f) => f.level === "WARN").length;
|
|
1307
|
+
if (compact) {
|
|
1308
|
+
console.log(JSON.stringify({ ok: fails === 0, fails, warns, findings }));
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
const ICON = { PASS: "✓", WARN: "!", FAIL: "✗" };
|
|
1312
|
+
for (const f of findings)
|
|
1313
|
+
console.log(` ${ICON[f.level]} ${f.check.padEnd(13)} ${f.message}`);
|
|
1314
|
+
console.log(`\n ${fails} fail / ${warns} warn / ${findings.length - fails - warns} pass`);
|
|
1315
|
+
}
|
|
1316
|
+
if (fails > 0)
|
|
1317
|
+
process.exit(1);
|
|
1318
|
+
return;
|
|
1319
|
+
}
|
|
1320
|
+
die(`usage: job-pro profile <init [--interactive] [--force] | show | lint>`);
|
|
1242
1321
|
}
|
|
1243
1322
|
const adapter = ADAPTERS[cmd];
|
|
1244
1323
|
if (adapter) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "job-pro",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "Query Chinese big-tech campus recruiting from your terminal. 50 companies, all 50 live. 46 via each company's own API; the 4 with no public canonical feed (Hikvision, CICC, Cainiao, WeBank) surfaced via Liepin as a clearly-labeled third-party fallback. No signup, no token, no server.",
|
|
5
5
|
"homepage": "https://job.ha7ch.com",
|
|
6
6
|
"repository": "https://github.com/HA7CH/job-pro",
|