@warpgogol/werkstatt-shared 0.2.0 → 0.2.1
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/package.json +1010 -1
- package/src/content/index.ts +22 -0
- package/src/content/markdown-frontmatter.ts +46 -0
- package/src/content/system-manifest.ts +182 -0
- package/src/onboarding/brief.ts +51 -0
- package/src/onboarding/index.ts +20 -0
- package/src/share/legal/index.ts +1 -0
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Public entrypoint for @warpgogol/werkstatt-shared/content — exports
|
|
4
|
+
markdown frontmatter utilities and system manifest loading (RFC-0868).</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not import from app-specific packages or stack plugins.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0868: extracted from werkstatt-site/src/content/index.ts.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { parseMarkdownFrontmatter, stringifyMarkdownFrontmatter } from "./markdown-frontmatter.ts";
|
|
15
|
+
export type { ParsedFrontmatter } from "./markdown-frontmatter.ts";
|
|
16
|
+
export {
|
|
17
|
+
loadSystemManifest,
|
|
18
|
+
loadSystemManifestSync,
|
|
19
|
+
isUsingSystemMd,
|
|
20
|
+
isUsingSystemMdSync,
|
|
21
|
+
} from "./system-manifest.ts";
|
|
22
|
+
export type { SystemManifest, SystemManifestLoadResult } from "./system-manifest.ts";
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Parse and stringify Markdown frontmatter using YAML. Extracts frontmatter
|
|
4
|
+
data and content from --- delimited blocks. Stack-agnostic utility used by both
|
|
5
|
+
engine and site plugin (RFC-0868).</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not handle raw Markdown parsing beyond frontmatter extraction.</item>
|
|
8
|
+
<item>Do not manage file I/O or transport mechanisms for Markdown files.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-0868: extracted from werkstatt-site/src/content/markdown-frontmatter.ts.</item>
|
|
13
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import YAML from "yaml";
|
|
17
|
+
|
|
18
|
+
export type ParsedFrontmatter = {
|
|
19
|
+
content: string;
|
|
20
|
+
data: Record<string, unknown>;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function parseMarkdownFrontmatter(source: string): ParsedFrontmatter {
|
|
24
|
+
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
25
|
+
|
|
26
|
+
if (!match) {
|
|
27
|
+
return {
|
|
28
|
+
content: source,
|
|
29
|
+
data: {},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
content: match[2] ?? "",
|
|
35
|
+
data: (YAML.parse(match[1]) ?? {}) as Record<string, unknown>,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function stringifyMarkdownFrontmatter(
|
|
40
|
+
content: string,
|
|
41
|
+
data: Record<string, unknown>,
|
|
42
|
+
): string {
|
|
43
|
+
const normalizedContent = content.replace(/^\s+/, "").replace(/\s+$/, "");
|
|
44
|
+
const frontmatter = YAML.stringify(data).trimEnd();
|
|
45
|
+
return `---\n${frontmatter}\n---\n\n${normalizedContent}\n`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Facilitates loading and parsing of the canonical system.md manifest per RFC-0047.
|
|
4
|
+
Stack-agnostic utility used by both engine and site plugin (RFC-0868).</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not validate system manifest content (handled by validators).</item>
|
|
7
|
+
<item>Do not handle system manifest generation or modification.</item>
|
|
8
|
+
</non-goals>
|
|
9
|
+
</MODULE_CONTRACT>
|
|
10
|
+
<CHANGE_SUMMARY>
|
|
11
|
+
<item>RFC-0868: extracted from werkstatt-site/src/content/system-manifest.ts.</item>
|
|
12
|
+
</CHANGE_SUMMARY>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFile, access } from "node:fs/promises";
|
|
16
|
+
import { readFileSync, accessSync } from "node:fs";
|
|
17
|
+
import { join } from "node:path";
|
|
18
|
+
import { parseMarkdownFrontmatter } from "./markdown-frontmatter.ts";
|
|
19
|
+
|
|
20
|
+
export interface SystemManifest {
|
|
21
|
+
app: string;
|
|
22
|
+
version: string;
|
|
23
|
+
identity: {
|
|
24
|
+
systemStar: string;
|
|
25
|
+
biome: string;
|
|
26
|
+
tagline: string;
|
|
27
|
+
domain?: string;
|
|
28
|
+
/** RFC-0087: Per-app default CTA pageId for shared header and final-cta. */
|
|
29
|
+
ctaTarget?: string;
|
|
30
|
+
/** RFC-0096: Operator details consumed by legal.scaffold to fill Impressum / Datenschutz stubs. */
|
|
31
|
+
legal?: {
|
|
32
|
+
responsibleName?: string;
|
|
33
|
+
address?: string;
|
|
34
|
+
email?: string;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
i18n?: {
|
|
38
|
+
default: string;
|
|
39
|
+
supported: Record<string, unknown>;
|
|
40
|
+
};
|
|
41
|
+
constellations: string[];
|
|
42
|
+
clientEditable: string[];
|
|
43
|
+
sharedContext?: {
|
|
44
|
+
requiredPageIds: string[];
|
|
45
|
+
};
|
|
46
|
+
pages: Array<{
|
|
47
|
+
pageId: string;
|
|
48
|
+
routes?: Record<string, string>;
|
|
49
|
+
route?: string;
|
|
50
|
+
/** RFC-0097: explicit locale opt-in; the page exists only in these locales. */
|
|
51
|
+
locales?: string[];
|
|
52
|
+
cosmicStar: string;
|
|
53
|
+
planets: Array<{
|
|
54
|
+
cosmicPlanet: string;
|
|
55
|
+
pin: string;
|
|
56
|
+
}>;
|
|
57
|
+
}>;
|
|
58
|
+
growth: {
|
|
59
|
+
vendor: {
|
|
60
|
+
adapter: string;
|
|
61
|
+
options: Record<string, unknown>;
|
|
62
|
+
};
|
|
63
|
+
funnels: unknown[];
|
|
64
|
+
experiments: unknown[];
|
|
65
|
+
};
|
|
66
|
+
release: {
|
|
67
|
+
passport: {
|
|
68
|
+
enabled: boolean;
|
|
69
|
+
indexable: boolean;
|
|
70
|
+
keyVersion: string;
|
|
71
|
+
heartbeatUrl: string;
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* RFC-0211 Content Knowledge Lifecycle policy. Mirrors the `knowledge` block in
|
|
76
|
+
* the ontology systemManifestSchema; declared here so the CKL kernel commands
|
|
77
|
+
* (content.freshness.validate RFC-0213, content.plan.build RFC-0216) read it
|
|
78
|
+
* with real types instead of structural casts.
|
|
79
|
+
*/
|
|
80
|
+
knowledge?: {
|
|
81
|
+
freshness?: {
|
|
82
|
+
soonWindowDays?: number;
|
|
83
|
+
critical?: Array<{ match: string; criticality: "advisory" | "important" | "blocking" }>;
|
|
84
|
+
};
|
|
85
|
+
derivation?: {
|
|
86
|
+
critical?: Array<{ match: string; criticality: "advisory" | "important" | "blocking" }>;
|
|
87
|
+
};
|
|
88
|
+
plan?: {
|
|
89
|
+
leadTimeDays?: number;
|
|
90
|
+
defaultOwner?: string;
|
|
91
|
+
criticalityMap?: Array<{ match: string; criticality: "advisory" | "important" | "blocking" }>;
|
|
92
|
+
};
|
|
93
|
+
};
|
|
94
|
+
/** RFC-0487: Business model declaration. Closed enum — currently only "b2b-only". */
|
|
95
|
+
businessModel?: "b2b-only";
|
|
96
|
+
/** RFC-0487/RFC-0509: Retired page routes — 410 Gone tombstones or 301 redirects. */
|
|
97
|
+
retiredRoutes?: Array<{ slug: string; status: 410 } | { slug: string; status: 301; to: string }>;
|
|
98
|
+
/** UI-level rendering toggles for split-list column order. */
|
|
99
|
+
ui?: {
|
|
100
|
+
responsibilityBlock?: {
|
|
101
|
+
swapOrder?: boolean;
|
|
102
|
+
};
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface SystemManifestLoadResult {
|
|
107
|
+
manifest: SystemManifest;
|
|
108
|
+
source: "system.md";
|
|
109
|
+
filePath: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Loads and parses the canonical system.md manifest from src/content/system.md.
|
|
114
|
+
*
|
|
115
|
+
* @param contentDirectory The src/content directory path
|
|
116
|
+
* @returns Parsed system manifest with source information
|
|
117
|
+
*/
|
|
118
|
+
export async function loadSystemManifest(
|
|
119
|
+
contentDirectory: string,
|
|
120
|
+
): Promise<SystemManifestLoadResult> {
|
|
121
|
+
const systemMdPath = join(contentDirectory, "system.md");
|
|
122
|
+
await access(systemMdPath);
|
|
123
|
+
const content = await readFile(systemMdPath, "utf8");
|
|
124
|
+
const parsed = parseMarkdownFrontmatter(content);
|
|
125
|
+
|
|
126
|
+
return {
|
|
127
|
+
manifest: parsed.data as unknown as SystemManifest,
|
|
128
|
+
source: "system.md",
|
|
129
|
+
filePath: systemMdPath,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Synchronous version of loadSystemManifest for contexts where async is not available.
|
|
135
|
+
*
|
|
136
|
+
* @param contentDirectory The src/content directory path
|
|
137
|
+
* @returns Parsed system manifest with source information
|
|
138
|
+
*/
|
|
139
|
+
export function loadSystemManifestSync(contentDirectory: string): SystemManifestLoadResult {
|
|
140
|
+
const systemMdPath = join(contentDirectory, "system.md");
|
|
141
|
+
accessSync(systemMdPath);
|
|
142
|
+
const content = readFileSync(systemMdPath, "utf8");
|
|
143
|
+
const parsed = parseMarkdownFrontmatter(content);
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
manifest: parsed.data as unknown as SystemManifest,
|
|
147
|
+
source: "system.md",
|
|
148
|
+
filePath: systemMdPath,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Checks if the canonical system.md manifest exists.
|
|
154
|
+
*
|
|
155
|
+
* @param contentDirectory The src/content directory path
|
|
156
|
+
* @returns true if src/content/system.md exists
|
|
157
|
+
*/
|
|
158
|
+
export async function isUsingSystemMd(contentDirectory: string): Promise<boolean> {
|
|
159
|
+
const systemMdPath = join(contentDirectory, "system.md");
|
|
160
|
+
try {
|
|
161
|
+
await access(systemMdPath);
|
|
162
|
+
return true;
|
|
163
|
+
} catch {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Synchronous version of isUsingSystemMd.
|
|
170
|
+
*
|
|
171
|
+
* @param contentDirectory The src/content directory path
|
|
172
|
+
* @returns true if src/content/system.md exists
|
|
173
|
+
*/
|
|
174
|
+
export function isUsingSystemMdSync(contentDirectory: string): boolean {
|
|
175
|
+
const systemMdPath = join(contentDirectory, "system.md");
|
|
176
|
+
try {
|
|
177
|
+
accessSync(systemMdPath);
|
|
178
|
+
return true;
|
|
179
|
+
} catch {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Defines and validates the RFC-0070 onboarding brief contract from
|
|
4
|
+
onboarding/<system-id>/.input/00-brief.md (RFC-0532). Stack-agnostic utility
|
|
5
|
+
used by both engine and site plugin (RFC-0868).</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not scaffold apps or infer derived ecosystem choices like biome or constellation.</item>
|
|
8
|
+
</non-goals>
|
|
9
|
+
</MODULE_CONTRACT>
|
|
10
|
+
<CHANGE_SUMMARY>
|
|
11
|
+
<item>RFC-0868: extracted from werkstatt-site/src/onboarding/brief.ts.</item>
|
|
12
|
+
</CHANGE_SUMMARY>
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import matter from "gray-matter";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import YAML from "yaml";
|
|
18
|
+
|
|
19
|
+
export const BriefFrontmatter = z
|
|
20
|
+
.object({
|
|
21
|
+
client: z.object({
|
|
22
|
+
id: z.string().regex(/^[a-z][a-z0-9-]{2,48}$/),
|
|
23
|
+
domain: z.string().regex(/^([a-z0-9-]+\.)+[a-z]{2,}$/),
|
|
24
|
+
}),
|
|
25
|
+
i18n: z
|
|
26
|
+
.object({
|
|
27
|
+
default: z.string().regex(/^[a-z]{2}$/),
|
|
28
|
+
supported: z.array(z.string().regex(/^[a-z]{2}$/)).min(1),
|
|
29
|
+
})
|
|
30
|
+
.refine((value) => value.supported.includes(value.default), {
|
|
31
|
+
message: "i18n.supported must contain i18n.default",
|
|
32
|
+
path: ["supported"],
|
|
33
|
+
}),
|
|
34
|
+
legalJurisdiction: z.string().regex(/^[A-Z]{2}$/),
|
|
35
|
+
})
|
|
36
|
+
.strict();
|
|
37
|
+
|
|
38
|
+
export type Brief = z.infer<typeof BriefFrontmatter>;
|
|
39
|
+
|
|
40
|
+
export function parseBriefFrontmatter(source: string): Brief {
|
|
41
|
+
const parsed = matter(source);
|
|
42
|
+
return BriefFrontmatter.parse(parsed.data);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function parseSystemFrontmatter(source: string): Record<string, unknown> {
|
|
46
|
+
return matter(source).data as Record<string, unknown>;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function parseMarkdownAsYaml(source: string): Record<string, unknown> {
|
|
50
|
+
return YAML.parse(source) as Record<string, unknown>;
|
|
51
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Public entrypoint for @warpgogol/werkstatt-shared/onboarding — exports
|
|
4
|
+
brief parsing utilities (RFC-0868).</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not import from app-specific packages or stack plugins.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-0868: extracted from werkstatt-site/src/onboarding/brief.ts.</item>
|
|
11
|
+
</CHANGE_SUMMARY>
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
BriefFrontmatter,
|
|
16
|
+
parseBriefFrontmatter,
|
|
17
|
+
parseSystemFrontmatter,
|
|
18
|
+
parseMarkdownAsYaml,
|
|
19
|
+
} from "./brief.ts";
|
|
20
|
+
export type { Brief } from "./brief.ts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./translation-policy.ts";
|