@whittakertech/virgil 0.1.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 (77) hide show
  1. package/.tool-versions +1 -0
  2. package/README.md +230 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +39 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/engine/hash.d.ts +20 -0
  8. package/dist/engine/hash.d.ts.map +1 -0
  9. package/dist/engine/hash.js +53 -0
  10. package/dist/engine/hash.js.map +1 -0
  11. package/dist/engine/lock.d.ts +18 -0
  12. package/dist/engine/lock.d.ts.map +1 -0
  13. package/dist/engine/lock.js +54 -0
  14. package/dist/engine/lock.js.map +1 -0
  15. package/dist/engine/manifest.d.ts +14 -0
  16. package/dist/engine/manifest.d.ts.map +1 -0
  17. package/dist/engine/manifest.js +39 -0
  18. package/dist/engine/manifest.js.map +1 -0
  19. package/dist/engine/run.d.ts +15 -0
  20. package/dist/engine/run.d.ts.map +1 -0
  21. package/dist/engine/run.js +110 -0
  22. package/dist/engine/run.js.map +1 -0
  23. package/dist/generators/og-image.d.ts +16 -0
  24. package/dist/generators/og-image.d.ts.map +1 -0
  25. package/dist/generators/og-image.js +46 -0
  26. package/dist/generators/og-image.js.map +1 -0
  27. package/dist/generators/robots.d.ts +9 -0
  28. package/dist/generators/robots.d.ts.map +1 -0
  29. package/dist/generators/robots.js +29 -0
  30. package/dist/generators/robots.js.map +1 -0
  31. package/dist/generators/sitemap.d.ts +9 -0
  32. package/dist/generators/sitemap.d.ts.map +1 -0
  33. package/dist/generators/sitemap.js +24 -0
  34. package/dist/generators/sitemap.js.map +1 -0
  35. package/dist/index.d.ts +8 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +5 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/templates/og-default.html +100 -0
  40. package/dist/types/lock.d.ts +11 -0
  41. package/dist/types/lock.d.ts.map +1 -0
  42. package/dist/types/lock.js +2 -0
  43. package/dist/types/lock.js.map +1 -0
  44. package/dist/types/manifest.d.ts +7 -0
  45. package/dist/types/manifest.d.ts.map +1 -0
  46. package/dist/types/manifest.js +2 -0
  47. package/dist/types/manifest.js.map +1 -0
  48. package/dist/types/spec.d.ts +47 -0
  49. package/dist/types/spec.d.ts.map +1 -0
  50. package/dist/types/spec.js +2 -0
  51. package/dist/types/spec.js.map +1 -0
  52. package/dist/utils/fs.d.ts +13 -0
  53. package/dist/utils/fs.d.ts.map +1 -0
  54. package/dist/utils/fs.js +32 -0
  55. package/dist/utils/fs.js.map +1 -0
  56. package/dist/utils/paths.d.ts +14 -0
  57. package/dist/utils/paths.d.ts.map +1 -0
  58. package/dist/utils/paths.js +30 -0
  59. package/dist/utils/paths.js.map +1 -0
  60. package/package.json +37 -0
  61. package/src/cli.ts +47 -0
  62. package/src/engine/hash.ts +71 -0
  63. package/src/engine/lock.ts +72 -0
  64. package/src/engine/manifest.ts +52 -0
  65. package/src/engine/run.ts +156 -0
  66. package/src/generators/og-image.ts +65 -0
  67. package/src/generators/robots.ts +44 -0
  68. package/src/generators/sitemap.ts +36 -0
  69. package/src/index.ts +8 -0
  70. package/src/templates/og-default.html +100 -0
  71. package/src/types/lock.ts +11 -0
  72. package/src/types/manifest.ts +6 -0
  73. package/src/types/spec.ts +52 -0
  74. package/src/utils/fs.ts +32 -0
  75. package/src/utils/paths.ts +33 -0
  76. package/tsconfig.json +20 -0
  77. package/virgil.spec.example.json +65 -0
