@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
package/dist/cli.d.mts
CHANGED
|
@@ -1,45 +1,38 @@
|
|
|
1
|
-
interface
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
interface Io {
|
|
2
|
+
cwd: string;
|
|
3
|
+
env: NodeJS.ProcessEnv;
|
|
4
|
+
/** Write to stdout; text is written as is, add `\n` yourself. */
|
|
5
|
+
out: (text: string) => void;
|
|
6
|
+
err: (text: string) => void;
|
|
7
|
+
/** Whether a person can answer questions: stdin and stdout are terminals. */
|
|
8
|
+
interactive: boolean;
|
|
9
|
+
/** Whether stdout is a terminal, for colour. */
|
|
10
|
+
tty: boolean;
|
|
11
|
+
/** Width of the terminal, when stdout is one. */
|
|
12
|
+
columns?: number;
|
|
13
|
+
/** Ask a question and return the answer; only called when `interactive`. */
|
|
14
|
+
ask: (question: string) => Promise<string>;
|
|
8
15
|
}
|
|
9
|
-
interface WrittenFile {
|
|
10
|
-
path: string;
|
|
11
|
-
/** Why it was written, for the command's output. */
|
|
12
|
-
note: string;
|
|
13
|
-
}
|
|
14
|
-
/**
|
|
15
|
-
* Create what is missing for a section: the YAML file, the schema, optionally
|
|
16
|
-
* a component. Existing files are never touched.
|
|
17
|
-
*/
|
|
18
|
-
declare function addSection(id: string, rootDir: string, options?: AddOptions): Promise<WrittenFile[]>;
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
kind: 'component' | 'template' | 'class' | 'schema' | 'style';
|
|
24
|
-
/** Absolute source path inside the package. */
|
|
25
|
-
source: string;
|
|
26
|
-
/** Destination relative to the project root. */
|
|
27
|
-
target: string;
|
|
28
|
-
}
|
|
29
|
-
/** Everything that can be copied into a project. */
|
|
30
|
-
declare function listEjectable(): Promise<EjectableItem[]>;
|
|
31
|
-
interface EjectResult {
|
|
32
|
-
item: EjectableItem;
|
|
33
|
-
/** Absolute path written. */
|
|
34
|
-
path: string;
|
|
35
|
-
}
|
|
17
|
+
declare function main(argv: string[], io?: Io): Promise<number>;
|
|
18
|
+
|
|
19
|
+
type PackageManager = 'pnpm' | 'npm';
|
|
36
20
|
/**
|
|
37
|
-
*
|
|
38
|
-
*
|
|
21
|
+
* The package manager that started this process, from the user agent npm and
|
|
22
|
+
* pnpm set for everything they run (`npm run`, `npx`, `pnpm <script>`,
|
|
23
|
+
* `pnpm exec`); anything but npm gets pnpm, the default, as in the creator.
|
|
24
|
+
* A binary run directly has no user agent; the project's lockfile decides.
|
|
39
25
|
*/
|
|
40
|
-
declare function
|
|
41
|
-
|
|
42
|
-
|
|
26
|
+
declare function detectPackageManager(rootDir?: string, env?: NodeJS.ProcessEnv): PackageManager;
|
|
27
|
+
interface Commands {
|
|
28
|
+
/** A script from package.json: `pnpm pdf cv`, `npm run pdf -- cv`. */
|
|
29
|
+
script: (name: string, args?: string) => string;
|
|
30
|
+
/** A binary of an installed package: `pnpm vitakit status`, `npx vitakit status`. */
|
|
31
|
+
bin: (name: string, args?: string) => string;
|
|
32
|
+
}
|
|
33
|
+
declare function commands(packageManager?: PackageManager): Commands;
|
|
34
|
+
/** Lines of `command # comment`, with the comments aligned. */
|
|
35
|
+
declare function aligned(rows: [command: string, comment?: string][], indent?: string): string;
|
|
43
36
|
|
|
44
|
-
export {
|
|
45
|
-
export type {
|
|
37
|
+
export { aligned, commands, detectPackageManager, main };
|
|
38
|
+
export type { Commands, Io, PackageManager };
|
package/dist/cli.mjs
CHANGED
|
@@ -1,225 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import { b as bundledTexDir } from './shared/vitakit.BRhjqpAt.mjs';
|
|
6
|
-
import { parse } from 'yaml';
|
|
1
|
+
export { a as aligned, c as commands, d as detectPackageManager, m as main } from './shared/vitakit.CoWX2Z4R.mjs';
|
|
2
|
+
import 'node:fs';
|
|
3
|
+
import 'node:path';
|
|
4
|
+
import './shared/vitakit.DQ6PmGoL.mjs';
|
|
7
5
|
import 'node:url';
|
|
8
|
-
import '
|
|
9
|
-
import '
|
|
10
|
-
import '
|
|
11
|
-
import './shared/vitakit.BlU-p01e.mjs';
|
|
12
|
-
|
|
13
|
-
const DATE_LIKE = /^\d{4}(-\d{2}){0,2}$/;
|
|
14
|
-
function pascalCase(id) {
|
|
15
|
-
return id.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
16
|
-
}
|
|
17
|
-
function inferField(values, present, total) {
|
|
18
|
-
const optional = present < total ? ".optional()" : "";
|
|
19
|
-
const sample = values[0];
|
|
20
|
-
if (typeof sample === "boolean") return `z.boolean().default(false)`;
|
|
21
|
-
if (typeof sample === "number" && !DATE_LIKE.test(String(sample))) return `z.number()${optional}`;
|
|
22
|
-
if (values.every((value) => DATE_LIKE.test(String(value)))) return `dateRange${optional}`;
|
|
23
|
-
if (typeof sample === "object" && sample !== null && "from" in sample) return `dateRange${optional}`;
|
|
24
|
-
if (typeof sample === "string" && /^https?:\/\//.test(sample)) return `url${optional}`;
|
|
25
|
-
const strings = values.filter((value) => typeof value === "string");
|
|
26
|
-
const unique = [...new Set(strings)];
|
|
27
|
-
if (strings.length === values.length && unique.length > 1 && unique.length <= Math.max(2, values.length / 2) && unique.every((value) => /^[a-z][\w-]*$/.test(value))) {
|
|
28
|
-
return `z.enum([${unique.map((value) => `'${value}'`).join(", ")}])${optional}`;
|
|
29
|
-
}
|
|
30
|
-
if (typeof sample === "string") return `text${optional}`;
|
|
31
|
-
return `z.unknown()${optional}`;
|
|
32
|
-
}
|
|
33
|
-
function inferSchema(entries) {
|
|
34
|
-
const fields = /* @__PURE__ */ new Map();
|
|
35
|
-
for (const entry of entries) {
|
|
36
|
-
for (const [key, value] of Object.entries(entry)) {
|
|
37
|
-
if (key === "visibility") continue;
|
|
38
|
-
if (!fields.has(key)) fields.set(key, []);
|
|
39
|
-
if (value !== void 0 && value !== null) fields.get(key).push(value);
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
const used = /* @__PURE__ */ new Set(["section"]);
|
|
43
|
-
const lines = [];
|
|
44
|
-
for (const [key, values] of fields) {
|
|
45
|
-
const present = entries.filter((entry) => entry[key] !== void 0).length;
|
|
46
|
-
let type = inferField(values, present, entries.length);
|
|
47
|
-
if (/^(title|headline)$/.test(key) && type.startsWith("text")) type = type.replace("text", "richText");
|
|
48
|
-
for (const primitive of ["dateRange", "richText", "text", "url", "z"]) {
|
|
49
|
-
if (new RegExp(`\\b${primitive}\\b`).test(type)) used.add(primitive);
|
|
50
|
-
}
|
|
51
|
-
lines.push(` ${key}: ${type},`);
|
|
52
|
-
}
|
|
53
|
-
return { body: lines.join("\n"), imports: [...used].sort() };
|
|
54
|
-
}
|
|
55
|
-
const SAMPLE_YAML = (id) => `# ${id}: one entry per item, newest first is not required --
|
|
56
|
-
# vitakit sorts by date for you. Edit the fields, then edit schemas/${id}.ts
|
|
57
|
-
# to match.
|
|
58
|
-
- date: 2025-03-14
|
|
59
|
-
title: An example entry
|
|
60
|
-
note: Replace this with something real.
|
|
61
|
-
`;
|
|
62
|
-
function schemaModule(id, body, imports, inferred) {
|
|
63
|
-
return [
|
|
64
|
-
`/**`,
|
|
65
|
-
` * Shape of content/${id}.yml.`,
|
|
66
|
-
` *`,
|
|
67
|
-
inferred ? ` * Drafted from the existing file: check the types, especially which fields
|
|
68
|
-
* are optional and whether any should be an enum.` : ` * Edit freely: this file defines what content/${id}.yml may contain.`,
|
|
69
|
-
` *`,
|
|
70
|
-
` * \`section()\` adds \`visibility\` and validates a list of entries.`,
|
|
71
|
-
` */`,
|
|
72
|
-
`import { ${imports.join(", ")} } from '@anja.codes/vitakit'`,
|
|
73
|
-
``,
|
|
74
|
-
`export default section({`,
|
|
75
|
-
body || ` date: dateRange,
|
|
76
|
-
title: richText,`,
|
|
77
|
-
`})`,
|
|
78
|
-
``
|
|
79
|
-
].join("\n");
|
|
80
|
-
}
|
|
81
|
-
function componentModule(id) {
|
|
82
|
-
`${pascalCase(id)}Section`;
|
|
83
|
-
return `<script setup lang="ts">
|
|
84
|
-
/**
|
|
85
|
-
* How ${id} entries look. Yours to change: props in, markup out.
|
|
86
|
-
*
|
|
87
|
-
* \`useSection('${id}')\` returns validated, sorted entries; \`VitaEntry\`,
|
|
88
|
-
* \`VitaDate\` and \`richHtml\` come from vitakit.
|
|
89
|
-
*/
|
|
90
|
-
import { richHtml, type Entry } from '@anja.codes/vitakit/shared'
|
|
91
|
-
|
|
92
|
-
const props = defineProps<{ entries?: Entry[], limit?: number }>()
|
|
93
|
-
|
|
94
|
-
const items = computed(() => {
|
|
95
|
-
const source = props.entries ?? useSection('${id}')
|
|
96
|
-
return props.limit ? source.slice(0, props.limit) : source
|
|
97
|
-
})
|
|
98
|
-
<\/script>
|
|
99
|
-
|
|
100
|
-
<template>
|
|
101
|
-
<ul v-if="items.length" class="vita-section">
|
|
102
|
-
<VitaEntry v-for="item in items" :key="item.id" :date="item.date as never">
|
|
103
|
-
<p class="vita-entry__title" v-html="richHtml(item.title)" />
|
|
104
|
-
<p v-if="typeof item.note === 'string'" class="vita-entry__note">
|
|
105
|
-
{{ item.note }}
|
|
106
|
-
</p>
|
|
107
|
-
</VitaEntry>
|
|
108
|
-
</ul>
|
|
109
|
-
</template>
|
|
110
|
-
`;
|
|
111
|
-
}
|
|
112
|
-
async function addSection(id, rootDir, options = {}) {
|
|
113
|
-
if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(id)) {
|
|
114
|
-
throw new Error(`"${id}" must be lowercase kebab-case, e.g. research-stays`);
|
|
115
|
-
}
|
|
116
|
-
if (id in schemas) {
|
|
117
|
-
throw new Error(
|
|
118
|
-
`"${id}" is a built-in section: create content/${id}.yml and it works.
|
|
119
|
-
To change which fields it may contain, copy its schema into your project:
|
|
120
|
-
pnpm vitakit eject ${id}
|
|
121
|
-
and edit schemas/${id}.ts.`
|
|
122
|
-
);
|
|
123
|
-
}
|
|
124
|
-
const written = [];
|
|
125
|
-
const yamlPath = join(rootDir, "content", `${id}.yml`);
|
|
126
|
-
const schemaPath = join(rootDir, "schemas", `${id}.ts`);
|
|
127
|
-
let entries = [];
|
|
128
|
-
let inferred = false;
|
|
129
|
-
if (existsSync(yamlPath)) {
|
|
130
|
-
const parsed = parse(await readFile(yamlPath, "utf8"));
|
|
131
|
-
if (!Array.isArray(parsed)) throw new Error(`content/${id}.yml must contain a list of entries`);
|
|
132
|
-
entries = parsed.filter((entry) => typeof entry === "object" && entry !== null);
|
|
133
|
-
if (!entries.length) throw new Error(`content/${id}.yml has no entries to infer a schema from`);
|
|
134
|
-
inferred = true;
|
|
135
|
-
} else {
|
|
136
|
-
await mkdir(join(yamlPath, ".."), { recursive: true });
|
|
137
|
-
await writeFile(yamlPath, SAMPLE_YAML(id));
|
|
138
|
-
written.push({ path: yamlPath, note: "your data \u2014 edit this first" });
|
|
139
|
-
entries = [{ date: "2025-03-14", title: "An example entry", note: "Replace this." }];
|
|
140
|
-
}
|
|
141
|
-
if (existsSync(schemaPath)) {
|
|
142
|
-
throw new Error(`schemas/${id}.ts already exists; delete it first to regenerate`);
|
|
143
|
-
}
|
|
144
|
-
const { body, imports } = inferSchema(entries);
|
|
145
|
-
await mkdir(join(schemaPath, ".."), { recursive: true });
|
|
146
|
-
await writeFile(schemaPath, schemaModule(id, body, imports, inferred));
|
|
147
|
-
written.push({ path: schemaPath, note: inferred ? `drafted from content/${id}.yml` : "the shape of an entry" });
|
|
148
|
-
if (options.component !== false) {
|
|
149
|
-
const componentPath = join(rootDir, "app", "components", `${pascalCase(id)}Section.vue`);
|
|
150
|
-
if (!existsSync(componentPath)) {
|
|
151
|
-
await mkdir(join(componentPath, ".."), { recursive: true });
|
|
152
|
-
await writeFile(componentPath, componentModule(id));
|
|
153
|
-
written.push({ path: componentPath, note: "how the entries look" });
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
return written.map((file) => ({ ...file, path: relative(rootDir, file.path) }));
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
async function listEjectable() {
|
|
160
|
-
const items = [];
|
|
161
|
-
const componentsDir = resolveAsset("runtime/components");
|
|
162
|
-
for (const file of (await readdir(componentsDir)).filter((name) => name.endsWith(".vue")).sort()) {
|
|
163
|
-
items.push({
|
|
164
|
-
name: basename(file, ".vue"),
|
|
165
|
-
kind: "component",
|
|
166
|
-
source: join(componentsDir, file),
|
|
167
|
-
target: join("app", "components", file)
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
const templatesDir = join(bundledTexDir, "templates");
|
|
171
|
-
for (const file of (await readdir(templatesDir)).filter((name) => name.endsWith(".eta")).sort()) {
|
|
172
|
-
items.push({
|
|
173
|
-
name: file,
|
|
174
|
-
kind: "template",
|
|
175
|
-
source: join(templatesDir, file),
|
|
176
|
-
target: join("cv-tex", "templates", file)
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
const schemaDir = resolveAsset("schemas");
|
|
180
|
-
for (const file of (await readdir(schemaDir)).filter((name) => name.endsWith(".ts")).sort()) {
|
|
181
|
-
const id = basename(file, ".ts");
|
|
182
|
-
items.push({
|
|
183
|
-
name: id,
|
|
184
|
-
kind: "schema",
|
|
185
|
-
source: join(schemaDir, file),
|
|
186
|
-
target: join("schemas", file)
|
|
187
|
-
});
|
|
188
|
-
}
|
|
189
|
-
items.push({
|
|
190
|
-
name: "publications.csl",
|
|
191
|
-
kind: "style",
|
|
192
|
-
// The path of `defaultCslStyle`, without loading citation-js to get it.
|
|
193
|
-
source: join(resolveAsset("styles"), "vitakit-math.csl"),
|
|
194
|
-
target: join("content", "publications.csl")
|
|
195
|
-
});
|
|
196
|
-
items.push({
|
|
197
|
-
name: "vitakit-cv.cls",
|
|
198
|
-
kind: "class",
|
|
199
|
-
source: join(bundledTexDir, "vitakit-cv.cls"),
|
|
200
|
-
target: join("cv-tex", "vitakit-cv.cls")
|
|
201
|
-
});
|
|
202
|
-
return items;
|
|
203
|
-
}
|
|
204
|
-
async function eject(name, rootDir, { force = false } = {}) {
|
|
205
|
-
const items = await listEjectable();
|
|
206
|
-
const item = items.find((candidate) => candidate.name === name) ?? items.find((candidate) => candidate.name.toLowerCase() === name.toLowerCase());
|
|
207
|
-
if (!item) {
|
|
208
|
-
const known = items.map((candidate) => candidate.name).join(", ");
|
|
209
|
-
throw new Error(`unknown item "${name}". Available: ${known}`);
|
|
210
|
-
}
|
|
211
|
-
const path = join(rootDir, item.target);
|
|
212
|
-
if (existsSync(path) && !force) {
|
|
213
|
-
throw new Error(`${relative(rootDir, path)} already exists; pass --force to overwrite`);
|
|
214
|
-
}
|
|
215
|
-
await mkdir(join(path, ".."), { recursive: true });
|
|
216
|
-
if (item.kind === "schema") {
|
|
217
|
-
const source = await readFile(item.source, "utf8");
|
|
218
|
-
await writeFile(path, source.replace(/from '\.\.\/[\w-]+'/g, "from '@anja.codes/vitakit'"));
|
|
219
|
-
} else {
|
|
220
|
-
await copyFile(item.source, path);
|
|
221
|
-
}
|
|
222
|
-
return { item, path };
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
export { addSection, eject, listEjectable };
|
|
6
|
+
import 'node:process';
|
|
7
|
+
import 'node:util';
|
|
8
|
+
import 'node:readline/promises';
|