@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.
- package/.tool-versions +1 -0
- package/README.md +230 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +39 -0
- package/dist/cli.js.map +1 -0
- package/dist/engine/hash.d.ts +20 -0
- package/dist/engine/hash.d.ts.map +1 -0
- package/dist/engine/hash.js +53 -0
- package/dist/engine/hash.js.map +1 -0
- package/dist/engine/lock.d.ts +18 -0
- package/dist/engine/lock.d.ts.map +1 -0
- package/dist/engine/lock.js +54 -0
- package/dist/engine/lock.js.map +1 -0
- package/dist/engine/manifest.d.ts +14 -0
- package/dist/engine/manifest.d.ts.map +1 -0
- package/dist/engine/manifest.js +39 -0
- package/dist/engine/manifest.js.map +1 -0
- package/dist/engine/run.d.ts +15 -0
- package/dist/engine/run.d.ts.map +1 -0
- package/dist/engine/run.js +110 -0
- package/dist/engine/run.js.map +1 -0
- package/dist/generators/og-image.d.ts +16 -0
- package/dist/generators/og-image.d.ts.map +1 -0
- package/dist/generators/og-image.js +46 -0
- package/dist/generators/og-image.js.map +1 -0
- package/dist/generators/robots.d.ts +9 -0
- package/dist/generators/robots.d.ts.map +1 -0
- package/dist/generators/robots.js +29 -0
- package/dist/generators/robots.js.map +1 -0
- package/dist/generators/sitemap.d.ts +9 -0
- package/dist/generators/sitemap.d.ts.map +1 -0
- package/dist/generators/sitemap.js +24 -0
- package/dist/generators/sitemap.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/templates/og-default.html +100 -0
- package/dist/types/lock.d.ts +11 -0
- package/dist/types/lock.d.ts.map +1 -0
- package/dist/types/lock.js +2 -0
- package/dist/types/lock.js.map +1 -0
- package/dist/types/manifest.d.ts +7 -0
- package/dist/types/manifest.d.ts.map +1 -0
- package/dist/types/manifest.js +2 -0
- package/dist/types/manifest.js.map +1 -0
- package/dist/types/spec.d.ts +47 -0
- package/dist/types/spec.d.ts.map +1 -0
- package/dist/types/spec.js +2 -0
- package/dist/types/spec.js.map +1 -0
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +32 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/paths.d.ts +14 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +30 -0
- package/dist/utils/paths.js.map +1 -0
- package/package.json +37 -0
- package/src/cli.ts +47 -0
- package/src/engine/hash.ts +71 -0
- package/src/engine/lock.ts +72 -0
- package/src/engine/manifest.ts +52 -0
- package/src/engine/run.ts +156 -0
- package/src/generators/og-image.ts +65 -0
- package/src/generators/robots.ts +44 -0
- package/src/generators/sitemap.ts +36 -0
- package/src/index.ts +8 -0
- package/src/templates/og-default.html +100 -0
- package/src/types/lock.ts +11 -0
- package/src/types/manifest.ts +6 -0
- package/src/types/spec.ts +52 -0
- package/src/utils/fs.ts +32 -0
- package/src/utils/paths.ts +33 -0
- package/tsconfig.json +20 -0
- 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 @@
|
|
|
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
|
package/dist/cli.js.map
ADDED
|
@@ -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":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAI5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AACnF,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,eAAe,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACrF,OAAO,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAczD;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,OAAmB;IAC3C,MAAM,KAAK,GAAG,IAAI,YAAY,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAChD,MAAM,MAAM,GAAc;QACxB,SAAS,EAAE,CAAC;QACZ,OAAO,EAAE,CAAC;QACV,MAAM,EAAE,EAAE;KACX,CAAC;IAEF,IAAI,CAAC;QACH,oBAAoB;QACpB,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;QACzD,MAAM,IAAI,GAAe,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;QAElD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,CAAC,OAAO,CAAC,MAAM,UAAU,CAAC,CAAC;QACpE,CAAC;QAED,6BAA6B;QAC7B,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAExC,wBAAwB;QACxB,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAEpD,gCAAgC;QAChC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,CAAC;gBACH,MAAM,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;YACrE,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBACjE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,MAAM,CAAC,EAAE,KAAK,OAAO,EAAE,CAAC,CAAC;YACjD,CAAC;QACH,CAAC;QAED,6BAA6B;QAC7B,MAAM,QAAQ,CAAC,KAAK,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAEjC,iCAAiC;QACjC,MAAM,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAE7C,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,SAAS,cAAc,MAAM,CAAC,OAAO,aAAa,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC;QAC/G,CAAC;QAED,OAAO,MAAM,CAAC;IAEhB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,OAAO,EAAE,CAAC,CAAC;QACxC,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC;AAED,KAAK,UAAU,aAAa,CAC1B,IAAgB,EAChB,MAAoB,EACpB,IAAgB,EAChB,QAAwB,EACxB,OAAmB,EACnB,MAAiB;IAEjB,uBAAuB;IACvB,MAAM,IAAI,GAAG,MAAM,WAAW,CAAC;QAC7B,IAAI,EAAE,MAAM;QACZ,aAAa,EAAE,MAAM,CAAC,IAAI;KAC3B,CAAC,CAAC;IAEH,+BAA+B;IAC/B,IAAI,CAAC,iBAAiB,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,CAAC,EAAE,CAAC;QAC9C,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;YACpB,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,EAAE,cAAc,CAAC,CAAC;QACvD,CAAC;QACD,MAAM,CAAC,OAAO,EAAE,CAAC;QACjB,OAAO;IACT,CAAC;IAED,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,iBAAiB,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC;IAC5C,CAAC;IAED,yBAAyB;IACzB,IAAI,UAAkB,CAAC;IACvB,IAAI,YAAoB,CAAC;IAEzB,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,QAAQ,GAAG,uBAAuB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;YACpD,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAC;YACrD,YAAY,GAAG,OAAO,QAAQ,EAAE,CAAC;YAEjC,MAAM,eAAe,CACnB;gBACE,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,MAAM;aACP,EACD,UAAU,CACX,CAAC;YAEF,mBAAmB,CAAC,QAAQ,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;YAC7D,MAAM;QACR,CAAC;QAED,KAAK,SAAS,CAAC,CAAC,CAAC;YACf,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,aAAa,CAAC,CAAC;YACpD,YAAY,GAAG,cAAc,CAAC;YAE9B,MAAM,eAAe,CAAC,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC;YAC9C,mBAAmB,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;YAClE,MAAM;QACR,CAAC;QAED,KAAK,QAAQ,CAAC,CAAC,CAAC;YACd,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;YACnD,YAAY,GAAG,aAAa,CAAC;YAE7B,MAAM,cAAc,CAAC,EAAE,MAAM,EAAE,EAAE,UAAU,CAAC,CAAC;YAC7C,mBAAmB,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,EAAE,YAAY,CAAC,CAAC;YACjE,MAAM;QACR,CAAC;QAED;YACE,MAAM,IAAI,KAAK,CAAC,wBAAyB,MAAc,CAAC,IAAI,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,cAAc;IACd,eAAe,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,iBAAiB,CAAC,CAAC;IACvE,MAAM,CAAC,SAAS,EAAE,CAAC;AACrB,CAAC"}
|
|
@@ -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
|