package/.tool-versions ADDED
@@ -0,0 +1 @@
1
+ nodejs 20.11.1
package/README.md ADDED
@@ -0,0 +1,230 @@
1
+ # WhittakerTech::Virgil
2
+
3
+ **Build-phase sigil forge for static site artifacts**
4
+
5
+ Virgil generates OG images, sitemaps, robots.txt, and other build artifacts deterministically with content-addressed caching.
6
+
7
+ ## Philosophy
8
+
9
+ - **Deterministic**: Content-addressed hashing eliminates timestamp lies
10
+ - **Locked**: Only regenerates what actually changed
11
+ - **CI-first**: Designed for GitHub Actions, not local dev servers
12
+ - **Declarative**: Everything in `virgil.spec.json`
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @whittakertech/virgil
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Create `virgil.spec.json`
23
+
24
+ ```json
25
+ {
26
+ "brand": {
27
+ "name": "WhittakerTech",
28
+ "logo": "docs/public/logo.svg",
29
+ "color": "#5b7cff"
30
+ },
31
+ "product": {
32
+ "name": "MosaicJS",
33
+ "logo": "docs/public/mosaic-logo.svg",
34
+ "version": "0.3.0"
35
+ },
36
+ "outputs": [
37
+ {
38
+ "type": "og-image",
39
+ "id": "getting-started",
40
+ "page": {
41
+ "title": "Getting Started",
42
+ "url": "https://mosaicjs.whittakertech.com/getting-started",
43
+ "description": "Learn how to use MosaicJS"
44
+ }
45
+ }
46
+ ]
47
+ }
48
+ ```
49
+
50
+ ### 2. Run Virgil
51
+
52
+ ```bash
53
+ npx virgil build
54
+ ```
55
+
56
+ ### 3. Reference in frontmatter
57
+
58
+ ```yaml
59
+ ---
60
+ og-image: og:getting-started
61
+ ---
62
+ ```
63
+
64
+ Your static site generator resolves `og:getting-started` via `virgil.manifest.json`.
65
+
66
+ ## How It Works
67
+
68
+ ### Execution Flow
69
+
70
+ 1. **Load spec** (`virgil.spec.json`) - declarative intent
71
+ 2. **Load lock** (`virgil.lock.json`) - truth from last run
72
+ 3. **Hash inputs** - SHA-256 of data + template + generator version
73
+ 4. **Compare** - skip if hash matches
74
+ 5. **Generate** - create artifacts only when needed
75
+ 6. **Update manifest** (`virgil.manifest.json`) - stable references
76
+ 7. **Write lock** - record current state
77
+
78
+ ### Content-Addressed Hashing
79
+
80
+ ```typescript
81
+ hash = sha256(
82
+ JSON.stringify(data) +
83
+ templateContents +
84
+ generatorVersion
85
+ )
86
+ ```
87
+
88
+ Changes to **data**, **template**, or **generator** trigger regeneration. Timestamp changes do not.
89
+
90
+ ### Lock File (`virgil.lock.json`)
91
+
92
+ ```json
93
+ {
94
+ "version": "0.1",
95
+ "entries": {
96
+ "getting-started": {
97
+ "hash": "sha256:9fa4c...",
98
+ "generatedAt": "2025-01-15T10:30:00Z",
99
+ "generator": "og-image",
100
+ "generatorVersion": "0.1.0"
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ### Manifest (`virgil.manifest.json`)
107
+
108
+ ```json
109
+ {
110
+ "version": "0.1",
111
+ "og": {
112
+ "getting-started": "/og/getting-started.1735346221.png"
113
+ }
114
+ }
115
+ ```
116
+
117
+ Frontmatter uses stable IDs (`og:getting-started`), manifest maps to cache-busted filenames.
118
+
119
+ ## Output Types
120
+
121
+ ### OG Images
122
+
123
+ ```json
124
+ {
125
+ "type": "og-image",
126
+ "id": "home",
127
+ "page": {
128
+ "title": "Welcome",
129
+ "url": "https://example.com",
130
+ "description": "Optional description"
131
+ }
132
+ }
133
+ ```
134
+
135
+ Generates 1200×630 PNG using Playwright.
136
+
137
+ ### Sitemap
138
+
139
+ ```json
140
+ {
141
+ "type": "sitemap",
142
+ "id": "main",
143
+ "baseUrl": "https://example.com",
144
+ "pages": [
145
+ {
146
+ "path": "/",
147
+ "lastmod": "2025-01-15",
148
+ "changefreq": "weekly",
149
+ "priority": 1.0
150
+ }
151
+ ]
152
+ }
153
+ ```
154
+
155
+ ### Robots.txt
156
+
157
+ ```json
158
+ {
159
+ "type": "robots",
160
+ "id": "default",
161
+ "rules": [
162
+ {
163
+ "userAgent": "*",
164
+ "allow": ["/"],
165
+ "disallow": ["/admin"]
166
+ }
167
+ ],
168
+ "sitemap": "https://example.com/sitemap.xml"
169
+ }
170
+ ```
171
+
172
+ ## CLI
173
+
174
+ ```bash
175
+ virgil build [options]
176
+
177
+ Options:
178
+ -r, --root <dir> Project root directory (default: cwd)
179
+ -o, --output <dir> Output directory (default: public)
180
+ -v, --verbose Verbose output
181
+ ```
182
+
183
+ ## Programmatic Usage
184
+
185
+ ```typescript
186
+ import { run } from '@whittakertech/virgil';
187
+
188
+ const result = await run({
189
+ rootDir: process.cwd(),
190
+ outputDir: 'public',
191
+ verbose: true
192
+ });
193
+
194
+ console.log(`Generated: ${result.generated}`);
195
+ console.log(`Skipped: ${result.skipped}`);
196
+ ```
197
+
198
+ ## GitHub Actions
199
+
200
+ ```yaml
201
+ - name: Generate artifacts
202
+ run: npx virgil build --verbose
203
+ ```
204
+
205
+ Virgil skips unchanged artifacts, keeping CI fast.
206
+
207
+ ## v0.1 Scope
208
+
209
+ **Supported:**
210
+ - ✅ OG image generation (PNG)
211
+ - ✅ sitemap.xml generation
212
+ - ✅ robots.txt generation
213
+ - ✅ Content-addressed locking
214
+ - ✅ Manifest rewriting
215
+ - ✅ No-op if nothing changed
216
+
217
+ **Not Yet:**
218
+ - ❌ Watch mode
219
+ - ❌ Vite/VitePress runtime integration
220
+ - ❌ React/Vue rendering
221
+ - ❌ Remote data fetching
222
+ - ❌ Partial page crawling
223
+
224
+ ## License
225
+
226
+ MIT
227
+
228
+ ## Author
229
+
230
+ Lee Whittaker • WhittakerTech
package/dist/cli.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js ADDED
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import { run } from './engine/run.js';
4
+ import { resolve } from 'path';
5
+ const program = new Command();
6
+ program
7
+ .name('virgil')
8
+ .description('Build-phase sigil forge for static site artifacts')
9
+ .version('0.1.0');
10
+ program
11
+ .command('build')
12
+ .description('Generate artifacts from virgil.spec.json')
13
+ .option('-r, --root <dir>', 'Project root directory', process.cwd())
14
+ .option('-o, --output <dir>', 'Output directory', 'public')
15
+ .option('-v, --verbose', 'Verbose output', false)
16
+ .action(async (options) => {
17
+ const rootDir = resolve(options.root);
18
+ const outputDir = resolve(rootDir, options.output);
19
+ console.log('🔮 Virgil v0.1.0');
20
+ console.log(`📁 Root: ${rootDir}`);
21
+ console.log(`📦 Output: ${outputDir}`);
22
+ console.log('');
23
+ const result = await run({
24
+ rootDir,
25
+ outputDir,
26
+ verbose: options.verbose
27
+ });
28
+ if (result.errors.length > 0) {
29
+ console.error('\n❌ Errors:');
30
+ for (const error of result.errors) {
31
+ console.error(` ${error}`);
32
+ }
33
+ process.exit(1);
34
+ }
35
+ console.log('\n✨ Done');
36
+ process.exit(0);
37
+ });
38
+ program.parse();
39
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,iBAAiB,CAAC;AACtC,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAE/B,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,mDAAmD,CAAC;KAChE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,0CAA0C,CAAC;KACvD,MAAM,CAAC,kBAAkB,EAAE,wBAAwB,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;KACnE,MAAM,CAAC,oBAAoB,EAAE,kBAAkB,EAAE,QAAQ,CAAC;KAC1D,MAAM,CAAC,eAAe,EAAE,gBAAgB,EAAE,KAAK,CAAC;KAChD,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnD,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAChC,OAAO,CAAC,GAAG,CAAC,YAAY,OAAO,EAAE,CAAC,CAAC;IACnC,OAAO,CAAC,GAAG,CAAC,cAAc,SAAS,EAAE,CAAC,CAAC;IACvC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAEhB,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC;QACvB,OAAO;QACP,SAAS;QACT,OAAO,EAAE,OAAO,CAAC,OAAO;KACzB,CAAC,CAAC;IAEH,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC7B,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC7B,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC;YAClC,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;QAC9B,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,20 @@
1
+ export declare const GENERATOR_VERSION = "0.1.0";
2
+ export interface HashInput {
3
+ data: unknown;
4
+ templatePath?: string;
5
+ generatorName: string;
6
+ }
7
+ /**
8
+ * Compute deterministic hash from input data, template contents, and generator version.
9
+ *
10
+ * This ensures:
11
+ * - Data changes trigger regeneration
12
+ * - Template changes trigger regeneration
13
+ * - Generator version changes trigger regeneration
14
+ */
15
+ export declare function computeHash(input: HashInput): Promise<string>;
16
+ /**
17
+ * Compute hash for a file's contents
18
+ */
19
+ export declare function hashFile(filepath: string): Promise<string>;
20
+ //# sourceMappingURL=hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.d.ts","sourceRoot":"","sources":["../../src/engine/hash.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,iBAAiB,UAAU,CAAC;AAEzC,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;CACvB;AAsBD;;;;;;;GAOG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBnE;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAKhE"}
@@ -0,0 +1,53 @@
1
+ import { createHash } from 'crypto';
2
+ import { readFile } from 'fs/promises';
3
+ export const GENERATOR_VERSION = '0.1.0';
4
+ function stableStringify(value) {
5
+ if (value === null || typeof value !== 'object') {
6
+ return JSON.stringify(value);
7
+ }
8
+ if (Array.isArray(value)) {
9
+ return JSON.stringify(value.map(stableStringify));
10
+ }
11
+ const obj = value;
12
+ const sortedKeys = Object.keys(obj).sort();
13
+ const normalized = {};
14
+ for (const key of sortedKeys) {
15
+ normalized[key] = obj[key];
16
+ }
17
+ return JSON.stringify(normalized);
18
+ }
19
+ /**
20
+ * Compute deterministic hash from input data, template contents, and generator version.
21
+ *
22
+ * This ensures:
23
+ * - Data changes trigger regeneration
24
+ * - Template changes trigger regeneration
25
+ * - Generator version changes trigger regeneration
26
+ */
27
+ export async function computeHash(input) {
28
+ const parts = [];
29
+ // 1. Serialize data (stable JSON)
30
+ parts.push(stableStringify(input.data));
31
+ // 2. Include template contents if specified
32
+ if (input.templatePath) {
33
+ const templateContents = await readFile(input.templatePath, 'utf-8');
34
+ parts.push(templateContents);
35
+ }
36
+ // 3. Include generator identity
37
+ parts.push(input.generatorName);
38
+ parts.push(GENERATOR_VERSION);
39
+ // Compute SHA-256
40
+ const hash = createHash('sha256');
41
+ hash.update(parts.join('\n'));
42
+ return `sha256:${hash.digest('hex')}`;
43
+ }
44
+ /**
45
+ * Compute hash for a file's contents
46
+ */
47
+ export async function hashFile(filepath) {
48
+ const contents = await readFile(filepath);
49
+ const hash = createHash('sha256');
50
+ hash.update(contents);
51
+ return `sha256:${hash.digest('hex')}`;
52
+ }
53
+ //# sourceMappingURL=hash.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hash.js","sourceRoot":"","sources":["../../src/engine/hash.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,MAAM,CAAC,MAAM,iBAAiB,GAAG,OAAO,CAAC;AAQzC,SAAS,eAAe,CAAC,KAAc;IACrC,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAC;IAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAE3C,MAAM,UAAU,GAA4B,EAAE,CAAC;IAC/C,KAAK,MAAM,GAAG,IAAI,UAAU,EAAE,CAAC;QAC7B,UAAU,CAAC,GAAG,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IAC7B,CAAC;IAED,OAAO,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,KAAgB;IAChD,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,kCAAkC;IAClC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAExC,4CAA4C;IAC5C,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC;QACvB,MAAM,gBAAgB,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACrE,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC;IAC/B,CAAC;IAED,gCAAgC;IAChC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;IAChC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;IAE9B,kBAAkB;IAClB,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAE9B,OAAO,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACxC,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAC1C,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtB,OAAO,UAAU,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CAAC;AACxC,CAAC"}
@@ -0,0 +1,18 @@
1
+ import { VirgilLock } from '../types/lock.js';
2
+ /**
3
+ * Load lock file if it exists, return empty lock otherwise
4
+ */
5
+ export declare function loadLock(lockPath: string): Promise<VirgilLock>;
6
+ /**
7
+ * Write lock file atomically
8
+ */
9
+ export declare function saveLock(lockPath: string, lock: VirgilLock): Promise<void>;
10
+ /**
11
+ * Check if output needs regeneration
12
+ */
13
+ export declare function needsRegeneration(lock: VirgilLock, id: string, currentHash: string): boolean;
14
+ /**
15
+ * Update lock entry
16
+ */
17
+ export declare function updateLockEntry(lock: VirgilLock, id: string, hash: string, generator: string, generatorVersion: string): void;
18
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/engine/lock.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAa,MAAM,kBAAkB,CAAC;AAIzD;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAWpE;AAED;;GAEG;AACH,wBAAsB,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAUhF;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,UAAU,EAChB,EAAE,EAAE,MAAM,EACV,WAAW,EAAE,MAAM,GAClB,OAAO,CAUT;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,UAAU,EAChB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,gBAAgB,EAAE,MAAM,GACvB,IAAI,CAON"}
@@ -0,0 +1,54 @@
1
+ import { readFile, writeFile } from 'fs/promises';
2
+ const LOCK_VERSION = '0.1';
3
+ /**
4
+ * Load lock file if it exists, return empty lock otherwise
5
+ */
6
+ export async function loadLock(lockPath) {
7
+ try {
8
+ const contents = await readFile(lockPath, 'utf-8');
9
+ return JSON.parse(contents);
10
+ }
11
+ catch (err) {
12
+ // Lock doesn't exist or is invalid - start fresh
13
+ return {
14
+ version: LOCK_VERSION,
15
+ entries: {}
16
+ };
17
+ }
18
+ }
19
+ /**
20
+ * Write lock file atomically
21
+ */
22
+ export async function saveLock(lockPath, lock) {
23
+ const contents = JSON.stringify(lock, null, 2);
24
+ // Write to temp file, then rename (atomic on POSIX)
25
+ const tempPath = `${lockPath}.tmp`;
26
+ await writeFile(tempPath, contents, 'utf-8');
27
+ // Atomic rename
28
+ const { rename } = await import('fs/promises');
29
+ await rename(tempPath, lockPath);
30
+ }
31
+ /**
32
+ * Check if output needs regeneration
33
+ */
34
+ export function needsRegeneration(lock, id, currentHash) {
35
+ const entry = lock.entries[id];
36
+ if (!entry) {
37
+ // Never generated before
38
+ return true;
39
+ }
40
+ // Compare hashes
41
+ return entry.hash !== currentHash;
42
+ }
43
+ /**
44
+ * Update lock entry
45
+ */
46
+ export function updateLockEntry(lock, id, hash, generator, generatorVersion) {
47
+ lock.entries[id] = {
48
+ hash,
49
+ generatedAt: new Date().toISOString(),
50
+ generator,
51
+ generatorVersion
52
+ };
53
+ }
54
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/engine/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,YAAY,GAAG,KAAc,CAAC;AAEpC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB;IAC7C,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,iDAAiD;QACjD,OAAO;YACL,OAAO,EAAE,YAAY;YACrB,OAAO,EAAE,EAAE;SACZ,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,QAAgB,EAAE,IAAgB;IAC/D,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAE/C,oDAAoD;IACpD,MAAM,QAAQ,GAAG,GAAG,QAAQ,MAAM,CAAC;IACnC,MAAM,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE7C,gBAAgB;IAChB,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAgB,EAChB,EAAU,EACV,WAAmB;IAEnB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAE/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,yBAAyB;QACzB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,iBAAiB;IACjB,OAAO,KAAK,CAAC,IAAI,KAAK,WAAW,CAAC;AACpC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAgB,EAChB,EAAU,EACV,IAAY,EACZ,SAAiB,EACjB,gBAAwB;IAExB,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,GAAG;QACjB,IAAI;QACJ,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,SAAS;QACT,gBAAgB;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { VirgilManifest } from '../types/manifest.js';
2
+ /**
3
+ * Load manifest if it exists, return empty manifest otherwise
4
+ */
5
+ export declare function loadManifest(manifestPath: string): Promise<VirgilManifest>;
6
+ /**
7
+ * Write manifest atomically
8
+ */
9
+ export declare function saveManifest(manifestPath: string, manifest: VirgilManifest): Promise<void>;
10
+ /**
11
+ * Update manifest entry for given output type
12
+ */
13
+ export declare function updateManifestEntry(manifest: VirgilManifest, type: 'og' | 'sitemap' | 'robots', id: string, path: string): void;
14
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/engine/manifest.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAItD;;GAEG;AACH,wBAAsB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAYhF;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,cAAc,GACvB,OAAO,CAAC,IAAI,CAAC,CAQf;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,EACxB,IAAI,EAAE,IAAI,GAAG,SAAS,GAAG,QAAQ,EACjC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,GACX,IAAI,CAKN"}
@@ -0,0 +1,39 @@
1
+ import { readFile, writeFile } from 'fs/promises';
2
+ const MANIFEST_VERSION = '0.1';
3
+ /**
4
+ * Load manifest if it exists, return empty manifest otherwise
5
+ */
6
+ export async function loadManifest(manifestPath) {
7
+ try {
8
+ const contents = await readFile(manifestPath, 'utf-8');
9
+ return JSON.parse(contents);
10
+ }
11
+ catch (err) {
12
+ return {
13
+ version: MANIFEST_VERSION,
14
+ og: {},
15
+ sitemap: {},
16
+ robots: {}
17
+ };
18
+ }
19
+ }
20
+ /**
21
+ * Write manifest atomically
22
+ */
23
+ export async function saveManifest(manifestPath, manifest) {
24
+ const contents = JSON.stringify(manifest, null, 2);
25
+ const tempPath = `${manifestPath}.tmp`;
26
+ await writeFile(tempPath, contents, 'utf-8');
27
+ const { rename } = await import('fs/promises');
28
+ await rename(tempPath, manifestPath);
29
+ }
30
+ /**
31
+ * Update manifest entry for given output type
32
+ */
33
+ export function updateManifestEntry(manifest, type, id, path) {
34
+ if (!manifest[type]) {
35
+ manifest[type] = {};
36
+ }
37
+ manifest[type][id] = path;
38
+ }
39
+ //# sourceMappingURL=manifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/engine/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGlD,MAAM,gBAAgB,GAAG,KAAc,CAAC;AAExC;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,YAAoB;IACrD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACvD,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO;YACL,OAAO,EAAE,gBAAgB;YACzB,EAAE,EAAE,EAAE;YACN,OAAO,EAAE,EAAE;YACX,MAAM,EAAE,EAAE;SACX,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,YAAoB,EACpB,QAAwB;IAExB,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEnD,MAAM,QAAQ,GAAG,GAAG,YAAY,MAAM,CAAC;IACvC,MAAM,SAAS,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IAE7C,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,aAAa,CAAC,CAAC;IAC/C,MAAM,MAAM,CAAC,QAAQ,EAAE,YAAY,CAAC,CAAC;AACvC,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,mBAAmB,CACjC,QAAwB,EACxB,IAAiC,EACjC,EAAU,EACV,IAAY;IAEZ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpB,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC;IACtB,CAAC;IACD,QAAQ,CAAC,IAAI,CAAE,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC;AAC7B,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface RunOptions {
2
+ rootDir: string;
3
+ outputDir: string;
4
+ verbose?: boolean;
5
+ }
6
+ export interface RunResult {
7
+ generated: number;
8
+ skipped: number;
9
+ errors: string[];
10
+ }
11
+ /**
12
+ * Main orchestration - this is the heart of Virgil
13
+ */
14
+ export declare function run(options: RunOptions): Promise<RunResult>;
15
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/engine/run.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,wBAAsB,GAAG,CAAC,OAAO,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAkDjE"}
@@ -0,0 +1,110 @@
1
+ import { readFile } from 'fs/promises';
2
+ import { join } from 'path';
3
+ import { PathResolver } from '../utils/paths.js';
4
+ import { computeHash, GENERATOR_VERSION } from './hash.js';
5
+ import { loadLock, saveLock, needsRegeneration, updateLockEntry } from './lock.js';
6
+ import { loadManifest, saveManifest, updateManifestEntry } from './manifest.js';
7
+ import { generateOGImage, generateOGImageFilename } from '../generators/og-image.js';
8
+ import { generateSitemap } from '../generators/sitemap.js';
9
+ import { generateRobots } from '../generators/robots.js';
10
+ /**
11
+ * Main orchestration - this is the heart of Virgil
12
+ */
13
+ export async function run(options) {
14
+ const paths = new PathResolver(options.rootDir);
15
+ const result = {
16
+ generated: 0,
17
+ skipped: 0,
18
+ errors: []
19
+ };
20
+ try {
21
+ // Step 1: Load spec
22
+ const specContents = await readFile(paths.spec, 'utf-8');
23
+ const spec = JSON.parse(specContents);
24
+ if (options.verbose) {
25
+ console.log(`📋 Loaded spec with ${spec.outputs.length} outputs`);
26
+ }
27
+ // Step 2: Load previous lock
28
+ const lock = await loadLock(paths.lock);
29
+ // Step 3: Load manifest
30
+ const manifest = await loadManifest(paths.manifest);
31
+ // Step 4-7: Process each output
32
+ for (const output of spec.outputs) {
33
+ try {
34
+ await processOutput(spec, output, lock, manifest, options, result);
35
+ }
36
+ catch (err) {
37
+ const message = err instanceof Error ? err.message : String(err);
38
+ result.errors.push(`${output.id}: ${message}`);
39
+ }
40
+ }
41
+ // Step 8: Write updated lock
42
+ await saveLock(paths.lock, lock);
43
+ // Step 9: Write updated manifest
44
+ await saveManifest(paths.manifest, manifest);
45
+ if (options.verbose) {
46
+ console.log(`✅ Generated: ${result.generated}, Skipped: ${result.skipped}, Errors: ${result.errors.length}`);
47
+ }
48
+ return result;
49
+ }
50
+ catch (err) {
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ result.errors.push(`Fatal: ${message}`);
53
+ return result;
54
+ }
55
+ }
56
+ async function processOutput(spec, output, lock, manifest, options, result) {
57
+ // Compute current hash
58
+ const hash = await computeHash({
59
+ data: output,
60
+ generatorName: output.type
61
+ });
62
+ // Check if regeneration needed
63
+ if (!needsRegeneration(lock, output.id, hash)) {
64
+ if (options.verbose) {
65
+ console.log(`⏭️ Skipping ${output.id} (unchanged)`);
66
+ }
67
+ result.skipped++;
68
+ return;
69
+ }
70
+ if (options.verbose) {
71
+ console.log(`🔨 Generating ${output.id}`);
72
+ }
73
+ // Generate based on type
74
+ let outputPath;
75
+ let manifestPath;
76
+ switch (output.type) {
77
+ case 'og-image': {
78
+ const filename = generateOGImageFilename(output.id);
79
+ outputPath = join(options.outputDir, 'og', filename);
80
+ manifestPath = `/og/${filename}`;
81
+ await generateOGImage({
82
+ brand: spec.brand,
83
+ product: spec.product,
84
+ output
85
+ }, outputPath);
86
+ updateManifestEntry(manifest, 'og', output.id, manifestPath);
87
+ break;
88
+ }
89
+ case 'sitemap': {
90
+ outputPath = join(options.outputDir, 'sitemap.xml');
91
+ manifestPath = '/sitemap.xml';
92
+ await generateSitemap({ output }, outputPath);
93
+ updateManifestEntry(manifest, 'sitemap', output.id, manifestPath);
94
+ break;
95
+ }
96
+ case 'robots': {
97
+ outputPath = join(options.outputDir, 'robots.txt');
98
+ manifestPath = '/robots.txt';
99
+ await generateRobots({ output }, outputPath);
100
+ updateManifestEntry(manifest, 'robots', output.id, manifestPath);
101
+ break;
102
+ }
103
+ default:
104
+ throw new Error(`Unknown output type: ${output.type}`);
105
+ }
106
+ // Update lock
107
+ updateLockEntry(lock, output.id, hash, output.type, GENERATOR_VERSION);
108
+ result.generated++;
109
+ }
110
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/engine/run.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { OGImageOutput, BrandConfig, ProductConfig } from '../types/spec.js';
2
+ export interface OGImageContext {
3
+ brand: BrandConfig;
4
+ product: ProductConfig;
5
+ output: OGImageOutput;
6
+ templatePath?: string;
7
+ }
8
+ /**
9
+ * Generate OG image using Playwright
10
+ */
11
+ export declare function generateOGImage(ctx: OGImageContext, outputPath: string): Promise<void>;
12
+ /**
13
+ * Generate filename for OG image with cache-busting timestamp
14
+ */
15
+ export declare function generateOGImageFilename(id: string): string;
16
+ //# sourceMappingURL=og-image.d.ts.map