@formfeed/devkit 0.2.0 → 0.3.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/README.md +17 -2
- package/index.cjs +859 -56
- package/index.js +839 -49
- package/package.json +3 -3
- package/types/index.d.cts +9 -4
- package/types/index.d.ts +9 -4
- package/types/lib/brand.d.cts +14 -0
- package/types/lib/brand.d.ts +14 -0
- package/types/lib/local-render.d.cts +39 -3
- package/types/lib/local-render.d.ts +39 -3
- package/types/lib/partials.d.cts +2 -0
- package/types/lib/partials.d.ts +2 -0
- package/types/lib/project-config.d.cts +19 -0
- package/types/lib/project-config.d.ts +19 -0
- package/types/lib/project.d.cts +71 -7
- package/types/lib/project.d.ts +71 -7
- package/types/lib/redact.d.cts +19 -0
- package/types/lib/redact.d.ts +19 -0
- package/types/lib/types/emit-python.d.cts +6 -0
- package/types/lib/types/emit-python.d.ts +6 -0
- package/types/lib/types/emit-ts.d.cts +10 -0
- package/types/lib/types/emit-ts.d.ts +10 -0
- package/types/lib/types/index.d.cts +15 -0
- package/types/lib/types/index.d.ts +15 -0
- package/types/lib/types/model.d.cts +81 -0
- package/types/lib/types/model.d.ts +81 -0
package/index.cjs
CHANGED
|
@@ -21,29 +21,50 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
DevkitError: () => DevkitError,
|
|
24
|
+
brandFromJson: () => brandFromJson,
|
|
25
|
+
brandPath: () => brandPath,
|
|
24
26
|
collectPartials: () => collectPartials,
|
|
25
27
|
contentHash: () => contentHash,
|
|
26
28
|
contentTypeFor: () => contentTypeFor,
|
|
27
29
|
defaultData: () => defaultData,
|
|
28
30
|
defaultProjectConfig: () => defaultProjectConfig,
|
|
31
|
+
defaultTypesFile: () => defaultTypesFile,
|
|
29
32
|
diagnose: () => diagnose,
|
|
30
33
|
findProject: () => findProject,
|
|
34
|
+
generateTypes: () => generateTypes,
|
|
35
|
+
isIgnored: () => isIgnored,
|
|
31
36
|
listLocalFiles: () => listLocalFiles,
|
|
32
37
|
listTemplateSlugs: () => listTemplateSlugs,
|
|
33
38
|
loadProject: () => loadProject,
|
|
34
39
|
localFilePath: () => localFilePath,
|
|
40
|
+
localTypeSource: () => localTypeSource,
|
|
41
|
+
officeSnapshot: () => officeSnapshot,
|
|
42
|
+
officeVersionPayload: () => officeVersionPayload,
|
|
43
|
+
parseRedactPath: () => parseRedactPath,
|
|
44
|
+
partialContentHash: () => partialContentHash,
|
|
35
45
|
partialResolver: () => partialResolver,
|
|
46
|
+
prettyXml: () => prettyXml,
|
|
36
47
|
previewDocument: () => previewDocument,
|
|
37
48
|
projectAround: () => projectAround,
|
|
49
|
+
readBrand: () => readBrand,
|
|
38
50
|
readState: () => readState,
|
|
39
51
|
readTemplate: () => readTemplate,
|
|
52
|
+
recordSharedPartial: () => recordSharedPartial,
|
|
40
53
|
recordSync: () => recordSync,
|
|
54
|
+
redactData: () => redactData,
|
|
55
|
+
redactString: () => redactString,
|
|
41
56
|
renderContext: () => renderContext,
|
|
42
57
|
renderLocal: () => renderLocal,
|
|
58
|
+
renderOfficeLocal: () => renderOfficeLocal,
|
|
59
|
+
renderedSettings: () => renderedSettings,
|
|
43
60
|
sha256File: () => sha256File,
|
|
61
|
+
sharedPartialNames: () => sharedPartialNames,
|
|
62
|
+
sharedPartialPath: () => sharedPartialPath,
|
|
44
63
|
templateDir: () => templateDir,
|
|
64
|
+
templateFileName: () => templateFileName,
|
|
45
65
|
titleFromSlug: () => titleFromSlug,
|
|
46
66
|
versionPayload: () => versionPayload,
|
|
67
|
+
writeBrand: () => writeBrand,
|
|
47
68
|
writeLocalFile: () => writeLocalFile,
|
|
48
69
|
writePartials: () => writePartials,
|
|
49
70
|
writeProjectConfig: () => writeProjectConfig,
|
|
@@ -71,6 +92,32 @@ var defaultProjectConfig = {
|
|
|
71
92
|
engine: "jinja2",
|
|
72
93
|
ignore: ["**/drafts/**"]
|
|
73
94
|
};
|
|
95
|
+
function globRegExp(pattern) {
|
|
96
|
+
let glob = pattern.trim().replace(/\\/g, "/").replace(/^\.\//, "");
|
|
97
|
+
if (!glob.includes("/")) glob = `**/${glob}`;
|
|
98
|
+
let source = "";
|
|
99
|
+
for (let i = 0; i < glob.length; i++) {
|
|
100
|
+
const ch = glob[i];
|
|
101
|
+
if (ch === "*" && glob[i + 1] === "*") {
|
|
102
|
+
const slash = glob[i + 2] === "/";
|
|
103
|
+
source += slash ? "(?:.*/)?" : ".*";
|
|
104
|
+
i += slash ? 2 : 1;
|
|
105
|
+
} else if (ch === "*") source += "[^/]*";
|
|
106
|
+
else if (ch === "?") source += "[^/]";
|
|
107
|
+
else source += ch.replace(/[.+^${}()|[\]\\]/g, "\\$&");
|
|
108
|
+
}
|
|
109
|
+
return new RegExp(`^${source.replace(/^\//, "")}$`);
|
|
110
|
+
}
|
|
111
|
+
function isIgnored(project, path, folder = false) {
|
|
112
|
+
const patterns = project.config.ignore ?? [];
|
|
113
|
+
if (!patterns.length) return false;
|
|
114
|
+
const rel = (0, import_node_path.relative)(project.root, (0, import_node_path.resolve)(project.root, path)).split(import_node_path.sep).join("/");
|
|
115
|
+
const candidates = folder ? [rel, `${rel}/`, `${rel}/x`] : [rel];
|
|
116
|
+
return patterns.some((p) => {
|
|
117
|
+
const re = globRegExp(p);
|
|
118
|
+
return candidates.some((c) => re.test(c));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
74
121
|
function findProject(cwd = process.cwd()) {
|
|
75
122
|
let dir = (0, import_node_path.resolve)(cwd);
|
|
76
123
|
for (; ; ) {
|
|
@@ -125,6 +172,7 @@ function writeProjectConfig(root, config) {
|
|
|
125
172
|
var import_node_crypto = require("node:crypto");
|
|
126
173
|
var import_node_fs2 = require("node:fs");
|
|
127
174
|
var import_node_path2 = require("node:path");
|
|
175
|
+
var import_engine2 = require("@formfeed/engine");
|
|
128
176
|
|
|
129
177
|
// packages/devkit/src/lib/partials.ts
|
|
130
178
|
var import_engine = require("@formfeed/engine");
|
|
@@ -137,8 +185,10 @@ function includeNames(engine, source) {
|
|
|
137
185
|
}
|
|
138
186
|
function collectPartials(project, engine, sources) {
|
|
139
187
|
const resolve3 = partialResolver(project);
|
|
188
|
+
const sharedNames = sharedPartialNames(project, engine);
|
|
140
189
|
const partials = {};
|
|
141
190
|
const missing = /* @__PURE__ */ new Set();
|
|
191
|
+
const shared = /* @__PURE__ */ new Set();
|
|
142
192
|
const queue = sources.filter((source) => Boolean(source));
|
|
143
193
|
const seen = /* @__PURE__ */ new Set();
|
|
144
194
|
while (queue.length) {
|
|
@@ -146,6 +196,11 @@ function collectPartials(project, engine, sources) {
|
|
|
146
196
|
if (seen.has(name)) continue;
|
|
147
197
|
seen.add(name);
|
|
148
198
|
const source = resolve3(name);
|
|
199
|
+
if (sharedNames.has((0, import_engine.normalisePartialName)(name))) {
|
|
200
|
+
shared.add((0, import_engine.normalisePartialName)(name));
|
|
201
|
+
if (source !== void 0) queue.push(source);
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
149
204
|
if (source === void 0) {
|
|
150
205
|
missing.add(name);
|
|
151
206
|
continue;
|
|
@@ -154,65 +209,86 @@ function collectPartials(project, engine, sources) {
|
|
|
154
209
|
queue.push(source);
|
|
155
210
|
}
|
|
156
211
|
}
|
|
157
|
-
return { partials, missing: [...missing].sort() };
|
|
212
|
+
return { partials, missing: [...missing].sort(), shared: [...shared].sort() };
|
|
158
213
|
}
|
|
159
214
|
|
|
160
215
|
// packages/devkit/src/lib/project.ts
|
|
161
216
|
var readText = (path) => (0, import_node_fs2.existsSync)(path) ? (0, import_node_fs2.readFileSync)(path, "utf8") : null;
|
|
162
217
|
function readJson(path, what) {
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
218
|
+
const text2 = readText(path);
|
|
219
|
+
if (text2 === null) return null;
|
|
165
220
|
try {
|
|
166
|
-
return JSON.parse(
|
|
221
|
+
return JSON.parse(text2);
|
|
167
222
|
} catch (e) {
|
|
168
223
|
throw new DevkitError(`${what} is not valid JSON: ${path} (${e instanceof Error ? e.message : e})`, "validation");
|
|
169
224
|
}
|
|
170
225
|
}
|
|
226
|
+
var templateFileName = (kind) => (0, import_engine2.isOfficeKind)(kind) ? `template.${kind}` : "template.html";
|
|
227
|
+
function sourceFilesIn(dir) {
|
|
228
|
+
return ["template.html", ...import_engine2.officeKinds.map((k) => `template.${k}`)].filter((name) => (0, import_node_fs2.existsSync)((0, import_node_path2.join)(dir, name)));
|
|
229
|
+
}
|
|
171
230
|
function listTemplateSlugs(project) {
|
|
172
231
|
if (!(0, import_node_fs2.existsSync)(project.templatesDir)) return [];
|
|
173
|
-
return (0, import_node_fs2.readdirSync)(project.templatesDir).filter((name) => (
|
|
232
|
+
return (0, import_node_fs2.readdirSync)(project.templatesDir).filter((name) => sourceFilesIn((0, import_node_path2.join)(project.templatesDir, name)).length > 0).sort();
|
|
174
233
|
}
|
|
175
234
|
function templateDir(project, slug) {
|
|
176
235
|
return (0, import_node_path2.join)(project.templatesDir, slug);
|
|
177
236
|
}
|
|
237
|
+
var sha256Of = (bytes) => (0, import_node_crypto.createHash)("sha256").update(bytes).digest("hex");
|
|
238
|
+
function readOfficeFile(dir, name) {
|
|
239
|
+
const path = (0, import_node_path2.join)(dir, name);
|
|
240
|
+
const bytes = new Uint8Array((0, import_node_fs2.readFileSync)(path));
|
|
241
|
+
return { format: (0, import_node_path2.extname)(name).slice(1), path, bytes, sha256: sha256Of(bytes) };
|
|
242
|
+
}
|
|
178
243
|
function readTemplate(project, slug) {
|
|
179
244
|
const dir = templateDir(project, slug);
|
|
180
|
-
const
|
|
181
|
-
if (
|
|
245
|
+
const sources = sourceFilesIn(dir);
|
|
246
|
+
if (sources.length === 0)
|
|
247
|
+
throw new DevkitError(`No template "${slug}" in ${project.templatesDir} (template.html, template.docx or template.pptx missing)`);
|
|
248
|
+
if (sources.length > 1)
|
|
249
|
+
throw new DevkitError(`${dir} holds ${sources.join(" and ")}; a template folder has one of them`, "validation");
|
|
250
|
+
const file = sources[0] === "template.html" ? null : readOfficeFile(dir, sources[0]);
|
|
251
|
+
const html = file ? "" : readText((0, import_node_path2.join)(dir, "template.html")) ?? "";
|
|
182
252
|
const metaFile = readJson((0, import_node_path2.join)(dir, "template.json"), "template.json") ?? {};
|
|
253
|
+
if (file && metaFile.kind && metaFile.kind !== file.format)
|
|
254
|
+
throw new DevkitError(`${slug}: template.json says kind "${metaFile.kind}", but the folder holds ${(0, import_node_path2.basename)(file.path)}`, "validation");
|
|
255
|
+
if (!file && (0, import_engine2.isOfficeKind)(metaFile.kind))
|
|
256
|
+
throw new DevkitError(`${slug}: template.json says kind "${metaFile.kind}", but the folder holds template.html instead of template.${metaFile.kind}`, "validation");
|
|
183
257
|
const meta = {
|
|
184
258
|
name: metaFile.name ?? titleFromSlug(slug),
|
|
185
|
-
kind: metaFile.kind ?? "pdf",
|
|
259
|
+
kind: file?.format ?? metaFile.kind ?? "pdf",
|
|
186
260
|
engine: metaFile.engine ?? project.config.engine,
|
|
187
261
|
description: metaFile.description ?? null,
|
|
188
262
|
tags: metaFile.tags ?? []
|
|
189
263
|
};
|
|
190
264
|
const settings = { ...readJson((0, import_node_path2.join)(dir, "settings.json"), "settings.json") ?? {} };
|
|
191
|
-
const header = readText((0, import_node_path2.join)(dir, "header.html"));
|
|
192
|
-
const footer = readText((0, import_node_path2.join)(dir, "footer.html"));
|
|
265
|
+
const header = file ? null : readText((0, import_node_path2.join)(dir, "header.html"));
|
|
266
|
+
const footer = file ? null : readText((0, import_node_path2.join)(dir, "footer.html"));
|
|
193
267
|
if (header !== null && header.trim()) settings.header = { ...settings.header ?? {}, html: header };
|
|
194
268
|
if (footer !== null && footer.trim()) settings.footer = { ...settings.footer ?? {}, html: footer };
|
|
195
269
|
const dataSets = {};
|
|
196
270
|
const dataDir = (0, import_node_path2.join)(dir, "data");
|
|
197
271
|
if ((0, import_node_fs2.existsSync)(dataDir)) {
|
|
198
|
-
for (const
|
|
199
|
-
dataSets[(0, import_node_path2.basename)(
|
|
272
|
+
for (const file2 of (0, import_node_fs2.readdirSync)(dataDir).filter((f) => f.endsWith(".json")).sort()) {
|
|
273
|
+
dataSets[(0, import_node_path2.basename)(file2, ".json")] = readJson((0, import_node_path2.join)(dataDir, file2), `data/${file2}`);
|
|
200
274
|
}
|
|
201
275
|
}
|
|
202
|
-
const { partials, missing } = collectPartials(project, meta.engine, [html, settings.header?.html, settings.footer?.html]);
|
|
276
|
+
const { partials, missing, shared } = file ? { partials: {}, missing: [], shared: [] } : collectPartials(project, meta.engine, [html, settings.header?.html, settings.footer?.html]);
|
|
203
277
|
return {
|
|
204
278
|
slug,
|
|
205
279
|
dir,
|
|
206
280
|
meta,
|
|
281
|
+
file,
|
|
207
282
|
html,
|
|
208
|
-
css: readText((0, import_node_path2.join)(dir, "style.css")) ?? "",
|
|
209
|
-
head: readText((0, import_node_path2.join)(dir, "head.html")) ?? "",
|
|
283
|
+
css: file ? "" : readText((0, import_node_path2.join)(dir, "style.css")) ?? "",
|
|
284
|
+
head: file ? "" : readText((0, import_node_path2.join)(dir, "head.html")) ?? "",
|
|
210
285
|
settings,
|
|
211
286
|
dataSets,
|
|
212
287
|
dataSchema: readJson((0, import_node_path2.join)(dir, "schema.json"), "schema.json"),
|
|
213
288
|
i18n: readJson((0, import_node_path2.join)(dir, "i18n.json"), "i18n.json"),
|
|
214
289
|
partials,
|
|
215
|
-
missingPartials: missing
|
|
290
|
+
missingPartials: missing,
|
|
291
|
+
sharedPartials: shared
|
|
216
292
|
};
|
|
217
293
|
}
|
|
218
294
|
function defaultData(tpl, name) {
|
|
@@ -222,18 +298,23 @@ function defaultData(tpl, name) {
|
|
|
222
298
|
throw new DevkitError(`Data set "${name}" not found; available: ${names.join(", ") || "none"}`);
|
|
223
299
|
return chosen ? { name: chosen, data: tpl.dataSets[chosen] } : { name: "empty", data: {} };
|
|
224
300
|
}
|
|
225
|
-
function writeTemplate(project, slug, meta, version) {
|
|
301
|
+
function writeTemplate(project, slug, meta, version, file) {
|
|
226
302
|
const dir = templateDir(project, slug);
|
|
303
|
+
const office = (0, import_engine2.isOfficeKind)(meta.kind);
|
|
304
|
+
if (office && !file) throw new DevkitError(`${slug} is a ${meta.kind} template; its document is needed to write the folder`);
|
|
227
305
|
(0, import_node_fs2.mkdirSync)((0, import_node_path2.join)(dir, "data"), { recursive: true });
|
|
228
306
|
const settings = { ...version.settings ?? {} };
|
|
229
|
-
const header = settings.header?.html ?? "";
|
|
230
|
-
const footer = settings.footer?.html ?? "";
|
|
307
|
+
const header = office ? "" : settings.header?.html ?? "";
|
|
308
|
+
const footer = office ? "" : settings.footer?.html ?? "";
|
|
231
309
|
if (settings.header) settings.header = stripHtml(settings.header);
|
|
232
310
|
if (settings.footer) settings.footer = stripHtml(settings.footer);
|
|
233
311
|
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(dir, "template.json"), JSON.stringify(meta, null, 2) + "\n");
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
312
|
+
for (const name of ["template.html", ...import_engine2.officeKinds.map((k) => `template.${k}`)])
|
|
313
|
+
if (name !== templateFileName(meta.kind) && (0, import_node_fs2.existsSync)((0, import_node_path2.join)(dir, name))) (0, import_node_fs2.rmSync)((0, import_node_path2.join)(dir, name));
|
|
314
|
+
if (office) (0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(dir, templateFileName(meta.kind)), file);
|
|
315
|
+
else (0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(dir, "template.html"), version.html ?? "");
|
|
316
|
+
writeOrRemove((0, import_node_path2.join)(dir, "style.css"), office ? "" : version.css ?? "");
|
|
317
|
+
writeOrRemove((0, import_node_path2.join)(dir, "head.html"), office ? "" : version.head ?? "");
|
|
237
318
|
writeOrRemove((0, import_node_path2.join)(dir, "header.html"), header);
|
|
238
319
|
writeOrRemove((0, import_node_path2.join)(dir, "footer.html"), footer);
|
|
239
320
|
(0, import_node_fs2.writeFileSync)((0, import_node_path2.join)(dir, "settings.json"), JSON.stringify(settings, null, 2) + "\n");
|
|
@@ -268,25 +349,38 @@ function writeOrRemove(path, content) {
|
|
|
268
349
|
if (content) (0, import_node_fs2.writeFileSync)(path, content);
|
|
269
350
|
else if ((0, import_node_fs2.existsSync)(path)) (0, import_node_fs2.rmSync)(path);
|
|
270
351
|
}
|
|
271
|
-
function
|
|
352
|
+
function commonPayload(tpl) {
|
|
272
353
|
const sample = tpl.dataSets["default"] ?? Object.values(tpl.dataSets)[0] ?? {};
|
|
273
354
|
const named = Object.fromEntries(
|
|
274
355
|
Object.entries(tpl.dataSets).filter(([name]) => name !== "default")
|
|
275
356
|
);
|
|
276
357
|
return {
|
|
277
|
-
html: tpl.html,
|
|
278
|
-
css: tpl.css,
|
|
279
|
-
head: tpl.head,
|
|
280
358
|
settings: tpl.settings,
|
|
281
359
|
sample_data: sample ?? {},
|
|
282
360
|
data_sets: named,
|
|
283
361
|
data_schema: tpl.dataSchema,
|
|
284
|
-
i18n: tpl.i18n
|
|
362
|
+
i18n: tpl.i18n
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function officeVersionPayload(tpl) {
|
|
366
|
+
return commonPayload(tpl);
|
|
367
|
+
}
|
|
368
|
+
function versionPayload(tpl) {
|
|
369
|
+
return {
|
|
370
|
+
html: tpl.html,
|
|
371
|
+
css: tpl.css,
|
|
372
|
+
head: tpl.head,
|
|
373
|
+
...commonPayload(tpl),
|
|
285
374
|
// only when the template includes one, so a project without partials sends what it always sent
|
|
286
375
|
...Object.keys(tpl.partials).length ? { partials: tpl.partials } : {}
|
|
287
376
|
};
|
|
288
377
|
}
|
|
289
378
|
function contentHash(tpl) {
|
|
379
|
+
if (tpl.file) {
|
|
380
|
+
const payload2 = officeVersionPayload(tpl);
|
|
381
|
+
const parts2 = [tpl.file.format, tpl.file.sha256, payload2.settings, payload2.sample_data, payload2.data_sets, payload2.data_schema, payload2.i18n];
|
|
382
|
+
return (0, import_node_crypto.createHash)("sha256").update(JSON.stringify(parts2)).digest("hex");
|
|
383
|
+
}
|
|
290
384
|
const payload = versionPayload(tpl);
|
|
291
385
|
const parts = [payload.html, payload.css, payload.head, payload.settings, payload.sample_data, payload.data_sets, payload.data_schema, payload.i18n];
|
|
292
386
|
if (payload.partials) parts.push(payload.partials);
|
|
@@ -307,6 +401,7 @@ function recordSync(project, slug, version, tpl) {
|
|
|
307
401
|
number: version.number,
|
|
308
402
|
status: version.status,
|
|
309
403
|
contentHash: contentHash(tpl),
|
|
404
|
+
...tpl.file ? { fileSha256: tpl.file.sha256 } : {},
|
|
310
405
|
syncedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
311
406
|
};
|
|
312
407
|
writeState(project, state);
|
|
@@ -315,24 +410,85 @@ function titleFromSlug(slug) {
|
|
|
315
410
|
return slug.replace(/[-_]+/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
|
|
316
411
|
}
|
|
317
412
|
function partialResolver(project) {
|
|
413
|
+
let shared = null;
|
|
318
414
|
return (name) => {
|
|
319
415
|
for (const candidate of [name, `${name}.html`]) {
|
|
320
416
|
const path = (0, import_node_path2.join)(project.partialsDir, candidate);
|
|
321
417
|
if ((0, import_node_fs2.existsSync)(path) && (0, import_node_fs2.statSync)(path).isFile()) return (0, import_node_fs2.readFileSync)(path, "utf8");
|
|
322
418
|
}
|
|
323
|
-
|
|
419
|
+
shared ??= new Set(Object.keys(readState(project).sharedPartials ?? {}));
|
|
420
|
+
const normalised = (0, import_engine2.normalisePartialName)(name);
|
|
421
|
+
if (!shared.has(normalised)) return void 0;
|
|
422
|
+
return readText(sharedPartialPath(project, normalised)) ?? void 0;
|
|
324
423
|
};
|
|
325
424
|
}
|
|
425
|
+
function sharedPartialPath(project, name) {
|
|
426
|
+
return (0, import_node_path2.join)(project.partialsDir, `${name}.html`);
|
|
427
|
+
}
|
|
428
|
+
function sharedPartialNames(project, engine) {
|
|
429
|
+
return new Set(
|
|
430
|
+
Object.entries(readState(project).sharedPartials ?? {}).filter(([, partial]) => partial.engine === engine).map(([name]) => (0, import_engine2.normalisePartialName)(name))
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
var partialContentHash = (source) => (0, import_node_crypto.createHash)("sha256").update(source).digest("hex");
|
|
434
|
+
function recordSharedPartial(project, name, remote, source) {
|
|
435
|
+
const state = readState(project);
|
|
436
|
+
state.sharedPartials = {
|
|
437
|
+
...state.sharedPartials ?? {},
|
|
438
|
+
[name]: { version: remote.version, engine: remote.engine, contentHash: partialContentHash(source), syncedAt: (/* @__PURE__ */ new Date()).toISOString() }
|
|
439
|
+
};
|
|
440
|
+
writeState(project, state);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// packages/devkit/src/lib/brand.ts
|
|
444
|
+
var import_node_fs3 = require("node:fs");
|
|
445
|
+
var import_node_path3 = require("node:path");
|
|
446
|
+
var import_engine3 = require("@formfeed/engine");
|
|
447
|
+
var brandPath = (project) => (0, import_node_path3.join)(project.root, ".formfeed", "brand.json");
|
|
448
|
+
var text = (value) => typeof value === "string" ? value : null;
|
|
449
|
+
var record = (value) => value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
450
|
+
function brandFromJson(json) {
|
|
451
|
+
const body = record(json);
|
|
452
|
+
const empty = (0, import_engine3.emptyBrand)();
|
|
453
|
+
const colors = Object.fromEntries(Object.entries(record(body["colors"])).filter((entry) => typeof entry[1] === "string"));
|
|
454
|
+
const fonts = record(body["fonts"]);
|
|
455
|
+
const logo = record(body["logo"]);
|
|
456
|
+
return {
|
|
457
|
+
version: typeof body["version"] === "number" ? body["version"] : empty.version,
|
|
458
|
+
name: text(body["name"]),
|
|
459
|
+
colors,
|
|
460
|
+
fonts: { heading: text(fonts["heading"]), body: text(fonts["body"]) },
|
|
461
|
+
font_size: text(body["font_size"]),
|
|
462
|
+
logo: { primary: text(logo["primary"]), inverse: text(logo["inverse"]), mark: text(logo["mark"]) },
|
|
463
|
+
legal_footer: text(body["legal_footer"])
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
function readBrand(project) {
|
|
467
|
+
const path = brandPath(project);
|
|
468
|
+
if (!(0, import_node_fs3.existsSync)(path)) return (0, import_engine3.emptyBrand)();
|
|
469
|
+
try {
|
|
470
|
+
return brandFromJson(JSON.parse((0, import_node_fs3.readFileSync)(path, "utf8")));
|
|
471
|
+
} catch (e) {
|
|
472
|
+
throw new DevkitError(`.formfeed/brand.json is not valid JSON: ${path} (${e instanceof Error ? e.message : e})`, "validation");
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
function writeBrand(project, brand) {
|
|
476
|
+
(0, import_node_fs3.mkdirSync)((0, import_node_path3.join)(project.root, ".formfeed"), { recursive: true });
|
|
477
|
+
const path = brandPath(project);
|
|
478
|
+
(0, import_node_fs3.writeFileSync)(path, JSON.stringify(brand, null, 2) + "\n");
|
|
479
|
+
return path;
|
|
480
|
+
}
|
|
326
481
|
|
|
327
482
|
// packages/devkit/src/lib/local-render.ts
|
|
328
|
-
var
|
|
483
|
+
var import_engine4 = require("@formfeed/engine");
|
|
329
484
|
function diagnose(tpl, sampleData) {
|
|
330
|
-
|
|
485
|
+
if (tpl.file) return diagnoseOffice(tpl, tpl.file.bytes, sampleData);
|
|
486
|
+
const engine = (0, import_engine4.getEngine)(tpl.meta.engine);
|
|
331
487
|
const diagnostics = [];
|
|
332
488
|
try {
|
|
333
489
|
engine.compile(tpl.html, { name: tpl.slug });
|
|
334
490
|
} catch (e) {
|
|
335
|
-
if (e instanceof
|
|
491
|
+
if (e instanceof import_engine4.EngineSyntaxError) {
|
|
336
492
|
diagnostics.push({
|
|
337
493
|
severity: "error",
|
|
338
494
|
code: "syntax-error",
|
|
@@ -358,45 +514,116 @@ function diagnose(tpl, sampleData) {
|
|
|
358
514
|
});
|
|
359
515
|
return diagnostics;
|
|
360
516
|
}
|
|
361
|
-
|
|
362
|
-
|
|
517
|
+
var nowhere = { start: { line: 1, column: 1 }, end: { line: 1, column: 1 } };
|
|
518
|
+
function diagnoseOffice(tpl, file, sampleData) {
|
|
519
|
+
let analysis;
|
|
520
|
+
try {
|
|
521
|
+
analysis = (0, import_engine4.analyzeOffice)(file, { engine: tpl.meta.engine, sampleData });
|
|
522
|
+
} catch (e) {
|
|
523
|
+
const code = e.code;
|
|
524
|
+
return [{ severity: "error", code: typeof code === "string" ? code : "office-document-invalid", message: e instanceof Error ? e.message : String(e), range: nowhere }];
|
|
525
|
+
}
|
|
526
|
+
return analysis.diagnostics.map((d) => ({
|
|
527
|
+
severity: d.severity,
|
|
528
|
+
code: d.code,
|
|
529
|
+
message: d.text ? `${d.message} (${d.text})` : d.message,
|
|
530
|
+
range: nowhere,
|
|
531
|
+
part: d.part,
|
|
532
|
+
paragraph: d.paragraph
|
|
533
|
+
}));
|
|
534
|
+
}
|
|
535
|
+
async function renderOfficeLocal(project, tpl, data, options = {}) {
|
|
536
|
+
if (!tpl.file) throw new DevkitError(`${tpl.slug} is not a Word or PowerPoint template`, "validation");
|
|
537
|
+
const context = renderContext(project, tpl, options.assetBaseUrl, options.brand);
|
|
538
|
+
if (options.locale) context.locale = options.locale;
|
|
539
|
+
return (0, import_engine4.renderOffice)(tpl.file.bytes, { engine: tpl.meta.engine, data, context, images: options.images, random: options.random });
|
|
540
|
+
}
|
|
541
|
+
function officeSnapshot(filled) {
|
|
542
|
+
return (0, import_engine4.officeTextParts)(filled).map((part) => `--- ${part.name} ---
|
|
543
|
+
${prettyXml(part.xml)}`).join("\n");
|
|
544
|
+
}
|
|
545
|
+
function prettyXml(xml) {
|
|
546
|
+
const lines = [];
|
|
547
|
+
let depth = 0;
|
|
548
|
+
let open = false;
|
|
549
|
+
for (const token of xml.match(/<[^>]*>|[^<]+/g) ?? []) {
|
|
550
|
+
if (token.startsWith("</")) {
|
|
551
|
+
depth = Math.max(0, depth - 1);
|
|
552
|
+
if (open && lines.length) lines[lines.length - 1] += token;
|
|
553
|
+
else lines.push(" ".repeat(depth) + token);
|
|
554
|
+
open = false;
|
|
555
|
+
} else if (token.startsWith("<?") || token.startsWith("<!")) {
|
|
556
|
+
lines.push(" ".repeat(depth) + token);
|
|
557
|
+
open = false;
|
|
558
|
+
} else if (token.startsWith("<")) {
|
|
559
|
+
lines.push(" ".repeat(depth) + token);
|
|
560
|
+
open = !token.endsWith("/>");
|
|
561
|
+
if (open) depth++;
|
|
562
|
+
} else if (token.trim() || open) {
|
|
563
|
+
if (lines.length) lines[lines.length - 1] += token;
|
|
564
|
+
else lines.push(token);
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
return lines.join("\n") + "\n";
|
|
568
|
+
}
|
|
569
|
+
function renderContext(project, tpl, assetBaseUrl, brand) {
|
|
570
|
+
const settings = (0, import_engine4.mergeSettings)(tpl.settings);
|
|
363
571
|
return {
|
|
364
572
|
locale: settings.locale ?? "en",
|
|
365
573
|
timezone: settings.timezone ?? "UTC",
|
|
366
574
|
currency: settings.currency ?? "EUR",
|
|
367
575
|
partials: partialResolver(project),
|
|
368
|
-
helpers: (0,
|
|
369
|
-
limits:
|
|
576
|
+
helpers: (0, import_engine4.defaultHelpers)(),
|
|
577
|
+
limits: import_engine4.defaultLimits,
|
|
370
578
|
i18n: tpl.i18n ?? void 0,
|
|
371
|
-
assetBaseUrl
|
|
579
|
+
assetBaseUrl,
|
|
580
|
+
// the organisation's kit from `formfeed brand pull`, else the empty kit the server uses too
|
|
581
|
+
brand: brand ?? readBrand(project)
|
|
372
582
|
};
|
|
373
583
|
}
|
|
374
584
|
async function renderLocal(project, tpl, data, options) {
|
|
375
|
-
const
|
|
585
|
+
const kind = htmlKind(tpl);
|
|
586
|
+
const ctx = renderContext(project, tpl, options.assetBaseUrl, options.brand);
|
|
376
587
|
if (options.locale) ctx.locale = options.locale;
|
|
377
|
-
return (0,
|
|
378
|
-
{ engine: tpl.meta.engine, html: tpl.html, css: tpl.css, head: tpl.head, settings: (0,
|
|
588
|
+
return (0, import_engine4.renderVersion)(
|
|
589
|
+
{ engine: tpl.meta.engine, html: tpl.html, css: tpl.css, head: tpl.head, settings: (0, import_engine4.mergeSettings)(tpl.settings), kind },
|
|
379
590
|
data,
|
|
380
591
|
ctx,
|
|
381
592
|
options.mode,
|
|
382
593
|
options.vendor ? { vendor: options.vendor } : {}
|
|
383
594
|
);
|
|
384
595
|
}
|
|
596
|
+
function renderedSettings(rendered) {
|
|
597
|
+
const style = rendered.inlineCss ? `<style>${rendered.inlineCss}</style>` : "";
|
|
598
|
+
const part = (settings2, html) => settings2?.html ? { ...settings2, html: `${style}${html ?? ""}` } : settings2;
|
|
599
|
+
const { settings } = rendered;
|
|
600
|
+
return {
|
|
601
|
+
...settings,
|
|
602
|
+
...settings.header ? { header: part(settings.header, rendered.headerHtml) } : {},
|
|
603
|
+
...settings.footer ? { footer: part(settings.footer, rendered.footerHtml) } : {},
|
|
604
|
+
...settings.pdf?.metadata?.title !== void 0 ? { pdf: { ...settings.pdf, metadata: { ...settings.pdf.metadata, title: rendered.title ?? "" } } } : {}
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
function htmlKind(tpl) {
|
|
608
|
+
if ((0, import_engine4.isOfficeKind)(tpl.meta.kind)) throw new DevkitError(`${tpl.slug} is a ${tpl.meta.kind} template; fill it with renderOfficeLocal`, "validation");
|
|
609
|
+
return tpl.meta.kind;
|
|
610
|
+
}
|
|
385
611
|
function previewDocument(tpl, rendered, mode, pagedScriptUrl) {
|
|
612
|
+
const kind = htmlKind(tpl);
|
|
386
613
|
const draft = {
|
|
387
614
|
document: rendered.document,
|
|
388
615
|
headerHtml: rendered.headerHtml,
|
|
389
616
|
footerHtml: rendered.footerHtml,
|
|
390
617
|
settings: rendered.settings,
|
|
391
|
-
kind
|
|
618
|
+
kind
|
|
392
619
|
};
|
|
393
|
-
return mode === "paged" &&
|
|
620
|
+
return mode === "paged" && kind === "pdf" ? (0, import_engine4.pagedDocument)(draft, { pagedScriptUrl }) : (0, import_engine4.flowDocument)(draft);
|
|
394
621
|
}
|
|
395
622
|
|
|
396
623
|
// packages/devkit/src/lib/files.ts
|
|
397
624
|
var import_node_crypto2 = require("node:crypto");
|
|
398
|
-
var
|
|
399
|
-
var
|
|
625
|
+
var import_node_fs4 = require("node:fs");
|
|
626
|
+
var import_node_path4 = require("node:path");
|
|
400
627
|
|
|
401
628
|
// packages/api-types/src/lib/assets.ts
|
|
402
629
|
var SEGMENT = "[A-Za-z0-9][A-Za-z0-9._-]*";
|
|
@@ -428,70 +655,646 @@ function sha256File(bytes) {
|
|
|
428
655
|
}
|
|
429
656
|
function listLocalFiles(project) {
|
|
430
657
|
const listing = { files: [], skipped: [] };
|
|
431
|
-
if (!(0,
|
|
432
|
-
const
|
|
433
|
-
for (const entry of (0,
|
|
658
|
+
if (!(0, import_node_fs4.existsSync)(project.filesDir)) return listing;
|
|
659
|
+
const walk2 = (dir) => {
|
|
660
|
+
for (const entry of (0, import_node_fs4.readdirSync)(dir, { withFileTypes: true })) {
|
|
434
661
|
if (entry.name.startsWith(".")) continue;
|
|
435
|
-
const path = (0,
|
|
662
|
+
const path = (0, import_node_path4.join)(dir, entry.name);
|
|
436
663
|
if (entry.isDirectory()) {
|
|
437
|
-
|
|
664
|
+
walk2(path);
|
|
438
665
|
continue;
|
|
439
666
|
}
|
|
440
667
|
if (!entry.isFile()) continue;
|
|
441
|
-
const name = (0,
|
|
442
|
-
const size = (0,
|
|
668
|
+
const name = (0, import_node_path4.relative)(project.filesDir, path).split(import_node_path4.sep).join("/");
|
|
669
|
+
const size = (0, import_node_fs4.statSync)(path).size;
|
|
443
670
|
const contentType = contentTypeFor(name);
|
|
444
671
|
const reason = !isAssetName(name) ? "the name has characters a library name cannot have (letters, digits, dot, dash, underscore)" : !contentType ? "not an image or a PDF" : size === 0 ? "empty" : size > ASSET_MAX_BYTES ? `larger than ${ASSET_MAX_BYTES / 1024 / 1024} MB` : null;
|
|
445
672
|
if (reason) {
|
|
446
673
|
listing.skipped.push({ path, reason });
|
|
447
674
|
continue;
|
|
448
675
|
}
|
|
449
|
-
const bytes = (0,
|
|
676
|
+
const bytes = (0, import_node_fs4.readFileSync)(path);
|
|
450
677
|
listing.files.push({ name, path, bytes: size, sha256: sha256File(bytes), contentType });
|
|
451
678
|
}
|
|
452
679
|
};
|
|
453
|
-
|
|
680
|
+
walk2(project.filesDir);
|
|
454
681
|
listing.files.sort((a, b) => a.name.localeCompare(b.name));
|
|
455
682
|
return listing;
|
|
456
683
|
}
|
|
457
684
|
function localFilePath(project, name) {
|
|
458
685
|
if (!isAssetName(name)) throw new DevkitError(`"${name}" is not a library file name`);
|
|
459
|
-
const path = (0,
|
|
460
|
-
if (!path.startsWith((0,
|
|
686
|
+
const path = (0, import_node_path4.resolve)(project.filesDir, ...name.split("/"));
|
|
687
|
+
if (!path.startsWith((0, import_node_path4.resolve)(project.filesDir) + import_node_path4.sep)) throw new DevkitError(`"${name}" is not inside the files folder`);
|
|
461
688
|
return path;
|
|
462
689
|
}
|
|
463
690
|
function writeLocalFile(project, name, bytes) {
|
|
464
691
|
const path = localFilePath(project, name);
|
|
465
|
-
(0,
|
|
466
|
-
(0,
|
|
692
|
+
(0, import_node_fs4.mkdirSync)((0, import_node_path4.dirname)(path), { recursive: true });
|
|
693
|
+
(0, import_node_fs4.writeFileSync)(path, bytes);
|
|
467
694
|
return path;
|
|
468
695
|
}
|
|
696
|
+
|
|
697
|
+
// packages/devkit/src/lib/redact.ts
|
|
698
|
+
function redactData(value, paths) {
|
|
699
|
+
if (!paths || paths.length === 0) return redactAll(value);
|
|
700
|
+
return walk(value, paths.map(parseRedactPath));
|
|
701
|
+
}
|
|
702
|
+
function redactString(text2) {
|
|
703
|
+
let out = "";
|
|
704
|
+
for (const ch of text2) {
|
|
705
|
+
if (/\p{Lu}/u.test(ch)) out += "A";
|
|
706
|
+
else if (/\p{L}/u.test(ch)) out += "a";
|
|
707
|
+
else if (/\p{N}/u.test(ch)) out += "0";
|
|
708
|
+
else out += ch;
|
|
709
|
+
}
|
|
710
|
+
return out;
|
|
711
|
+
}
|
|
712
|
+
function parseRedactPath(path) {
|
|
713
|
+
const segments = [];
|
|
714
|
+
for (const part of path.trim().split(".")) {
|
|
715
|
+
if (part === "") continue;
|
|
716
|
+
const match = /^([^[\]]*)((?:\[[^\]]*\])*)$/.exec(part);
|
|
717
|
+
if (!match) {
|
|
718
|
+
segments.push(part);
|
|
719
|
+
continue;
|
|
720
|
+
}
|
|
721
|
+
if (match[1]) segments.push(match[1]);
|
|
722
|
+
for (const index of match[2].match(/\[[^\]]*\]/g) ?? []) {
|
|
723
|
+
const inner = index.slice(1, -1).trim();
|
|
724
|
+
segments.push(inner === "" || inner === "*" ? "*" : inner);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
return segments;
|
|
728
|
+
}
|
|
729
|
+
function redactAll(value) {
|
|
730
|
+
if (typeof value === "string") return redactString(value);
|
|
731
|
+
if (Array.isArray(value)) return value.map(redactAll);
|
|
732
|
+
if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, redactAll(v)]));
|
|
733
|
+
return value;
|
|
734
|
+
}
|
|
735
|
+
function walk(value, patterns) {
|
|
736
|
+
if (patterns.some((p) => p.length === 0)) return redactAll(value);
|
|
737
|
+
if (Array.isArray(value)) {
|
|
738
|
+
return value.map((item, index) => {
|
|
739
|
+
const next = advance(patterns, String(index));
|
|
740
|
+
return next.length ? walk(item, next) : item;
|
|
741
|
+
});
|
|
742
|
+
}
|
|
743
|
+
if (isRecord(value)) {
|
|
744
|
+
return Object.fromEntries(
|
|
745
|
+
Object.entries(value).map(([key, item]) => {
|
|
746
|
+
const next = advance(patterns, key);
|
|
747
|
+
return [key, next.length ? walk(item, next) : item];
|
|
748
|
+
})
|
|
749
|
+
);
|
|
750
|
+
}
|
|
751
|
+
return value;
|
|
752
|
+
}
|
|
753
|
+
function advance(patterns, key) {
|
|
754
|
+
return patterns.filter((p) => p[0] === "*" || p[0] === key).map((p) => p.slice(1));
|
|
755
|
+
}
|
|
756
|
+
function isRecord(value) {
|
|
757
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// packages/devkit/src/lib/types/index.ts
|
|
761
|
+
var import_engine5 = require("@formfeed/engine");
|
|
762
|
+
|
|
763
|
+
// packages/devkit/src/lib/types/model.ts
|
|
764
|
+
var isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
765
|
+
var UNSUPPORTED = [
|
|
766
|
+
"not",
|
|
767
|
+
"if",
|
|
768
|
+
"then",
|
|
769
|
+
"else",
|
|
770
|
+
"patternProperties",
|
|
771
|
+
"prefixItems",
|
|
772
|
+
"contains",
|
|
773
|
+
"dependentSchemas",
|
|
774
|
+
"dependentRequired",
|
|
775
|
+
"unevaluatedProperties",
|
|
776
|
+
"unevaluatedItems",
|
|
777
|
+
"propertyNames"
|
|
778
|
+
];
|
|
779
|
+
function pascalCase(text2) {
|
|
780
|
+
const words = text2.replace(/([a-z0-9])([A-Z])/g, "$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean);
|
|
781
|
+
const joined = words.map((w) => w[0].toUpperCase() + w.slice(1)).join("");
|
|
782
|
+
if (!joined) return "Field";
|
|
783
|
+
return /^[0-9]/.test(joined) ? `T${joined}` : joined;
|
|
784
|
+
}
|
|
785
|
+
function singular(word) {
|
|
786
|
+
if (/ies$/i.test(word) && word.length > 3) return word.slice(0, -3) + "y";
|
|
787
|
+
if (/(ss|x|ch|sh)es$/i.test(word)) return word.slice(0, -2);
|
|
788
|
+
if (/[^su]s$/i.test(word)) return word.slice(0, -1);
|
|
789
|
+
return null;
|
|
790
|
+
}
|
|
791
|
+
var NameRegistry = class {
|
|
792
|
+
used = /* @__PURE__ */ new Set();
|
|
793
|
+
constructor(reserved = []) {
|
|
794
|
+
for (const name of reserved) this.used.add(name);
|
|
795
|
+
}
|
|
796
|
+
claim(wanted) {
|
|
797
|
+
let name = wanted;
|
|
798
|
+
for (let i = 2; this.used.has(name); i++) name = `${wanted}${i}`;
|
|
799
|
+
this.used.add(name);
|
|
800
|
+
return name;
|
|
801
|
+
}
|
|
802
|
+
};
|
|
803
|
+
function relaxRequired(schema) {
|
|
804
|
+
if (Array.isArray(schema)) return schema.map(relaxRequired);
|
|
805
|
+
if (!isObject(schema)) return schema;
|
|
806
|
+
const out = {};
|
|
807
|
+
for (const [key, value] of Object.entries(schema)) {
|
|
808
|
+
if (key === "required") continue;
|
|
809
|
+
if (key === "properties" || key === "$defs" || key === "definitions") {
|
|
810
|
+
out[key] = isObject(value) ? Object.fromEntries(Object.entries(value).map(([k, v]) => [k, relaxRequired(v)])) : value;
|
|
811
|
+
} else if (key === "examples" || key === "enum" || key === "const" || key === "default") {
|
|
812
|
+
out[key] = value;
|
|
813
|
+
} else {
|
|
814
|
+
out[key] = relaxRequired(value);
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return out;
|
|
818
|
+
}
|
|
819
|
+
function docOf(schema) {
|
|
820
|
+
const lines = [];
|
|
821
|
+
if (typeof schema["title"] === "string" && schema["title"] !== schema["description"]) lines.push(schema["title"]);
|
|
822
|
+
if (typeof schema["description"] === "string") lines.push(...schema["description"].split("\n"));
|
|
823
|
+
if (typeof schema["format"] === "string") lines.push(`Format: ${schema["format"]}`);
|
|
824
|
+
const unsupported = UNSUPPORTED.filter((k) => k in schema);
|
|
825
|
+
if (unsupported.length) lines.push(`Not expressed in the types: ${unsupported.join(", ")}`);
|
|
826
|
+
if (Array.isArray(schema["examples"]) && schema["examples"].length)
|
|
827
|
+
lines.push(`@example ${JSON.stringify(schema["examples"][0])}`);
|
|
828
|
+
if (schema["deprecated"] === true) lines.push("@deprecated");
|
|
829
|
+
return lines;
|
|
830
|
+
}
|
|
831
|
+
var Builder = class {
|
|
832
|
+
constructor(root, base, names) {
|
|
833
|
+
this.root = root;
|
|
834
|
+
this.base = base;
|
|
835
|
+
this.names = names;
|
|
836
|
+
}
|
|
837
|
+
objects = [];
|
|
838
|
+
defs = /* @__PURE__ */ new Map();
|
|
839
|
+
rootName = "";
|
|
840
|
+
/**
|
|
841
|
+
* A plain object schema becomes the interface `rootName` itself; anything else (a nullable object,
|
|
842
|
+
* a union, an array) becomes an alias of that name, so the root never names two things.
|
|
843
|
+
*/
|
|
844
|
+
build(rootName) {
|
|
845
|
+
this.rootName = rootName;
|
|
846
|
+
const s = this.root;
|
|
847
|
+
const plainObject = (s["type"] === "object" || s["type"] === void 0 && isObject(s["properties"])) && isObject(s["properties"]) && Object.keys(s["properties"]).length > 0 && !["$ref", "const", "enum", "anyOf", "oneOf", "allOf"].some((k) => k in s);
|
|
848
|
+
return this.convert(s, null, null, plainObject ? rootName : void 0);
|
|
849
|
+
}
|
|
850
|
+
/**
|
|
851
|
+
* `owner` + `key` name the objects found below: property `customer` of `Invoice` is
|
|
852
|
+
* `InvoiceCustomer`, an element of `items` is `InvoiceItem`. `exact` forces a name (the root).
|
|
853
|
+
*/
|
|
854
|
+
convert(raw, owner, key, exact, depth = 0) {
|
|
855
|
+
if (raw === true || raw === void 0) return { kind: "unknown" };
|
|
856
|
+
if (raw === false) return { kind: "never" };
|
|
857
|
+
if (!isObject(raw) || depth > 48) return { kind: "unknown" };
|
|
858
|
+
const schema = raw;
|
|
859
|
+
if (typeof schema["$ref"] === "string") return this.reference(schema["$ref"]);
|
|
860
|
+
if ("const" in schema) return literal(schema["const"]);
|
|
861
|
+
if (Array.isArray(schema["enum"])) {
|
|
862
|
+
const members2 = schema["enum"].map(literal);
|
|
863
|
+
return members2.length === 1 ? members2[0] : { kind: "union", members: members2 };
|
|
864
|
+
}
|
|
865
|
+
for (const combinator of ["anyOf", "oneOf"]) {
|
|
866
|
+
const list = schema[combinator];
|
|
867
|
+
if (Array.isArray(list) && list.length) {
|
|
868
|
+
const members2 = list.map(
|
|
869
|
+
(option, i) => this.convert(option, owner, key === null ? `Option${i + 1}` : `${key}Option${i + 1}`, void 0, depth + 1)
|
|
870
|
+
);
|
|
871
|
+
return union(members2);
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
if (Array.isArray(schema["allOf"]) && schema["allOf"].length) {
|
|
875
|
+
const members2 = schema["allOf"].map(
|
|
876
|
+
(part, i) => this.convert(part, owner, key === null ? `Part${i + 1}` : `${key}Part${i + 1}`, void 0, depth + 1)
|
|
877
|
+
);
|
|
878
|
+
return members2.length === 1 ? members2[0] : { kind: "intersection", members: members2 };
|
|
879
|
+
}
|
|
880
|
+
const declared = typeof schema["type"] === "string" ? [schema["type"]] : Array.isArray(schema["type"]) ? schema["type"] : null;
|
|
881
|
+
const types = declared ?? (isObject(schema["properties"]) || isObject(schema["additionalProperties"]) ? ["object"] : "items" in schema ? ["array"] : null);
|
|
882
|
+
if (!types) {
|
|
883
|
+
const unsupported = UNSUPPORTED.filter((k) => k in schema);
|
|
884
|
+
return unsupported.length ? { kind: "unknown", note: unsupported.join(", ") } : { kind: "unknown" };
|
|
885
|
+
}
|
|
886
|
+
const members = types.map((type) => {
|
|
887
|
+
switch (type) {
|
|
888
|
+
case "object":
|
|
889
|
+
return this.object(schema, owner, key, exact, depth);
|
|
890
|
+
case "array":
|
|
891
|
+
return { kind: "array", items: this.convert(schema["items"], owner, itemKey(key), void 0, depth + 1) };
|
|
892
|
+
case "string":
|
|
893
|
+
case "number":
|
|
894
|
+
case "integer":
|
|
895
|
+
case "boolean":
|
|
896
|
+
case "null":
|
|
897
|
+
return { kind: "primitive", name: type };
|
|
898
|
+
default:
|
|
899
|
+
return { kind: "unknown" };
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
return union(members);
|
|
903
|
+
}
|
|
904
|
+
object(schema, owner, key, exact, depth) {
|
|
905
|
+
const properties = isObject(schema["properties"]) ? schema["properties"] : {};
|
|
906
|
+
const additional = schema["additionalProperties"];
|
|
907
|
+
const extra = isObject(additional) ? this.convert(additional, owner, `${key ?? ""}Value`, void 0, depth + 1) : null;
|
|
908
|
+
if (Object.keys(properties).length === 0) return { kind: "record", values: extra ?? { kind: "unknown" } };
|
|
909
|
+
const name = exact ?? this.names.claim(`${owner ?? this.base}${key === null ? "" : pascalCase(key)}`);
|
|
910
|
+
const target = { name, doc: docOf(schema), fields: [], extra };
|
|
911
|
+
this.objects.push(target);
|
|
912
|
+
const required = new Set(Array.isArray(schema["required"]) ? schema["required"] : []);
|
|
913
|
+
const nestedOwner = name === this.rootName ? this.base : name;
|
|
914
|
+
for (const [field, child] of Object.entries(properties)) {
|
|
915
|
+
target.fields.push({
|
|
916
|
+
key: field,
|
|
917
|
+
type: this.convert(child, nestedOwner, field, void 0, depth + 1),
|
|
918
|
+
required: required.has(field),
|
|
919
|
+
doc: isObject(child) ? docOf(child) : []
|
|
920
|
+
});
|
|
921
|
+
}
|
|
922
|
+
return { kind: "ref", name };
|
|
923
|
+
}
|
|
924
|
+
reference(ref) {
|
|
925
|
+
const match = /^#\/(\$defs|definitions)\/(.+)$/.exec(ref);
|
|
926
|
+
if (!match) return { kind: "unknown", note: `$ref ${ref}` };
|
|
927
|
+
const known = this.defs.get(ref);
|
|
928
|
+
if (known) return { kind: "ref", name: known };
|
|
929
|
+
const section = this.root[match[1]];
|
|
930
|
+
const target = isObject(section) ? section[decodePointer(match[2])] : void 0;
|
|
931
|
+
if (!isObject(target)) return { kind: "unknown", note: `$ref ${ref}` };
|
|
932
|
+
const hasProperties = isObject(target["properties"]) && Object.keys(target["properties"]).length > 0;
|
|
933
|
+
if (!hasProperties) return this.convert(target, this.base, decodePointer(match[2]));
|
|
934
|
+
const name = this.names.claim(`${this.base}${pascalCase(decodePointer(match[2]))}`);
|
|
935
|
+
this.defs.set(ref, name);
|
|
936
|
+
this.convert(target, null, null, name);
|
|
937
|
+
return { kind: "ref", name };
|
|
938
|
+
}
|
|
939
|
+
};
|
|
940
|
+
var decodePointer = (part) => part.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
941
|
+
function itemKey(key) {
|
|
942
|
+
if (key === null) return "Item";
|
|
943
|
+
return singular(key) ?? `${key}Item`;
|
|
944
|
+
}
|
|
945
|
+
function literal(value) {
|
|
946
|
+
if (value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean")
|
|
947
|
+
return { kind: "literal", value };
|
|
948
|
+
return { kind: "unknown" };
|
|
949
|
+
}
|
|
950
|
+
function union(members) {
|
|
951
|
+
const flat = [];
|
|
952
|
+
for (const member of members) {
|
|
953
|
+
if (member.kind === "union") flat.push(...member.members);
|
|
954
|
+
else flat.push(member);
|
|
955
|
+
}
|
|
956
|
+
if (flat.some((m) => m.kind === "unknown")) return { kind: "unknown" };
|
|
957
|
+
const unique = flat.filter((m, i) => flat.findIndex((n) => JSON.stringify(n) === JSON.stringify(m)) === i);
|
|
958
|
+
return unique.length === 1 ? unique[0] : { kind: "union", members: unique };
|
|
959
|
+
}
|
|
960
|
+
function buildTypes(sources, reserved = []) {
|
|
961
|
+
const names = new NameRegistry(reserved);
|
|
962
|
+
return [...sources].sort((a, b) => a.slug.localeCompare(b.slug)).map((source) => {
|
|
963
|
+
const schema = source.origin === "inferred" ? relaxRequired(source.schema) : source.schema;
|
|
964
|
+
const base = pascalCase(source.slug);
|
|
965
|
+
const rootName = names.claim(`${base}Data`);
|
|
966
|
+
const builder = new Builder(schema, base, names);
|
|
967
|
+
const root = builder.build(rootName);
|
|
968
|
+
return {
|
|
969
|
+
slug: source.slug,
|
|
970
|
+
origin: source.origin,
|
|
971
|
+
version: source.version ?? null,
|
|
972
|
+
channel: source.channel ?? null,
|
|
973
|
+
rootName,
|
|
974
|
+
root,
|
|
975
|
+
objects: builder.objects
|
|
976
|
+
};
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
function originNote(t) {
|
|
980
|
+
switch (t.origin) {
|
|
981
|
+
case "local":
|
|
982
|
+
return "schema.json of the project";
|
|
983
|
+
case "stored":
|
|
984
|
+
return `stored schema of version ${t.version ?? "?"}${t.channel ? ` (channel ${t.channel})` : ""}`;
|
|
985
|
+
case "inferred":
|
|
986
|
+
return "inferred from sample data, every field optional: store a schema to make this a contract";
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// packages/devkit/src/lib/types/emit-python.ts
|
|
991
|
+
var KEYWORDS = /* @__PURE__ */ new Set([
|
|
992
|
+
"False",
|
|
993
|
+
"None",
|
|
994
|
+
"True",
|
|
995
|
+
"and",
|
|
996
|
+
"as",
|
|
997
|
+
"assert",
|
|
998
|
+
"async",
|
|
999
|
+
"await",
|
|
1000
|
+
"break",
|
|
1001
|
+
"class",
|
|
1002
|
+
"continue",
|
|
1003
|
+
"def",
|
|
1004
|
+
"del",
|
|
1005
|
+
"elif",
|
|
1006
|
+
"else",
|
|
1007
|
+
"except",
|
|
1008
|
+
"finally",
|
|
1009
|
+
"for",
|
|
1010
|
+
"from",
|
|
1011
|
+
"global",
|
|
1012
|
+
"if",
|
|
1013
|
+
"import",
|
|
1014
|
+
"in",
|
|
1015
|
+
"is",
|
|
1016
|
+
"lambda",
|
|
1017
|
+
"nonlocal",
|
|
1018
|
+
"not",
|
|
1019
|
+
"or",
|
|
1020
|
+
"pass",
|
|
1021
|
+
"raise",
|
|
1022
|
+
"return",
|
|
1023
|
+
"try",
|
|
1024
|
+
"while",
|
|
1025
|
+
"with",
|
|
1026
|
+
"yield"
|
|
1027
|
+
]);
|
|
1028
|
+
var isIdentifier = (key) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key) && !KEYWORDS.has(key);
|
|
1029
|
+
function methodName(slug) {
|
|
1030
|
+
const name = slug.replace(/[^A-Za-z0-9_]+/g, "_").toLowerCase();
|
|
1031
|
+
return isIdentifier(name) ? name : `${/^[0-9]/.test(name) ? "t_" : ""}${name}${KEYWORDS.has(name) ? "_" : ""}`;
|
|
1032
|
+
}
|
|
1033
|
+
function expression(node, quoteRefs) {
|
|
1034
|
+
switch (node.kind) {
|
|
1035
|
+
case "unknown":
|
|
1036
|
+
return "Any";
|
|
1037
|
+
case "never":
|
|
1038
|
+
return "Never";
|
|
1039
|
+
case "primitive":
|
|
1040
|
+
return { string: "str", number: "float", integer: "int", boolean: "bool", null: "None" }[node.name];
|
|
1041
|
+
case "literal":
|
|
1042
|
+
return node.value === null ? "None" : `Literal[${pythonLiteral(node.value)}]`;
|
|
1043
|
+
case "array":
|
|
1044
|
+
return `list[${expression(node.items, quoteRefs)}]`;
|
|
1045
|
+
case "record":
|
|
1046
|
+
return `dict[str, ${expression(node.values, quoteRefs)}]`;
|
|
1047
|
+
case "ref":
|
|
1048
|
+
return quoteRefs ? JSON.stringify(node.name) : node.name;
|
|
1049
|
+
case "union": {
|
|
1050
|
+
const literals = node.members.filter(
|
|
1051
|
+
(m) => m.kind === "literal" && m.value !== null
|
|
1052
|
+
);
|
|
1053
|
+
const rest = node.members.filter((m) => !(m.kind === "literal" && m.value !== null));
|
|
1054
|
+
const parts = [
|
|
1055
|
+
...literals.length ? [`Literal[${literals.map((l) => pythonLiteral(l.value)).join(", ")}]`] : [],
|
|
1056
|
+
...rest.map((m) => expression(m, quoteRefs))
|
|
1057
|
+
];
|
|
1058
|
+
if (quoteRefs && rest.some((m) => m.kind === "ref")) return `Union[${parts.join(", ")}]`;
|
|
1059
|
+
return parts.join(" | ");
|
|
1060
|
+
}
|
|
1061
|
+
case "intersection":
|
|
1062
|
+
return expression(node.members[0] ?? { kind: "unknown" }, quoteRefs);
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
function pythonLiteral(value) {
|
|
1066
|
+
if (value === null) return "None";
|
|
1067
|
+
if (typeof value === "boolean") return value ? "True" : "False";
|
|
1068
|
+
return JSON.stringify(value);
|
|
1069
|
+
}
|
|
1070
|
+
function docstring(lines, indent) {
|
|
1071
|
+
if (lines.length === 0) return [];
|
|
1072
|
+
const safe = lines.map((line) => line.replace(/\\/g, "\\\\").replace(/"""/g, '\\"\\"\\"'));
|
|
1073
|
+
if (safe.length === 1) return [`${indent}"""${safe[0]}"""`];
|
|
1074
|
+
return [`${indent}"""${safe[0]}`, ...safe.slice(1).map((line) => `${indent}${line}`.trimEnd()), `${indent}"""`];
|
|
1075
|
+
}
|
|
1076
|
+
function emitObject(object) {
|
|
1077
|
+
const functional = object.fields.some((f) => !isIdentifier(f.key));
|
|
1078
|
+
const field = (type, required, quote) => {
|
|
1079
|
+
const text2 = expression(type, quote);
|
|
1080
|
+
return required ? text2 : `NotRequired[${text2}]`;
|
|
1081
|
+
};
|
|
1082
|
+
if (functional) {
|
|
1083
|
+
const out2 = [`${object.name} = TypedDict(`, ` ${JSON.stringify(object.name)},`, " {"];
|
|
1084
|
+
for (const f of object.fields) out2.push(` ${JSON.stringify(f.key)}: ${field(f.type, f.required, true)},`);
|
|
1085
|
+
out2.push(" },", ")");
|
|
1086
|
+
if (object.doc.length) out2.unshift(...object.doc.map((line) => `# ${line}`));
|
|
1087
|
+
return out2;
|
|
1088
|
+
}
|
|
1089
|
+
const out = [`class ${object.name}(TypedDict):`, ...docstring(object.doc, " ")];
|
|
1090
|
+
for (const f of object.fields) {
|
|
1091
|
+
out.push(` ${f.key}: ${field(f.type, f.required, false)}`);
|
|
1092
|
+
out.push(...docstring(f.doc, " "));
|
|
1093
|
+
}
|
|
1094
|
+
if (object.fields.length === 0) out.push(" pass");
|
|
1095
|
+
return out;
|
|
1096
|
+
}
|
|
1097
|
+
function emitPython(templates) {
|
|
1098
|
+
const lines = [
|
|
1099
|
+
"# Generated by `formfeed types`. Do not edit: run it again when a template schema changes.",
|
|
1100
|
+
"#",
|
|
1101
|
+
...templates.map((t) => `# ${t.slug}: ${originNote(t)}`),
|
|
1102
|
+
"",
|
|
1103
|
+
"from __future__ import annotations",
|
|
1104
|
+
"",
|
|
1105
|
+
"from typing import Any, Literal, Union, cast",
|
|
1106
|
+
"",
|
|
1107
|
+
"from typing_extensions import Never, NotRequired, TypedDict",
|
|
1108
|
+
"",
|
|
1109
|
+
"from formfeed import AsyncFormfeed, Formfeed, Render",
|
|
1110
|
+
"",
|
|
1111
|
+
"__all__ = [",
|
|
1112
|
+
...templates.map((t) => ` ${JSON.stringify(t.rootName)},`),
|
|
1113
|
+
' "TypedRenders",',
|
|
1114
|
+
' "AsyncTypedRenders",',
|
|
1115
|
+
' "typed",',
|
|
1116
|
+
' "typed_async",',
|
|
1117
|
+
"]"
|
|
1118
|
+
];
|
|
1119
|
+
for (const template of templates) {
|
|
1120
|
+
lines.push("", "", `# --- ${template.slug}`);
|
|
1121
|
+
for (const object of [...template.objects].reverse()) lines.push("", ...emitObject(object));
|
|
1122
|
+
const isClass = template.root.kind === "ref" && template.root.name === template.rootName;
|
|
1123
|
+
if (!isClass) lines.push("", `${template.rootName} = ${expression(template.root, true)}`);
|
|
1124
|
+
}
|
|
1125
|
+
const wrapper = (name, isAsync) => {
|
|
1126
|
+
const example = templates[0] ? methodName(templates[0].slug) : "invoice";
|
|
1127
|
+
const out = [
|
|
1128
|
+
"",
|
|
1129
|
+
"",
|
|
1130
|
+
`class ${name}:`,
|
|
1131
|
+
` """Renders with \`data\` checked per template: \`${isAsync ? "await typed_async(client)" : "typed(client)"}.${example}(data, **options)\`."""`,
|
|
1132
|
+
"",
|
|
1133
|
+
` def __init__(self, client: ${isAsync ? "AsyncFormfeed" : "Formfeed"}) -> None:`,
|
|
1134
|
+
" self._renders: Any = client.renders"
|
|
1135
|
+
];
|
|
1136
|
+
for (const t of templates) {
|
|
1137
|
+
out.push(
|
|
1138
|
+
"",
|
|
1139
|
+
` ${isAsync ? "async " : ""}def ${methodName(t.slug)}(self, data: ${t.rootName}, **options: Any) -> Render:`,
|
|
1140
|
+
` return cast(Render, ${isAsync ? "await " : ""}self._renders.create(template=${JSON.stringify(t.slug)}, data=data, **options))`
|
|
1141
|
+
);
|
|
1142
|
+
}
|
|
1143
|
+
return out;
|
|
1144
|
+
};
|
|
1145
|
+
lines.push(...wrapper("TypedRenders", false), ...wrapper("AsyncTypedRenders", true));
|
|
1146
|
+
lines.push(
|
|
1147
|
+
"",
|
|
1148
|
+
"",
|
|
1149
|
+
"def typed(client: Formfeed) -> TypedRenders:",
|
|
1150
|
+
" return TypedRenders(client)",
|
|
1151
|
+
"",
|
|
1152
|
+
"",
|
|
1153
|
+
"def typed_async(client: AsyncFormfeed) -> AsyncTypedRenders:",
|
|
1154
|
+
" return AsyncTypedRenders(client)"
|
|
1155
|
+
);
|
|
1156
|
+
return lines.join("\n") + "\n";
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
// packages/devkit/src/lib/types/emit-ts.ts
|
|
1160
|
+
var identifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
1161
|
+
var quoteKey = (key) => identifier.test(key) ? key : JSON.stringify(key);
|
|
1162
|
+
function expression2(node, nested = false) {
|
|
1163
|
+
switch (node.kind) {
|
|
1164
|
+
case "unknown":
|
|
1165
|
+
return "unknown";
|
|
1166
|
+
case "never":
|
|
1167
|
+
return "never";
|
|
1168
|
+
case "primitive":
|
|
1169
|
+
return node.name === "integer" ? "number" : node.name;
|
|
1170
|
+
case "literal":
|
|
1171
|
+
return JSON.stringify(node.value);
|
|
1172
|
+
case "array": {
|
|
1173
|
+
const inner = expression2(node.items, true);
|
|
1174
|
+
return /[|&]/.test(inner) ? `Array<${expression2(node.items)}>` : `${inner}[]`;
|
|
1175
|
+
}
|
|
1176
|
+
case "record":
|
|
1177
|
+
return `Record<string, ${expression2(node.values)}>`;
|
|
1178
|
+
case "ref":
|
|
1179
|
+
return node.name;
|
|
1180
|
+
case "union": {
|
|
1181
|
+
const text2 = node.members.map((m) => expression2(m, true)).join(" | ");
|
|
1182
|
+
return nested ? `(${text2})` : text2;
|
|
1183
|
+
}
|
|
1184
|
+
case "intersection": {
|
|
1185
|
+
const text2 = node.members.map((m) => expression2(m, true)).join(" & ");
|
|
1186
|
+
return nested ? `(${text2})` : text2;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
function docBlock(lines, indent) {
|
|
1191
|
+
if (lines.length === 0) return [];
|
|
1192
|
+
const safe = lines.map((line) => line.replace(/\*\//g, "*\\/"));
|
|
1193
|
+
if (safe.length === 1) return [`${indent}/** ${safe[0]} */`];
|
|
1194
|
+
return [`${indent}/**`, ...safe.map((line) => `${indent} * ${line}`.trimEnd()), `${indent} */`];
|
|
1195
|
+
}
|
|
1196
|
+
function emitObject2(object) {
|
|
1197
|
+
const out = [...docBlock(object.doc, ""), `export interface ${object.name} {`];
|
|
1198
|
+
for (const field of object.fields) {
|
|
1199
|
+
out.push(...docBlock(field.doc, " "));
|
|
1200
|
+
out.push(` ${quoteKey(field.key)}${field.required ? "" : "?"}: ${expression2(field.type)};`);
|
|
1201
|
+
}
|
|
1202
|
+
if (object.extra) {
|
|
1203
|
+
out.push(` /** Further fields: ${expression2(object.extra).replace(/\*\//g, "*\\/")} */`);
|
|
1204
|
+
out.push(" [key: string]: unknown;");
|
|
1205
|
+
}
|
|
1206
|
+
out.push("}");
|
|
1207
|
+
return out;
|
|
1208
|
+
}
|
|
1209
|
+
function emitTypeScript(templates, options = {}) {
|
|
1210
|
+
const sdk = options.sdkModule ?? "@formfeed/sdk";
|
|
1211
|
+
const lines = [
|
|
1212
|
+
"// Generated by `formfeed types`. Do not edit: run it again when a template schema changes.",
|
|
1213
|
+
"//",
|
|
1214
|
+
...templates.map((t) => `// ${t.slug}: ${originNote(t)}`),
|
|
1215
|
+
"",
|
|
1216
|
+
`declare module ${JSON.stringify(sdk)} {`,
|
|
1217
|
+
" interface FormfeedTemplates {",
|
|
1218
|
+
...templates.map((t) => ` ${JSON.stringify(t.slug)}: ${t.rootName};`),
|
|
1219
|
+
" }",
|
|
1220
|
+
"}"
|
|
1221
|
+
];
|
|
1222
|
+
for (const template of templates) {
|
|
1223
|
+
lines.push("", `// --- ${template.slug}`);
|
|
1224
|
+
const isInterface = template.root.kind === "ref" && template.root.name === template.rootName;
|
|
1225
|
+
if (!isInterface) lines.push("", `export type ${template.rootName} = ${expression2(template.root)};`);
|
|
1226
|
+
for (const object of template.objects) lines.push("", ...emitObject2(object));
|
|
1227
|
+
}
|
|
1228
|
+
if (templates.length === 0) lines.push("", "export {};");
|
|
1229
|
+
return lines.join("\n") + "\n";
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
// packages/devkit/src/lib/types/index.ts
|
|
1233
|
+
var defaultTypesFile = {
|
|
1234
|
+
ts: "formfeed.d.ts",
|
|
1235
|
+
python: "formfeed_templates.py"
|
|
1236
|
+
};
|
|
1237
|
+
function localTypeSource(tpl) {
|
|
1238
|
+
if (tpl.dataSchema) return { slug: tpl.slug, schema: tpl.dataSchema, origin: "local" };
|
|
1239
|
+
const sets = Object.values(tpl.dataSets);
|
|
1240
|
+
return {
|
|
1241
|
+
slug: tpl.slug,
|
|
1242
|
+
schema: (0, import_engine5.inferSchemaFromDataSets)(sets.length ? sets : [{}]),
|
|
1243
|
+
origin: "inferred"
|
|
1244
|
+
};
|
|
1245
|
+
}
|
|
1246
|
+
function generateTypes(sources, language, options = {}) {
|
|
1247
|
+
const reserved = language === "python" ? ["TypedRenders", "AsyncTypedRenders", "Formfeed", "AsyncFormfeed", "Render", "Any", "Literal", "Union", "Never", "NotRequired", "TypedDict"] : ["FormfeedTemplates"];
|
|
1248
|
+
const model = buildTypes(sources, reserved);
|
|
1249
|
+
return language === "python" ? emitPython(model) : emitTypeScript(model, options);
|
|
1250
|
+
}
|
|
469
1251
|
// Annotate the CommonJS export names for ESM import in node:
|
|
470
1252
|
0 && (module.exports = {
|
|
471
1253
|
DevkitError,
|
|
1254
|
+
brandFromJson,
|
|
1255
|
+
brandPath,
|
|
472
1256
|
collectPartials,
|
|
473
1257
|
contentHash,
|
|
474
1258
|
contentTypeFor,
|
|
475
1259
|
defaultData,
|
|
476
1260
|
defaultProjectConfig,
|
|
1261
|
+
defaultTypesFile,
|
|
477
1262
|
diagnose,
|
|
478
1263
|
findProject,
|
|
1264
|
+
generateTypes,
|
|
1265
|
+
isIgnored,
|
|
479
1266
|
listLocalFiles,
|
|
480
1267
|
listTemplateSlugs,
|
|
481
1268
|
loadProject,
|
|
482
1269
|
localFilePath,
|
|
1270
|
+
localTypeSource,
|
|
1271
|
+
officeSnapshot,
|
|
1272
|
+
officeVersionPayload,
|
|
1273
|
+
parseRedactPath,
|
|
1274
|
+
partialContentHash,
|
|
483
1275
|
partialResolver,
|
|
1276
|
+
prettyXml,
|
|
484
1277
|
previewDocument,
|
|
485
1278
|
projectAround,
|
|
1279
|
+
readBrand,
|
|
486
1280
|
readState,
|
|
487
1281
|
readTemplate,
|
|
1282
|
+
recordSharedPartial,
|
|
488
1283
|
recordSync,
|
|
1284
|
+
redactData,
|
|
1285
|
+
redactString,
|
|
489
1286
|
renderContext,
|
|
490
1287
|
renderLocal,
|
|
1288
|
+
renderOfficeLocal,
|
|
1289
|
+
renderedSettings,
|
|
491
1290
|
sha256File,
|
|
1291
|
+
sharedPartialNames,
|
|
1292
|
+
sharedPartialPath,
|
|
492
1293
|
templateDir,
|
|
1294
|
+
templateFileName,
|
|
493
1295
|
titleFromSlug,
|
|
494
1296
|
versionPayload,
|
|
1297
|
+
writeBrand,
|
|
495
1298
|
writeLocalFile,
|
|
496
1299
|
writePartials,
|
|
497
1300
|
writeProjectConfig,
|