@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.
Files changed (63) hide show
  1. package/README.md +51 -131
  2. package/dist/check.mjs +12 -5
  3. package/dist/chunks/authoring.mjs +367 -0
  4. package/dist/chunks/build.mjs +176 -0
  5. package/dist/chunks/documents.mjs +143 -0
  6. package/dist/chunks/draft.mjs +222 -0
  7. package/dist/chunks/eject.mjs +59 -0
  8. package/dist/chunks/emit.mjs +200 -0
  9. package/dist/chunks/eta.mjs +16 -0
  10. package/dist/chunks/fields.mjs +79 -0
  11. package/dist/chunks/inventory.mjs +199 -0
  12. package/dist/chunks/plan.mjs +144 -0
  13. package/dist/chunks/prompt.mjs +33 -0
  14. package/dist/chunks/schema-source.mjs +12 -0
  15. package/dist/chunks/schemas.mjs +109 -0
  16. package/dist/chunks/sections.mjs +109 -0
  17. package/dist/chunks/snippets.mjs +199 -0
  18. package/dist/chunks/validate.mjs +124 -0
  19. package/dist/chunks/vue.mjs +116 -0
  20. package/dist/cli.d.mts +33 -40
  21. package/dist/cli.mjs +7 -224
  22. package/dist/index.d.mts +372 -42
  23. package/dist/index.mjs +10 -4
  24. package/dist/nuxt.mjs +35 -5
  25. package/dist/runtime/components/VitaSection.vue +94 -38
  26. package/dist/schemas/education.ts +4 -4
  27. package/dist/schemas/employment.ts +3 -3
  28. package/dist/schemas/publications.ts +2 -2
  29. package/dist/schemas/supervision.ts +3 -3
  30. package/dist/schemas/talks.ts +1 -1
  31. package/dist/schemas/teaching.ts +3 -3
  32. package/dist/shared/index.d.mts +60 -9
  33. package/dist/shared/index.mjs +3 -57
  34. package/dist/shared/vitakit.BZZIsWlY.mjs +179 -0
  35. package/dist/shared/vitakit.BerUaxtF.mjs +121 -0
  36. package/dist/shared/{vitakit.D8WRg_Xx.d.mts → vitakit.BfpxDtz1.d.mts} +9 -8
  37. package/dist/shared/{vitakit.CypCF-4Q.mjs → vitakit.By5VWQ1n.mjs} +12 -78
  38. package/dist/shared/vitakit.CfB3hMHl.mjs +60 -0
  39. package/dist/shared/vitakit.CoWX2Z4R.mjs +1554 -0
  40. package/dist/shared/{vitakit.BlU-p01e.mjs → vitakit.CtChKO0I.mjs} +1 -1
  41. package/dist/shared/vitakit.D-DdJaDZ.mjs +20 -0
  42. package/dist/shared/vitakit.D10HyDhF.mjs +19 -0
  43. package/dist/shared/vitakit.DQ6PmGoL.mjs +34 -0
  44. package/dist/shared/vitakit.DbSeENVS.mjs +58 -0
  45. package/dist/shared/vitakit.DcQOdM4I.mjs +57 -0
  46. package/dist/shared/vitakit.Do5HC_MD.mjs +142 -0
  47. package/dist/shared/vitakit.TSMTC5Jl.mjs +6 -0
  48. package/dist/tex/templates/_generic.eta +6 -4
  49. package/dist/tex/templates/education.eta +10 -5
  50. package/dist/tex/templates/employment.eta +12 -5
  51. package/dist/tex/templates/publications.eta +4 -2
  52. package/dist/tex/templates/supervision.eta +10 -2
  53. package/dist/tex/templates/talks.eta +5 -1
  54. package/dist/tex/templates/teaching.eta +6 -4
  55. package/dist/tex/vitakit-cv.cls +21 -6
  56. package/dist/tex.d.mts +53 -5
  57. package/dist/tex.mjs +20 -113
  58. package/package.json +10 -7
  59. package/src/bin/vitakit.mjs +3 -153
  60. package/dist/shared/vitakit.BRhjqpAt.mjs +0 -250
  61. package/dist/shared/vitakit.BxcnlFGP.mjs +0 -325
  62. package/src/bin/vitakit-build-cv.mjs +0 -50
  63. package/src/bin/vitakit-check.mjs +0 -57
package/dist/cli.d.mts CHANGED
@@ -1,45 +1,38 @@
1
- interface AddOptions {
2
- /**
3
- * Also scaffold `app/components/<Name>Section.vue`. On by default: a section
4
- * without a component renders through the generic one, which is a fallback
5
- * rather than a design.
6
- */
7
- component?: boolean;
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
- interface EjectableItem {
21
- /** Name to pass to `eject`, e.g. `VitaSection`, `talks.eta` or `teaching`. */
22
- name: string;
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
- * Copy a package default into the project, where it then wins by the normal
38
- * resolution rule. Never overwrites without `force`.
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 eject(name: string, rootDir: string, { force }?: {
41
- force?: boolean | undefined;
42
- }): Promise<EjectResult>;
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 { addSection, eject, listEjectable };
45
- export type { AddOptions, EjectResult, EjectableItem, WrittenFile };
37
+ export { aligned, commands, detectPackageManager, main };
38
+ export type { Commands, Io, PackageManager };
package/dist/cli.mjs CHANGED
@@ -1,225 +1,8 @@
1
- import { readFile, mkdir, writeFile, copyFile, readdir } from 'node:fs/promises';
2
- import { existsSync } from 'node:fs';
3
- import { join, relative, basename } from 'node:path';
4
- import { s as schemas, o as resolveAsset } from './shared/vitakit.BxcnlFGP.mjs';
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 'zod';
9
- import 'eta';
10
- import './shared/index.mjs';
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';