@anja.codes/vitakit 0.1.0 → 0.2.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/README.md +51 -131
- package/dist/check.mjs +12 -5
- package/dist/chunks/authoring.mjs +367 -0
- package/dist/chunks/build.mjs +176 -0
- package/dist/chunks/documents.mjs +143 -0
- package/dist/chunks/draft.mjs +222 -0
- package/dist/chunks/eject.mjs +59 -0
- package/dist/chunks/emit.mjs +200 -0
- package/dist/chunks/eta.mjs +16 -0
- package/dist/chunks/fields.mjs +79 -0
- package/dist/chunks/inventory.mjs +199 -0
- package/dist/chunks/plan.mjs +144 -0
- package/dist/chunks/prompt.mjs +33 -0
- package/dist/chunks/schema-source.mjs +12 -0
- package/dist/chunks/schemas.mjs +109 -0
- package/dist/chunks/sections.mjs +109 -0
- package/dist/chunks/snippets.mjs +199 -0
- package/dist/chunks/validate.mjs +124 -0
- package/dist/chunks/vue.mjs +116 -0
- package/dist/cli.d.mts +33 -40
- package/dist/cli.mjs +7 -224
- package/dist/index.d.mts +372 -42
- package/dist/index.mjs +10 -4
- package/dist/nuxt.mjs +35 -5
- package/dist/runtime/components/VitaSection.vue +94 -38
- package/dist/schemas/education.ts +4 -4
- package/dist/schemas/employment.ts +3 -3
- package/dist/schemas/publications.ts +2 -2
- package/dist/schemas/supervision.ts +3 -3
- package/dist/schemas/talks.ts +1 -1
- package/dist/schemas/teaching.ts +3 -3
- package/dist/shared/index.d.mts +60 -9
- package/dist/shared/index.mjs +3 -57
- package/dist/shared/vitakit.BZZIsWlY.mjs +179 -0
- package/dist/shared/vitakit.BerUaxtF.mjs +121 -0
- package/dist/shared/{vitakit.D8WRg_Xx.d.mts → vitakit.BfpxDtz1.d.mts} +9 -8
- package/dist/shared/{vitakit.CypCF-4Q.mjs → vitakit.By5VWQ1n.mjs} +12 -78
- package/dist/shared/vitakit.CfB3hMHl.mjs +60 -0
- package/dist/shared/vitakit.CoWX2Z4R.mjs +1554 -0
- package/dist/shared/{vitakit.BlU-p01e.mjs → vitakit.CtChKO0I.mjs} +1 -1
- package/dist/shared/vitakit.D-DdJaDZ.mjs +20 -0
- package/dist/shared/vitakit.D10HyDhF.mjs +19 -0
- package/dist/shared/vitakit.DQ6PmGoL.mjs +34 -0
- package/dist/shared/vitakit.DbSeENVS.mjs +58 -0
- package/dist/shared/vitakit.DcQOdM4I.mjs +57 -0
- package/dist/shared/vitakit.Do5HC_MD.mjs +142 -0
- package/dist/shared/vitakit.TSMTC5Jl.mjs +6 -0
- package/dist/tex/templates/_generic.eta +6 -4
- package/dist/tex/templates/education.eta +10 -5
- package/dist/tex/templates/employment.eta +12 -5
- package/dist/tex/templates/publications.eta +4 -2
- package/dist/tex/templates/supervision.eta +10 -2
- package/dist/tex/templates/talks.eta +5 -1
- package/dist/tex/templates/teaching.eta +6 -4
- package/dist/tex/vitakit-cv.cls +21 -6
- package/dist/tex.d.mts +53 -5
- package/dist/tex.mjs +20 -113
- package/package.json +10 -7
- package/src/bin/vitakit.mjs +3 -153
- package/dist/shared/vitakit.BRhjqpAt.mjs +0 -250
- package/dist/shared/vitakit.BxcnlFGP.mjs +0 -325
- package/src/bin/vitakit-build-cv.mjs +0 -50
- package/src/bin/vitakit-check.mjs +0 -57
|
@@ -1,325 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
import { join, dirname } from 'node:path';
|
|
3
|
-
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
4
|
-
import { z } from 'zod';
|
|
5
|
-
|
|
6
|
-
const PATH_KEY = "__vitakitPath";
|
|
7
|
-
const isSitePath = (value) => value.startsWith("/") && !value.startsWith("//");
|
|
8
|
-
const sitePathMarker = (path) => ({ [PATH_KEY]: path });
|
|
9
|
-
function isSitePathMarker(value) {
|
|
10
|
-
return typeof value === "object" && value !== null && PATH_KEY in value;
|
|
11
|
-
}
|
|
12
|
-
function resolveSitePaths(value, resolve) {
|
|
13
|
-
if (isSitePathMarker(value)) return resolve(value[PATH_KEY]);
|
|
14
|
-
if (Array.isArray(value)) return value.map((item) => resolveSitePaths(item, resolve));
|
|
15
|
-
if (typeof value === "object" && value !== null) {
|
|
16
|
-
return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, resolveSitePaths(item, resolve)]));
|
|
17
|
-
}
|
|
18
|
-
return value;
|
|
19
|
-
}
|
|
20
|
-
function absoluteSitePath(path, siteUrl) {
|
|
21
|
-
return siteUrl ? `${siteUrl.replace(/\/?$/, "/")}${path.slice(1)}` : path;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function sourcePath(asset) {
|
|
25
|
-
return asset === "styles" ? asset : join("src", asset);
|
|
26
|
-
}
|
|
27
|
-
let root;
|
|
28
|
-
function packageRoot() {
|
|
29
|
-
if (root) return root;
|
|
30
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
31
|
-
for (let depth = 0; depth < 6; depth += 1) {
|
|
32
|
-
if (existsSync(join(dir, "package.json"))) return root = dir;
|
|
33
|
-
const parent = dirname(dir);
|
|
34
|
-
if (parent === dir) break;
|
|
35
|
-
dir = parent;
|
|
36
|
-
}
|
|
37
|
-
throw new Error("vitakit: cannot locate the package root; the installation is incomplete");
|
|
38
|
-
}
|
|
39
|
-
const built = /[/\\]dist[/\\]/.test(import.meta.url);
|
|
40
|
-
function resolveAsset(asset) {
|
|
41
|
-
const base = packageRoot();
|
|
42
|
-
const order = built ? ["dist", void 0] : [void 0, "dist"];
|
|
43
|
-
const candidates = order.map((prefix) => prefix ? join(base, prefix, asset) : join(base, sourcePath(asset)));
|
|
44
|
-
for (const candidate of candidates) {
|
|
45
|
-
if (existsSync(candidate)) return candidate;
|
|
46
|
-
}
|
|
47
|
-
throw new Error(
|
|
48
|
-
`vitakit: missing bundled asset "${asset}"; looked in
|
|
49
|
-
` + candidates.map((candidate) => ` ${candidate}`).join("\n") + "\nRun `pnpm build` in the package, or reinstall it."
|
|
50
|
-
);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
class ContentError extends Error {
|
|
54
|
-
constructor(where, message) {
|
|
55
|
-
super(`${where}: ${message}`);
|
|
56
|
-
this.where = where;
|
|
57
|
-
this.name = "ContentError";
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
function fail(where, message) {
|
|
61
|
-
throw new ContentError(where, message);
|
|
62
|
-
}
|
|
63
|
-
function messageOf(error) {
|
|
64
|
-
if (error instanceof ContentError) return error.message.slice(error.where.length + 2);
|
|
65
|
-
return error instanceof Error ? error.message : String(error);
|
|
66
|
-
}
|
|
67
|
-
function whereOf(error, fallback) {
|
|
68
|
-
return error instanceof ContentError ? error.where : fallback;
|
|
69
|
-
}
|
|
70
|
-
async function importProjectModule(path, mtimeMs, where) {
|
|
71
|
-
try {
|
|
72
|
-
return await import(`${pathToFileURL(path).href}?mtime=${mtimeMs}`);
|
|
73
|
-
} catch (error) {
|
|
74
|
-
fail(where, importFailure(error));
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
function importFailure(error, node = process.versions.node) {
|
|
78
|
-
if (error?.code === "ERR_UNKNOWN_FILE_EXTENSION") {
|
|
79
|
-
return `cannot load TypeScript on Node ${node}; vitakit needs Node 22.18 or newer`;
|
|
80
|
-
}
|
|
81
|
-
return messageOf(error);
|
|
82
|
-
}
|
|
83
|
-
function formatZodIssues(error) {
|
|
84
|
-
return error.issues.map((issue) => ` - ${issue.path.join(".") || "(root)"}: ${issue.message}`).join("\n");
|
|
85
|
-
}
|
|
86
|
-
class Issues {
|
|
87
|
-
constructor(strict) {
|
|
88
|
-
this.strict = strict;
|
|
89
|
-
}
|
|
90
|
-
list = [];
|
|
91
|
-
/** Record a problem, or throw it when strict. */
|
|
92
|
-
record(where, message) {
|
|
93
|
-
if (this.strict) fail(where, message);
|
|
94
|
-
this.list.push({ where, message });
|
|
95
|
-
}
|
|
96
|
-
/** Run `run`; when lenient, record any failure and use `fallback` instead. */
|
|
97
|
-
async attempt(where, run, fallback) {
|
|
98
|
-
try {
|
|
99
|
-
return await run();
|
|
100
|
-
} catch (error) {
|
|
101
|
-
if (this.strict) throw error;
|
|
102
|
-
this.list.push({ where: whereOf(error, where), message: messageOf(error) });
|
|
103
|
-
return fallback;
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const MARKER_KEY = "__vitakitRich";
|
|
109
|
-
function isRichMarker(value) {
|
|
110
|
-
return typeof value === "object" && value !== null && MARKER_KEY in value;
|
|
111
|
-
}
|
|
112
|
-
function richMarker(value) {
|
|
113
|
-
return { [MARKER_KEY]: value };
|
|
114
|
-
}
|
|
115
|
-
function renderRichFields(value, renderer, path, where) {
|
|
116
|
-
if (isRichMarker(value)) {
|
|
117
|
-
try {
|
|
118
|
-
return { text: value[MARKER_KEY], html: renderer.inline(value[MARKER_KEY]) };
|
|
119
|
-
} catch (error) {
|
|
120
|
-
fail(where, `${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
if (Array.isArray(value)) {
|
|
124
|
-
return value.map((item, index) => renderRichFields(item, renderer, `${path}[${index}]`, where));
|
|
125
|
-
}
|
|
126
|
-
if (typeof value === "object" && value !== null) {
|
|
127
|
-
return Object.fromEntries(Object.entries(value).map(
|
|
128
|
-
([key, item]) => [key, renderRichFields(item, renderer, path ? `${path}.${key}` : key, where)]
|
|
129
|
-
));
|
|
130
|
-
}
|
|
131
|
-
return value;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const text = z.string().trim().min(1);
|
|
135
|
-
const richText = text.transform((value) => richMarker(value));
|
|
136
|
-
const absoluteUrl = z.string().trim().refine(
|
|
137
|
-
// One check, not `.url().refine(...)`: zod runs a refinement even after
|
|
138
|
-
// `.url()` failed, and `new URL()` then throws instead of reporting.
|
|
139
|
-
(value) => URL.canParse(value) && ["http:", "https:", "mailto:"].includes(new URL(value).protocol),
|
|
140
|
-
"expected an http(s) or mailto URL"
|
|
141
|
-
);
|
|
142
|
-
const url = z.string().trim().transform((value, context) => {
|
|
143
|
-
if (isSitePath(value) && !/\s/.test(value)) return sitePathMarker(value);
|
|
144
|
-
const parsed = absoluteUrl.safeParse(value);
|
|
145
|
-
if (parsed.success) return parsed.data;
|
|
146
|
-
context.addIssue({ code: "custom", message: "expected an http(s) or mailto URL, or a path on the site such as /slides/talk.pdf" });
|
|
147
|
-
return z.NEVER;
|
|
148
|
-
});
|
|
149
|
-
const DATE = /^(\d{4})(?:-(\d{2}))?(?:-(\d{2}))?$/;
|
|
150
|
-
function parseDatePoint(value, context) {
|
|
151
|
-
const match = DATE.exec(value);
|
|
152
|
-
if (!match) {
|
|
153
|
-
context.addIssue({ code: "custom", message: `expected a date like 2024, 2024-03 or 2024-03-15, got "${value}"` });
|
|
154
|
-
return void 0;
|
|
155
|
-
}
|
|
156
|
-
const [, y, m, d] = match;
|
|
157
|
-
const year = Number(y);
|
|
158
|
-
const month = m === void 0 ? void 0 : Number(m);
|
|
159
|
-
const day = d === void 0 ? void 0 : Number(d);
|
|
160
|
-
if (month !== void 0 && (month < 1 || month > 12)) {
|
|
161
|
-
context.addIssue({ code: "custom", message: `month out of range in "${value}"` });
|
|
162
|
-
return void 0;
|
|
163
|
-
}
|
|
164
|
-
if (day !== void 0) {
|
|
165
|
-
const date = new Date(Date.UTC(year, month - 1, day));
|
|
166
|
-
if (date.getUTCMonth() !== month - 1 || date.getUTCDate() !== day) {
|
|
167
|
-
context.addIssue({ code: "custom", message: `"${value}" is not a real date` });
|
|
168
|
-
return void 0;
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
172
|
-
raw: value,
|
|
173
|
-
year,
|
|
174
|
-
month,
|
|
175
|
-
day,
|
|
176
|
-
// Pad missing parts so that 2024 < 2024-03 < 2024-03-15 sorts correctly.
|
|
177
|
-
sortKey: `${String(year).padStart(4, "0")}-${String(month ?? 1).padStart(2, "0")}-${String(day ?? 1).padStart(2, "0")}`
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
const datePoint = z.union([z.string(), z.number().int()]).transform((value) => String(value));
|
|
181
|
-
const dateRange = z.union([
|
|
182
|
-
datePoint,
|
|
183
|
-
z.strictObject({
|
|
184
|
-
from: datePoint,
|
|
185
|
-
to: z.union([datePoint, z.literal("present")]).optional()
|
|
186
|
-
})
|
|
187
|
-
]).transform((value, context) => {
|
|
188
|
-
const source = typeof value === "string" ? { from: value, to: void 0 } : value;
|
|
189
|
-
const from = parseDatePoint(source.from, context);
|
|
190
|
-
const ongoing = source.to === "present";
|
|
191
|
-
const to = source.to === void 0 || ongoing ? void 0 : parseDatePoint(source.to, context);
|
|
192
|
-
if (!from || source.to !== void 0 && !ongoing && !to) return z.NEVER;
|
|
193
|
-
if (to && to.sortKey < from.sortKey) {
|
|
194
|
-
context.addIssue({ code: "custom", message: `range ends (${to.raw}) before it starts (${from.raw})` });
|
|
195
|
-
return z.NEVER;
|
|
196
|
-
}
|
|
197
|
-
return { from, to, ongoing, point: typeof value === "string" };
|
|
198
|
-
});
|
|
199
|
-
const institution = z.union([
|
|
200
|
-
text.transform((name) => ({ name })),
|
|
201
|
-
z.strictObject({ name: text, url: url.optional() })
|
|
202
|
-
]);
|
|
203
|
-
const link = z.union([
|
|
204
|
-
url.transform((value) => ({
|
|
205
|
-
label: typeof value === "string" ? value.replace(/^https?:\/\//, "").replace(/\/$/, "") : value[PATH_KEY],
|
|
206
|
-
url: value
|
|
207
|
-
})),
|
|
208
|
-
z.strictObject({ label: text, url })
|
|
209
|
-
]);
|
|
210
|
-
const visibility = z.enum(["public", "cv", "private"]).default("public");
|
|
211
|
-
function entry(shape) {
|
|
212
|
-
return z.strictObject({ visibility, ...shape });
|
|
213
|
-
}
|
|
214
|
-
function section(shape) {
|
|
215
|
-
return z.array(entry(shape));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
const education = section({
|
|
219
|
-
date: dateRange,
|
|
220
|
-
degree: text,
|
|
221
|
-
institution,
|
|
222
|
-
thesis: z.strictObject({
|
|
223
|
-
title: richText,
|
|
224
|
-
type: text.optional(),
|
|
225
|
-
supervisor: z.union([text, link]).optional(),
|
|
226
|
-
url: url.optional()
|
|
227
|
-
}).optional(),
|
|
228
|
-
note: text.optional()
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
const employment = section({
|
|
232
|
-
date: dateRange,
|
|
233
|
-
position: text,
|
|
234
|
-
institution,
|
|
235
|
-
project: z.strictObject({
|
|
236
|
-
title: richText,
|
|
237
|
-
funder: text.optional(),
|
|
238
|
-
grant: text.optional(),
|
|
239
|
-
url: url.optional()
|
|
240
|
-
}).optional(),
|
|
241
|
-
note: text.optional()
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const teaching = section({
|
|
245
|
-
date: dateRange,
|
|
246
|
-
title: richText,
|
|
247
|
-
role: text.optional(),
|
|
248
|
-
institution: institution.optional(),
|
|
249
|
-
/** e.g. `2024S`, `Winter 2023/24` -- free text, shown as written. */
|
|
250
|
-
term: text.optional(),
|
|
251
|
-
url: url.optional(),
|
|
252
|
-
note: text.optional()
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
const talks = section({
|
|
256
|
-
date: dateRange,
|
|
257
|
-
/** Markdown, math allowed. */
|
|
258
|
-
title: richText,
|
|
259
|
-
event: text.optional(),
|
|
260
|
-
location: text.optional(),
|
|
261
|
-
kind: z.enum(["invited", "contributed", "seminar", "poster", "outreach"]).optional(),
|
|
262
|
-
url: url.optional(),
|
|
263
|
-
slides: url.optional()
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
const supervision = section({
|
|
267
|
-
date: dateRange,
|
|
268
|
-
student: text,
|
|
269
|
-
/** A thesis still in progress; shown as such instead of as completed. */
|
|
270
|
-
ongoing: z.boolean().default(false),
|
|
271
|
-
/** Markdown, math allowed. */
|
|
272
|
-
title: richText,
|
|
273
|
-
degree: text.optional(),
|
|
274
|
-
institution: institution.optional(),
|
|
275
|
-
coSupervisor: text.optional()
|
|
276
|
-
});
|
|
277
|
-
|
|
278
|
-
const profile = z.strictObject({
|
|
279
|
-
name: text,
|
|
280
|
-
title: text.optional(),
|
|
281
|
-
affiliation: institution.optional(),
|
|
282
|
-
email: z.string().trim().email().optional(),
|
|
283
|
-
links: z.array(link).default([]),
|
|
284
|
-
/**
|
|
285
|
-
* One or two sentences for metadata: the page description and social
|
|
286
|
-
* previews. Prose belongs in `content/pages/index.md`, which is where a
|
|
287
|
-
* reader sees it -- this is deliberately not rendered on the page.
|
|
288
|
-
*/
|
|
289
|
-
summary: text.optional(),
|
|
290
|
-
/**
|
|
291
|
-
* Where the website is published, e.g. `https://jane.example.org/` or
|
|
292
|
-
* `https://jane.github.io/site/`. The PDF needs it to link paths on the site
|
|
293
|
-
* such as `/slides/talk.pdf`; the website itself does not.
|
|
294
|
-
*/
|
|
295
|
-
url: absoluteUrl.optional(),
|
|
296
|
-
/** Path below `public/`, e.g. `/img/portrait.jpg`. */
|
|
297
|
-
avatar: text.startsWith("/").optional(),
|
|
298
|
-
/** Navigation, in the order it should appear. */
|
|
299
|
-
nav: z.array(z.strictObject({ label: text, to: text })).default([]),
|
|
300
|
-
/** Footer line; inline Markdown. */
|
|
301
|
-
footer: text.optional()
|
|
302
|
-
});
|
|
303
|
-
const page = z.strictObject({
|
|
304
|
-
title: text,
|
|
305
|
-
description: text.optional(),
|
|
306
|
-
route: text.regex(/^\/(?:[a-z0-9-]+\/)*$/, "expected a route like /research/").optional(),
|
|
307
|
-
order: z.number().int().default(100)
|
|
308
|
-
});
|
|
309
|
-
const schemas = {
|
|
310
|
-
education,
|
|
311
|
-
employment,
|
|
312
|
-
teaching,
|
|
313
|
-
talks,
|
|
314
|
-
supervision
|
|
315
|
-
};
|
|
316
|
-
function extendSection(base, shape) {
|
|
317
|
-
return z.array(base.element.extend(shape));
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
const LEVEL = { public: 0, cv: 1, private: 2 };
|
|
321
|
-
function atMost(value, limit) {
|
|
322
|
-
return LEVEL[value ?? "public"] <= LEVEL[limit];
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
export { whereOf as A, messageOf as B, page as C, Issues as I, MARKER_KEY as M, employment as a, entry as b, extendSection as c, dateRange as d, education as e, section as f, supervision as g, teaching as h, institution as i, text as j, atMost as k, link as l, fail as m, isSitePath as n, resolveAsset as o, formatZodIssues as p, importProjectModule as q, richText as r, schemas as s, talks as t, url as u, visibility as v, renderRichFields as w, resolveSitePaths as x, profile as y, absoluteSitePath as z };
|
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Render content/ into cv-tex/generated/ and typeset it.
|
|
4
|
-
*/
|
|
5
|
-
import process from 'node:process'
|
|
6
|
-
import { buildCv } from '../../dist/tex.mjs'
|
|
7
|
-
|
|
8
|
-
const HELP = `vitakit-build-cv — build the PDF documents in cv-tex/
|
|
9
|
-
|
|
10
|
-
Usage
|
|
11
|
-
pnpm build:cv [document...] [options]
|
|
12
|
-
|
|
13
|
-
Renders content/ into cv-tex/generated/<document>/, one .tex file per section,
|
|
14
|
-
then runs latexmk. The finished PDF is written next to its source, as
|
|
15
|
-
cv-tex/<document>.pdf; everything else stays in the generated directory.
|
|
16
|
-
|
|
17
|
-
pnpm build:cv every document in cv-tex/
|
|
18
|
-
pnpm build:cv cv just cv.tex
|
|
19
|
-
pnpm build:cv cv application several
|
|
20
|
-
|
|
21
|
-
Options
|
|
22
|
-
--no-pdf write the .tex files but do not run latexmk. Useful without a
|
|
23
|
-
TeX installation, and to inspect what was generated.
|
|
24
|
-
--help this text
|
|
25
|
-
|
|
26
|
-
A document is a .tex file you own; a copy of cv.tex is a new document. What it
|
|
27
|
-
contains is decided by an optional cv-tex/<document>.yml (visibility, which
|
|
28
|
-
entries, how many). See docs/cv-documents.md.
|
|
29
|
-
|
|
30
|
-
Requires latexmk and biber, from any current TeX distribution.`
|
|
31
|
-
|
|
32
|
-
const args = process.argv.slice(2)
|
|
33
|
-
|
|
34
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
35
|
-
console.log(HELP)
|
|
36
|
-
process.exit(0)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
const noPdf = args.includes('--no-pdf')
|
|
40
|
-
const only = args.filter(argument => !argument.startsWith('-'))
|
|
41
|
-
|
|
42
|
-
try {
|
|
43
|
-
const outputs = await buildCv({ rootDir: process.cwd(), only, noPdf })
|
|
44
|
-
console.log(noPdf
|
|
45
|
-
? `vitakit: generated .tex for ${only.length ? only.join(', ') : 'all documents'}`
|
|
46
|
-
: `vitakit: built ${outputs.join(', ')}`)
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(`\nvitakit: ${error instanceof Error ? error.message : String(error)}\n`)
|
|
49
|
-
process.exit(1)
|
|
50
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Assert that the generated site is complete and correct.
|
|
4
|
-
*/
|
|
5
|
-
import process from 'node:process'
|
|
6
|
-
import { checkStatic } from '../../dist/check.mjs'
|
|
7
|
-
|
|
8
|
-
const HELP = `vitakit-check — verify the generated site
|
|
9
|
-
|
|
10
|
-
Usage
|
|
11
|
-
pnpm check:static [--out <dir>]
|
|
12
|
-
|
|
13
|
-
Run after \`nuxt generate\`; \`pnpm check\` does both. Checks the built HTML,
|
|
14
|
-
not your content, because a schema cannot catch a renderer that silently did
|
|
15
|
-
nothing:
|
|
16
|
-
|
|
17
|
-
- every page in content/pages/ was generated
|
|
18
|
-
- mathematics was typeset, and KaTeX reported no errors
|
|
19
|
-
- the site was built for the deployment base path (NUXT_APP_BASE_URL), and
|
|
20
|
-
every link and asset (href, src, srcset, poster, …) stays inside it
|
|
21
|
-
- every link into the site leads to a file or page that was generated,
|
|
22
|
-
e.g. slides: /slides/talk.pdf to public/slides/talk.pdf
|
|
23
|
-
- no entry marked \`visibility: cv\` or \`private\` appears anywhere in the site
|
|
24
|
-
- the bibliography is fully formatted wherever it is shown
|
|
25
|
-
|
|
26
|
-
Options
|
|
27
|
-
--out <dir> the generated site; defaults to .output/public
|
|
28
|
-
--no-leak-check skip the check for cv and private entries. It compares their
|
|
29
|
-
titles with the text of every page, so a title that is
|
|
30
|
-
legitimately public elsewhere looks like a leak.
|
|
31
|
-
--help this text
|
|
32
|
-
|
|
33
|
-
Reports every problem and exits non-zero if there is one, so it can gate a
|
|
34
|
-
deployment.`
|
|
35
|
-
|
|
36
|
-
const args = process.argv.slice(2)
|
|
37
|
-
|
|
38
|
-
if (args.includes('--help') || args.includes('-h')) {
|
|
39
|
-
console.log(HELP)
|
|
40
|
-
process.exit(0)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const outIndex = args.indexOf('--out')
|
|
44
|
-
|
|
45
|
-
const { checked, failures } = await checkStatic({
|
|
46
|
-
rootDir: process.cwd(),
|
|
47
|
-
outDir: outIndex === -1 ? undefined : args[outIndex + 1],
|
|
48
|
-
skipLeakCheck: args.includes('--no-leak-check'),
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
for (const line of checked) console.log(` ok ${line}`)
|
|
52
|
-
if (failures.length) {
|
|
53
|
-
console.error(`\nvitakit-check: ${failures.length} problem(s):`)
|
|
54
|
-
for (const failure of failures) console.error(` \u2716 ${failure}`)
|
|
55
|
-
process.exit(1)
|
|
56
|
-
}
|
|
57
|
-
console.log('\nvitakit-check: generated site looks correct.')
|