@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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@formfeed/devkit",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "Formfeed template folders, project config and local rendering, shared by the CLI and @formfeed/testing.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,8 +30,8 @@
30
30
  ],
31
31
  "sideEffects": false,
32
32
  "dependencies": {
33
- "@formfeed/engine": "0.2.0",
34
- "@formfeed/sdk": "0.2.0"
33
+ "@formfeed/engine": "0.3.1",
34
+ "@formfeed/sdk": "0.3.1"
35
35
  },
36
36
  "keywords": [
37
37
  "formfeed",
package/types/index.d.cts CHANGED
@@ -1,11 +1,16 @@
1
1
  export { DevkitError } from './lib/errors.cjs';
2
- export { defaultProjectConfig, findProject, loadProject, projectAround, writeProjectConfig } from './lib/project-config.cjs';
2
+ export { defaultProjectConfig, findProject, isIgnored, loadProject, projectAround, writeProjectConfig } from './lib/project-config.cjs';
3
3
  export type { Project, ProjectConfig } from './lib/project-config.cjs';
4
- export { contentHash, defaultData, listTemplateSlugs, partialResolver, readState, readTemplate, recordSync, templateDir, titleFromSlug, versionPayload, writePartials, writeState, writeTemplate, } from './lib/project.cjs';
5
- export type { LocalTemplate, ProjectState, TemplateMeta, TemplateState } from './lib/project.cjs';
4
+ export { contentHash, defaultData, listTemplateSlugs, officeVersionPayload, partialContentHash, partialResolver, readState, readTemplate, recordSharedPartial, recordSync, sharedPartialNames, sharedPartialPath, templateDir, templateFileName, titleFromSlug, versionPayload, writePartials, writeState, writeTemplate, } from './lib/project.cjs';
5
+ export type { LocalOfficeFile, LocalTemplate, ProjectState, SharedPartialState, TemplateMeta, TemplateState } from './lib/project.cjs';
6
+ export { brandFromJson, brandPath, readBrand, writeBrand } from './lib/brand.cjs';
6
7
  export { collectPartials } from './lib/partials.cjs';
7
8
  export type { CollectedPartials } from './lib/partials.cjs';
8
- export { diagnose, previewDocument, renderContext, renderLocal } from './lib/local-render.cjs';
9
+ export { diagnose, officeSnapshot, prettyXml, previewDocument, renderContext, renderLocal, renderOfficeLocal, renderedSettings } from './lib/local-render.cjs';
10
+ export type { LocalDiagnostic, OfficeLocalOptions } from './lib/local-render.cjs';
9
11
  export { contentTypeFor, listLocalFiles, localFilePath, sha256File, writeLocalFile } from './lib/files.cjs';
10
12
  export type { LocalFile, LocalFileListing } from './lib/files.cjs';
11
13
  export type { LocalRenderOptions } from './lib/local-render.cjs';
14
+ export { parseRedactPath, redactData, redactString } from './lib/redact.cjs';
15
+ export { defaultTypesFile, generateTypes, localTypeSource } from './lib/types/index.cjs';
16
+ export type { SchemaOrigin, TypeScriptOptions, TypeSource, TypesLanguage } from './lib/types/index.cjs';
package/types/index.d.ts CHANGED
@@ -1,11 +1,16 @@
1
1
  export { DevkitError } from './lib/errors.js';
2
- export { defaultProjectConfig, findProject, loadProject, projectAround, writeProjectConfig } from './lib/project-config.js';
2
+ export { defaultProjectConfig, findProject, isIgnored, loadProject, projectAround, writeProjectConfig } from './lib/project-config.js';
3
3
  export type { Project, ProjectConfig } from './lib/project-config.js';
4
- export { contentHash, defaultData, listTemplateSlugs, partialResolver, readState, readTemplate, recordSync, templateDir, titleFromSlug, versionPayload, writePartials, writeState, writeTemplate, } from './lib/project.js';
5
- export type { LocalTemplate, ProjectState, TemplateMeta, TemplateState } from './lib/project.js';
4
+ export { contentHash, defaultData, listTemplateSlugs, officeVersionPayload, partialContentHash, partialResolver, readState, readTemplate, recordSharedPartial, recordSync, sharedPartialNames, sharedPartialPath, templateDir, templateFileName, titleFromSlug, versionPayload, writePartials, writeState, writeTemplate, } from './lib/project.js';
5
+ export type { LocalOfficeFile, LocalTemplate, ProjectState, SharedPartialState, TemplateMeta, TemplateState } from './lib/project.js';
6
+ export { brandFromJson, brandPath, readBrand, writeBrand } from './lib/brand.js';
6
7
  export { collectPartials } from './lib/partials.js';
7
8
  export type { CollectedPartials } from './lib/partials.js';
8
- export { diagnose, previewDocument, renderContext, renderLocal } from './lib/local-render.js';
9
+ export { diagnose, officeSnapshot, prettyXml, previewDocument, renderContext, renderLocal, renderOfficeLocal, renderedSettings } from './lib/local-render.js';
10
+ export type { LocalDiagnostic, OfficeLocalOptions } from './lib/local-render.js';
9
11
  export { contentTypeFor, listLocalFiles, localFilePath, sha256File, writeLocalFile } from './lib/files.js';
10
12
  export type { LocalFile, LocalFileListing } from './lib/files.js';
11
13
  export type { LocalRenderOptions } from './lib/local-render.js';
14
+ export { parseRedactPath, redactData, redactString } from './lib/redact.js';
15
+ export { defaultTypesFile, generateTypes, localTypeSource } from './lib/types/index.js';
16
+ export type { SchemaOrigin, TypeScriptOptions, TypeSource, TypesLanguage } from './lib/types/index.js';
@@ -0,0 +1,14 @@
1
+ import { type BrandContext } from '@formfeed/engine';
2
+ import type { Project } from './project-config.cjs';
3
+ /**
4
+ * The organisation's brand kit as `formfeed brand pull` stores it (spec 18 §8): the `GET /brand`
5
+ * response, kept whole in `.formfeed/brand.json` so page defaults and the timestamp survive. Local
6
+ * renders take the template's `brand` from it; without the file they get the empty kit, which is
7
+ * also what an organisation that has set nothing renders with.
8
+ */
9
+ export declare const brandPath: (project: Project) => string;
10
+ /** The `brand` a template sees, from a stored `GET /brand` body; unknown or malformed fields fall back to the empty kit. */
11
+ export declare function brandFromJson(json: unknown): BrandContext;
12
+ export declare function readBrand(project: Project): BrandContext;
13
+ /** Stores a `GET /brand` body; returns the file path. */
14
+ export declare function writeBrand(project: Project, brand: unknown): string;
@@ -0,0 +1,14 @@
1
+ import { type BrandContext } from '@formfeed/engine';
2
+ import type { Project } from './project-config.js';
3
+ /**
4
+ * The organisation's brand kit as `formfeed brand pull` stores it (spec 18 §8): the `GET /brand`
5
+ * response, kept whole in `.formfeed/brand.json` so page defaults and the timestamp survive. Local
6
+ * renders take the template's `brand` from it; without the file they get the empty kit, which is
7
+ * also what an organisation that has set nothing renders with.
8
+ */
9
+ export declare const brandPath: (project: Project) => string;
10
+ /** The `brand` a template sees, from a stored `GET /brand` body; unknown or malformed fields fall back to the empty kit. */
11
+ export declare function brandFromJson(json: unknown): BrandContext;
12
+ export declare function readBrand(project: Project): BrandContext;
13
+ /** Stores a `GET /brand` body; returns the file path. */
14
+ export declare function writeBrand(project: Project, brand: unknown): string;
@@ -1,22 +1,58 @@
1
- import { type AssembleVendor, type Diagnostic, type RenderContext, type RenderedDocument } from '@formfeed/engine';
1
+ import { type AssembleVendor, type BrandContext, type Diagnostic, type OfficeImageHost, type OfficeRenderResult, type RenderContext, type RenderedDocument } from '@formfeed/engine';
2
2
  import type { Project } from './project-config.cjs';
3
3
  import { type LocalTemplate } from './project.cjs';
4
+ /**
5
+ * A finding of `diagnose`. Word and PowerPoint findings name the part and paragraph instead of a line
6
+ * (their `range` is 1:1), because a line of the extracted text means nothing in the document.
7
+ */
8
+ export type LocalDiagnostic = Diagnostic & {
9
+ part?: string;
10
+ paragraph?: number;
11
+ };
4
12
  /** The same diagnostics the editor shows: analysis of the body against the sample data plus compile errors. */
5
- export declare function diagnose(tpl: LocalTemplate, sampleData: unknown): Diagnostic[];
13
+ export declare function diagnose(tpl: LocalTemplate, sampleData: unknown): LocalDiagnostic[];
14
+ export interface OfficeLocalOptions {
15
+ locale?: string;
16
+ /** Where `asset()` resolves; the workspace library's CDN base for a document that leaves the machine. */
17
+ assetBaseUrl?: string;
18
+ brand?: BrandContext;
19
+ /** Loads and draws pictures and codes; without one, only PNG and JPEG data URLs are placed. */
20
+ images?: OfficeImageHost;
21
+ /** For snapshots: the source of the engine's placeholder nonce, so repeated runs fill alike. */
22
+ random?: () => number;
23
+ }
24
+ /** Fills a Word or PowerPoint template with the shared engine, as the render-worker does. */
25
+ export declare function renderOfficeLocal(project: Project, tpl: LocalTemplate, data: unknown, options?: OfficeLocalOptions): Promise<OfficeRenderResult>;
26
+ /**
27
+ * The filled parts of a Word or PowerPoint document as readable text for snapshots: each part's XML,
28
+ * one element per line and indented, text kept beside its element, under a `--- <part> ---` line.
29
+ */
30
+ export declare function officeSnapshot(filled: Uint8Array): string;
31
+ /** Indents XML one element per line; text and the closing tag stay on the line of their element. */
32
+ export declare function prettyXml(xml: string): string;
6
33
  /**
7
34
  * `assetBaseUrl` is where `asset()` and relative image paths resolve: the dev server's `/files`
8
35
  * locally, the workspace library's CDN base when the document goes to the API. Without it the
9
36
  * references stay relative, which keeps snapshots independent of any host.
10
37
  */
11
- export declare function renderContext(project: Project, tpl: LocalTemplate, assetBaseUrl?: string): RenderContext;
38
+ export declare function renderContext(project: Project, tpl: LocalTemplate, assetBaseUrl?: string, brand?: BrandContext): RenderContext;
12
39
  export interface LocalRenderOptions {
13
40
  mode: 'preview' | 'print';
14
41
  locale?: string;
15
42
  vendor?: AssembleVendor;
16
43
  /** Where `asset()` and relative image paths resolve (see `renderContext`). */
17
44
  assetBaseUrl?: string;
45
+ /** The template's `brand`; `.formfeed/brand.json` (`readBrand`) when left out. */
46
+ brand?: BrandContext;
18
47
  }
19
48
  /** Assembles the complete document with the shared engine; no browser involved. */
20
49
  export declare function renderLocal(project: Project, tpl: LocalTemplate, data: unknown, options: LocalRenderOptions): Promise<RenderedDocument>;
50
+ /**
51
+ * The settings to send with a locally rendered document as a raw-HTML render. The API uses
52
+ * `header.html`, `footer.html` and the PDF title of such a render verbatim, so they must be the
53
+ * filled parts, not the template's source; the inline CSS travels with them, because Chromium loads
54
+ * no stylesheet in its header and footer templates.
55
+ */
56
+ export declare function renderedSettings(rendered: RenderedDocument): Record<string, unknown>;
21
57
  /** Wraps a preview render for the screen the way the editor does. */
22
58
  export declare function previewDocument(tpl: LocalTemplate, rendered: RenderedDocument, mode: 'flow' | 'paged', pagedScriptUrl: string): string;
@@ -1,22 +1,58 @@
1
- import { type AssembleVendor, type Diagnostic, type RenderContext, type RenderedDocument } from '@formfeed/engine';
1
+ import { type AssembleVendor, type BrandContext, type Diagnostic, type OfficeImageHost, type OfficeRenderResult, type RenderContext, type RenderedDocument } from '@formfeed/engine';
2
2
  import type { Project } from './project-config.js';
3
3
  import { type LocalTemplate } from './project.js';
4
+ /**
5
+ * A finding of `diagnose`. Word and PowerPoint findings name the part and paragraph instead of a line
6
+ * (their `range` is 1:1), because a line of the extracted text means nothing in the document.
7
+ */
8
+ export type LocalDiagnostic = Diagnostic & {
9
+ part?: string;
10
+ paragraph?: number;
11
+ };
4
12
  /** The same diagnostics the editor shows: analysis of the body against the sample data plus compile errors. */
5
- export declare function diagnose(tpl: LocalTemplate, sampleData: unknown): Diagnostic[];
13
+ export declare function diagnose(tpl: LocalTemplate, sampleData: unknown): LocalDiagnostic[];
14
+ export interface OfficeLocalOptions {
15
+ locale?: string;
16
+ /** Where `asset()` resolves; the workspace library's CDN base for a document that leaves the machine. */
17
+ assetBaseUrl?: string;
18
+ brand?: BrandContext;
19
+ /** Loads and draws pictures and codes; without one, only PNG and JPEG data URLs are placed. */
20
+ images?: OfficeImageHost;
21
+ /** For snapshots: the source of the engine's placeholder nonce, so repeated runs fill alike. */
22
+ random?: () => number;
23
+ }
24
+ /** Fills a Word or PowerPoint template with the shared engine, as the render-worker does. */
25
+ export declare function renderOfficeLocal(project: Project, tpl: LocalTemplate, data: unknown, options?: OfficeLocalOptions): Promise<OfficeRenderResult>;
26
+ /**
27
+ * The filled parts of a Word or PowerPoint document as readable text for snapshots: each part's XML,
28
+ * one element per line and indented, text kept beside its element, under a `--- <part> ---` line.
29
+ */
30
+ export declare function officeSnapshot(filled: Uint8Array): string;
31
+ /** Indents XML one element per line; text and the closing tag stay on the line of their element. */
32
+ export declare function prettyXml(xml: string): string;
6
33
  /**
7
34
  * `assetBaseUrl` is where `asset()` and relative image paths resolve: the dev server's `/files`
8
35
  * locally, the workspace library's CDN base when the document goes to the API. Without it the
9
36
  * references stay relative, which keeps snapshots independent of any host.
10
37
  */
11
- export declare function renderContext(project: Project, tpl: LocalTemplate, assetBaseUrl?: string): RenderContext;
38
+ export declare function renderContext(project: Project, tpl: LocalTemplate, assetBaseUrl?: string, brand?: BrandContext): RenderContext;
12
39
  export interface LocalRenderOptions {
13
40
  mode: 'preview' | 'print';
14
41
  locale?: string;
15
42
  vendor?: AssembleVendor;
16
43
  /** Where `asset()` and relative image paths resolve (see `renderContext`). */
17
44
  assetBaseUrl?: string;
45
+ /** The template's `brand`; `.formfeed/brand.json` (`readBrand`) when left out. */
46
+ brand?: BrandContext;
18
47
  }
19
48
  /** Assembles the complete document with the shared engine; no browser involved. */
20
49
  export declare function renderLocal(project: Project, tpl: LocalTemplate, data: unknown, options: LocalRenderOptions): Promise<RenderedDocument>;
50
+ /**
51
+ * The settings to send with a locally rendered document as a raw-HTML render. The API uses
52
+ * `header.html`, `footer.html` and the PDF title of such a render verbatim, so they must be the
53
+ * filled parts, not the template's source; the inline CSS travels with them, because Chromium loads
54
+ * no stylesheet in its header and footer templates.
55
+ */
56
+ export declare function renderedSettings(rendered: RenderedDocument): Record<string, unknown>;
21
57
  /** Wraps a preview render for the screen the way the editor does. */
22
58
  export declare function previewDocument(tpl: LocalTemplate, rendered: RenderedDocument, mode: 'flow' | 'paged', pagedScriptUrl: string): string;
@@ -13,5 +13,7 @@ export interface CollectedPartials {
13
13
  partials: Record<string, string>;
14
14
  /** Names no file in the partials folder matches. */
15
15
  missing: string[];
16
+ /** Normalised names of included shared partials of the organisation (from the project state). */
17
+ shared: string[];
16
18
  }
17
19
  export declare function collectPartials(project: Project, engine: EngineId, sources: Array<string | undefined>): CollectedPartials;
@@ -13,5 +13,7 @@ export interface CollectedPartials {
13
13
  partials: Record<string, string>;
14
14
  /** Names no file in the partials folder matches. */
15
15
  missing: string[];
16
+ /** Normalised names of included shared partials of the organisation (from the project state). */
17
+ shared: string[];
16
18
  }
17
19
  export declare function collectPartials(project: Project, engine: EngineId, sources: Array<string | undefined>): CollectedPartials;
@@ -9,6 +9,19 @@ export interface ProjectConfig {
9
9
  filesDir: string;
10
10
  engine: EngineId;
11
11
  ignore: string[];
12
+ /** Defaults of `formfeed types` (spec 19 §2.1). */
13
+ types?: {
14
+ lang?: 'ts' | 'python';
15
+ /** Relative to the project root; `formfeed.d.ts` or `formfeed_templates.py` when left out. */
16
+ out?: string;
17
+ /** The module the TypeScript file augments; `@formfeed/sdk`. */
18
+ sdkModule?: string;
19
+ };
20
+ /** Defaults of `formfeed renders pull` (spec 20 §3.3). */
21
+ pull?: {
22
+ /** Data paths redacted on every pull (`customer.*`, `items[].name`); `--redact` paths replace them, `--no-redact` skips them. */
23
+ redact?: string[];
24
+ };
12
25
  }
13
26
  export interface Project {
14
27
  root: string;
@@ -20,6 +33,12 @@ export interface Project {
20
33
  filesDir: string;
21
34
  }
22
35
  export declare const defaultProjectConfig: ProjectConfig;
36
+ /**
37
+ * Whether a path of the project matches one of `ignore`. The path is taken relative to the project
38
+ * root; a folder also matches as `<folder>/` and through the files in it, so `**\/drafts/**` and
39
+ * `templates/wip-*` both catch `templates/wip-offer`.
40
+ */
41
+ export declare function isIgnored(project: Project, path: string, folder?: boolean): boolean;
23
42
  /** Walks up from `cwd` to the nearest `formfeed.json`. */
24
43
  export declare function findProject(cwd?: string): Project | null;
25
44
  export declare function loadProject(configPath: string): Project;
@@ -9,6 +9,19 @@ export interface ProjectConfig {
9
9
  filesDir: string;
10
10
  engine: EngineId;
11
11
  ignore: string[];
12
+ /** Defaults of `formfeed types` (spec 19 §2.1). */
13
+ types?: {
14
+ lang?: 'ts' | 'python';
15
+ /** Relative to the project root; `formfeed.d.ts` or `formfeed_templates.py` when left out. */
16
+ out?: string;
17
+ /** The module the TypeScript file augments; `@formfeed/sdk`. */
18
+ sdkModule?: string;
19
+ };
20
+ /** Defaults of `formfeed renders pull` (spec 20 §3.3). */
21
+ pull?: {
22
+ /** Data paths redacted on every pull (`customer.*`, `items[].name`); `--redact` paths replace them, `--no-redact` skips them. */
23
+ redact?: string[];
24
+ };
12
25
  }
13
26
  export interface Project {
14
27
  root: string;
@@ -20,6 +33,12 @@ export interface Project {
20
33
  filesDir: string;
21
34
  }
22
35
  export declare const defaultProjectConfig: ProjectConfig;
36
+ /**
37
+ * Whether a path of the project matches one of `ignore`. The path is taken relative to the project
38
+ * root; a folder also matches as `<folder>/` and through the files in it, so `**\/drafts/**` and
39
+ * `templates/wip-*` both catch `templates/wip-offer`.
40
+ */
41
+ export declare function isIgnored(project: Project, path: string, folder?: boolean): boolean;
23
42
  /** Walks up from `cwd` to the nearest `formfeed.json`. */
24
43
  export declare function findProject(cwd?: string): Project | null;
25
44
  export declare function loadProject(configPath: string): Project;
@@ -1,4 +1,4 @@
1
- import type { EngineId, TemplateKind, TemplateSettings } from '@formfeed/engine';
1
+ import { type EngineId, type OfficeKind, type TemplateKind, type TemplateSettings } from '@formfeed/engine';
2
2
  import type { TemplateVersion } from '@formfeed/sdk';
3
3
  import type { Project } from './project-config.cjs';
4
4
  /**
@@ -9,15 +9,29 @@ import type { Project } from './project-config.cjs';
9
9
  */
10
10
  export interface TemplateMeta {
11
11
  name: string;
12
- kind: TemplateKind;
12
+ /** An HTML kind, or `docx`/`pptx` for Word and PowerPoint templates. */
13
+ kind: TemplateKind | OfficeKind;
13
14
  engine: EngineId;
14
15
  description?: string | null;
15
16
  tags?: string[];
16
17
  }
18
+ /**
19
+ * The document of a Word or PowerPoint template (spec 22 §7): `template.docx` or `template.pptx`
20
+ * instead of `template.html`. Such a folder has no markup, style, head, header, footer or partials.
21
+ */
22
+ export interface LocalOfficeFile {
23
+ format: OfficeKind;
24
+ path: string;
25
+ bytes: Uint8Array;
26
+ sha256: string;
27
+ }
17
28
  export interface LocalTemplate {
18
29
  slug: string;
19
30
  dir: string;
20
31
  meta: TemplateMeta;
32
+ /** The Word or PowerPoint document; `null` for HTML templates. */
33
+ file: LocalOfficeFile | null;
34
+ /** Empty for Word and PowerPoint templates. */
21
35
  html: string;
22
36
  css: string;
23
37
  head: string;
@@ -31,6 +45,11 @@ export interface LocalTemplate {
31
45
  partials: Record<string, string>;
32
46
  /** Included names without a file; `validate` reports them and `push` refuses them. */
33
47
  missingPartials: string[];
48
+ /**
49
+ * Included names that are shared partials of the organisation (recorded in the state by
50
+ * `formfeed partials pull|push`). They resolve live on the server, so they stay out of `partials`.
51
+ */
52
+ sharedPartials: string[];
34
53
  }
35
54
  export interface TemplateState {
36
55
  /** Checksum of the remote version the folder was pulled from or pushed as. */
@@ -39,11 +58,26 @@ export interface TemplateState {
39
58
  status: string;
40
59
  /** Hash of the local files at that moment; a different hash means local edits. */
41
60
  contentHash: string;
61
+ /** Word and PowerPoint templates: the document's SHA-256, so `push` uploads it only when it changed. */
62
+ fileSha256?: string;
63
+ syncedAt: string;
64
+ }
65
+ /** A shared partial of the organisation as last pulled or pushed (spec 18 §8). */
66
+ export interface SharedPartialState {
67
+ /** The remote version; `partials push` sends it as `base_version`. */
68
+ version: number;
69
+ engine: EngineId;
70
+ /** Hash of the local file at that moment; a different hash means local edits. */
71
+ contentHash: string;
42
72
  syncedAt: string;
43
73
  }
44
74
  export interface ProjectState {
45
75
  templates: Record<string, TemplateState>;
76
+ /** Shared partials by name; they live in `partialsDir` as `<name>.html`. */
77
+ sharedPartials?: Record<string, SharedPartialState>;
46
78
  }
79
+ /** The file a template folder is built around, by kind. */
80
+ export declare const templateFileName: (kind: TemplateKind | string) => string;
47
81
  export declare function listTemplateSlugs(project: Project): string[];
48
82
  export declare function templateDir(project: Project, slug: string): string;
49
83
  export declare function readTemplate(project: Project, slug: string): LocalTemplate;
@@ -52,27 +86,43 @@ export declare function defaultData(tpl: LocalTemplate, name?: string): {
52
86
  name: string;
53
87
  data: unknown;
54
88
  };
89
+ /**
90
+ * Writes a pulled version into the template folder. A Word or PowerPoint template needs its
91
+ * document as `file`; the folder then holds `template.<kind>` and none of the HTML files, so a
92
+ * template whose kind changed leaves nothing behind that would be read as its source.
93
+ */
55
94
  export declare function writeTemplate(project: Project, slug: string, meta: TemplateMeta, version: Pick<TemplateVersion, 'html' | 'css' | 'head' | 'settings' | 'sample_data' | 'data_schema' | 'i18n'> & {
56
95
  data_sets?: Record<string, unknown> | null;
57
96
  partials?: Record<string, string> | null;
58
- }): string;
97
+ }, file?: Uint8Array): string;
59
98
  /**
60
99
  * Writes the partials of a pulled version into the project's folder (`name` or `name.html`, the
61
100
  * two spellings `partialResolver` reads). Returns the files whose content changed, so `pull` can
62
101
  * say that a partial shared with another template was replaced.
63
102
  */
64
103
  export declare function writePartials(project: Project, partials: Record<string, string> | null): string[];
104
+ /**
105
+ * Fields of a push request besides the document: what the API's version create takes for a Word or
106
+ * PowerPoint template. The document itself travels as the multipart `file`.
107
+ */
108
+ export declare function officeVersionPayload(tpl: LocalTemplate): {
109
+ settings: Record<string, unknown>;
110
+ sample_data: Record<string, unknown>;
111
+ data_sets: Record<string, unknown>;
112
+ data_schema: Record<string, unknown> | null;
113
+ i18n: Record<string, Record<string, string>> | null;
114
+ };
65
115
  /** Files of a push request: what the API's TemplateVersionCreate takes. */
66
116
  export declare function versionPayload(tpl: LocalTemplate): {
67
117
  partials?: Record<string, string> | undefined;
68
- html: string;
69
- css: string;
70
- head: string;
71
118
  settings: Record<string, unknown>;
72
119
  sample_data: Record<string, unknown>;
73
120
  data_sets: Record<string, unknown>;
74
121
  data_schema: Record<string, unknown> | null;
75
122
  i18n: Record<string, Record<string, string>> | null;
123
+ html: string;
124
+ css: string;
125
+ head: string;
76
126
  };
77
127
  /** Stable hash of the local files; compared with the state to detect local edits. */
78
128
  export declare function contentHash(tpl: LocalTemplate): string;
@@ -80,5 +130,19 @@ export declare function readState(project: Project): ProjectState;
80
130
  export declare function writeState(project: Project, state: ProjectState): void;
81
131
  export declare function recordSync(project: Project, slug: string, version: Pick<TemplateVersion, 'checksum' | 'number' | 'status'>, tpl: LocalTemplate): void;
82
132
  export declare function titleFromSlug(slug: string): string;
83
- /** Resolves `{% include %}` / partials from the project's partials folder (`name` or `name.html`). */
133
+ /**
134
+ * Resolves `{% include %}` / partials from the project's partials folder (`name` or `name.html`).
135
+ * A name that matches a shared partial of the state only after normalisation (`Letterhead.j2` for
136
+ * `letterhead`) falls back to that partial's file, the way the server matches shared partials.
137
+ */
84
138
  export declare function partialResolver(project: Project): (name: string) => string | undefined;
139
+ /** Where a shared partial lives locally: `<partialsDir>/<name>.html`. */
140
+ export declare function sharedPartialPath(project: Project, name: string): string;
141
+ /** Normalised names of the shared partials recorded for one engine; other engines' partials never resolve. */
142
+ export declare function sharedPartialNames(project: Project, engine: EngineId): Set<string>;
143
+ export declare const partialContentHash: (source: string) => string;
144
+ /** Records a pulled or pushed shared partial with the remote version and the hash of `source`. */
145
+ export declare function recordSharedPartial(project: Project, name: string, remote: {
146
+ version: number;
147
+ engine: EngineId;
148
+ }, source: string): void;
@@ -1,4 +1,4 @@
1
- import type { EngineId, TemplateKind, TemplateSettings } from '@formfeed/engine';
1
+ import { type EngineId, type OfficeKind, type TemplateKind, type TemplateSettings } from '@formfeed/engine';
2
2
  import type { TemplateVersion } from '@formfeed/sdk';
3
3
  import type { Project } from './project-config.js';
4
4
  /**
@@ -9,15 +9,29 @@ import type { Project } from './project-config.js';
9
9
  */
10
10
  export interface TemplateMeta {
11
11
  name: string;
12
- kind: TemplateKind;
12
+ /** An HTML kind, or `docx`/`pptx` for Word and PowerPoint templates. */
13
+ kind: TemplateKind | OfficeKind;
13
14
  engine: EngineId;
14
15
  description?: string | null;
15
16
  tags?: string[];
16
17
  }
18
+ /**
19
+ * The document of a Word or PowerPoint template (spec 22 §7): `template.docx` or `template.pptx`
20
+ * instead of `template.html`. Such a folder has no markup, style, head, header, footer or partials.
21
+ */
22
+ export interface LocalOfficeFile {
23
+ format: OfficeKind;
24
+ path: string;
25
+ bytes: Uint8Array;
26
+ sha256: string;
27
+ }
17
28
  export interface LocalTemplate {
18
29
  slug: string;
19
30
  dir: string;
20
31
  meta: TemplateMeta;
32
+ /** The Word or PowerPoint document; `null` for HTML templates. */
33
+ file: LocalOfficeFile | null;
34
+ /** Empty for Word and PowerPoint templates. */
21
35
  html: string;
22
36
  css: string;
23
37
  head: string;
@@ -31,6 +45,11 @@ export interface LocalTemplate {
31
45
  partials: Record<string, string>;
32
46
  /** Included names without a file; `validate` reports them and `push` refuses them. */
33
47
  missingPartials: string[];
48
+ /**
49
+ * Included names that are shared partials of the organisation (recorded in the state by
50
+ * `formfeed partials pull|push`). They resolve live on the server, so they stay out of `partials`.
51
+ */
52
+ sharedPartials: string[];
34
53
  }
35
54
  export interface TemplateState {
36
55
  /** Checksum of the remote version the folder was pulled from or pushed as. */
@@ -39,11 +58,26 @@ export interface TemplateState {
39
58
  status: string;
40
59
  /** Hash of the local files at that moment; a different hash means local edits. */
41
60
  contentHash: string;
61
+ /** Word and PowerPoint templates: the document's SHA-256, so `push` uploads it only when it changed. */
62
+ fileSha256?: string;
63
+ syncedAt: string;
64
+ }
65
+ /** A shared partial of the organisation as last pulled or pushed (spec 18 §8). */
66
+ export interface SharedPartialState {
67
+ /** The remote version; `partials push` sends it as `base_version`. */
68
+ version: number;
69
+ engine: EngineId;
70
+ /** Hash of the local file at that moment; a different hash means local edits. */
71
+ contentHash: string;
42
72
  syncedAt: string;
43
73
  }
44
74
  export interface ProjectState {
45
75
  templates: Record<string, TemplateState>;
76
+ /** Shared partials by name; they live in `partialsDir` as `<name>.html`. */
77
+ sharedPartials?: Record<string, SharedPartialState>;
46
78
  }
79
+ /** The file a template folder is built around, by kind. */
80
+ export declare const templateFileName: (kind: TemplateKind | string) => string;
47
81
  export declare function listTemplateSlugs(project: Project): string[];
48
82
  export declare function templateDir(project: Project, slug: string): string;
49
83
  export declare function readTemplate(project: Project, slug: string): LocalTemplate;
@@ -52,27 +86,43 @@ export declare function defaultData(tpl: LocalTemplate, name?: string): {
52
86
  name: string;
53
87
  data: unknown;
54
88
  };
89
+ /**
90
+ * Writes a pulled version into the template folder. A Word or PowerPoint template needs its
91
+ * document as `file`; the folder then holds `template.<kind>` and none of the HTML files, so a
92
+ * template whose kind changed leaves nothing behind that would be read as its source.
93
+ */
55
94
  export declare function writeTemplate(project: Project, slug: string, meta: TemplateMeta, version: Pick<TemplateVersion, 'html' | 'css' | 'head' | 'settings' | 'sample_data' | 'data_schema' | 'i18n'> & {
56
95
  data_sets?: Record<string, unknown> | null;
57
96
  partials?: Record<string, string> | null;
58
- }): string;
97
+ }, file?: Uint8Array): string;
59
98
  /**
60
99
  * Writes the partials of a pulled version into the project's folder (`name` or `name.html`, the
61
100
  * two spellings `partialResolver` reads). Returns the files whose content changed, so `pull` can
62
101
  * say that a partial shared with another template was replaced.
63
102
  */
64
103
  export declare function writePartials(project: Project, partials: Record<string, string> | null): string[];
104
+ /**
105
+ * Fields of a push request besides the document: what the API's version create takes for a Word or
106
+ * PowerPoint template. The document itself travels as the multipart `file`.
107
+ */
108
+ export declare function officeVersionPayload(tpl: LocalTemplate): {
109
+ settings: Record<string, unknown>;
110
+ sample_data: Record<string, unknown>;
111
+ data_sets: Record<string, unknown>;
112
+ data_schema: Record<string, unknown> | null;
113
+ i18n: Record<string, Record<string, string>> | null;
114
+ };
65
115
  /** Files of a push request: what the API's TemplateVersionCreate takes. */
66
116
  export declare function versionPayload(tpl: LocalTemplate): {
67
117
  partials?: Record<string, string> | undefined;
68
- html: string;
69
- css: string;
70
- head: string;
71
118
  settings: Record<string, unknown>;
72
119
  sample_data: Record<string, unknown>;
73
120
  data_sets: Record<string, unknown>;
74
121
  data_schema: Record<string, unknown> | null;
75
122
  i18n: Record<string, Record<string, string>> | null;
123
+ html: string;
124
+ css: string;
125
+ head: string;
76
126
  };
77
127
  /** Stable hash of the local files; compared with the state to detect local edits. */
78
128
  export declare function contentHash(tpl: LocalTemplate): string;
@@ -80,5 +130,19 @@ export declare function readState(project: Project): ProjectState;
80
130
  export declare function writeState(project: Project, state: ProjectState): void;
81
131
  export declare function recordSync(project: Project, slug: string, version: Pick<TemplateVersion, 'checksum' | 'number' | 'status'>, tpl: LocalTemplate): void;
82
132
  export declare function titleFromSlug(slug: string): string;
83
- /** Resolves `{% include %}` / partials from the project's partials folder (`name` or `name.html`). */
133
+ /**
134
+ * Resolves `{% include %}` / partials from the project's partials folder (`name` or `name.html`).
135
+ * A name that matches a shared partial of the state only after normalisation (`Letterhead.j2` for
136
+ * `letterhead`) falls back to that partial's file, the way the server matches shared partials.
137
+ */
84
138
  export declare function partialResolver(project: Project): (name: string) => string | undefined;
139
+ /** Where a shared partial lives locally: `<partialsDir>/<name>.html`. */
140
+ export declare function sharedPartialPath(project: Project, name: string): string;
141
+ /** Normalised names of the shared partials recorded for one engine; other engines' partials never resolve. */
142
+ export declare function sharedPartialNames(project: Project, engine: EngineId): Set<string>;
143
+ export declare const partialContentHash: (source: string) => string;
144
+ /** Records a pulled or pushed shared partial with the remote version and the hash of `source`. */
145
+ export declare function recordSharedPartial(project: Project, name: string, remote: {
146
+ version: number;
147
+ engine: EngineId;
148
+ }, source: string): void;
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shape-keeping redaction of render data (spec 20 §3.3), for `formfeed renders pull --redact`.
3
+ *
4
+ * Strings become placeholders of the same length that keep the character classes: upper-case
5
+ * letters become `A`, other letters `a`, digits `0`, and everything else (spaces, `@`, `.`, `-`)
6
+ * stays, so `jane@mail.test` becomes `aaaa@aaaa.aaaa` and `DE00 0000` becomes `AA00 0000`. Numbers,
7
+ * booleans and null are kept: they are usually what made the render go wrong, and they rarely
8
+ * identify a person. Arrays and objects keep their keys and lengths.
9
+ *
10
+ * Paths select what is redacted, from the root of the data: dotted segments (`customer.email`),
11
+ * `*` for any one key or array item (`customer.*`), and `[]` for every item of an array
12
+ * (`items[].name`, the same as `items.*.name`). A path that ends on an object or array redacts
13
+ * everything below it. Without paths (or with an empty list) the whole data set is redacted.
14
+ */
15
+ export declare function redactData<T>(value: T, paths?: readonly string[]): T;
16
+ /** A placeholder of the same length with the character classes of `text` kept. */
17
+ export declare function redactString(text: string): string;
18
+ /** `items[].name` → `['items', '*', 'name']`; `lines[0].qty` → `['lines', '0', 'qty']`. */
19
+ export declare function parseRedactPath(path: string): string[];
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Shape-keeping redaction of render data (spec 20 §3.3), for `formfeed renders pull --redact`.
3
+ *
4
+ * Strings become placeholders of the same length that keep the character classes: upper-case
5
+ * letters become `A`, other letters `a`, digits `0`, and everything else (spaces, `@`, `.`, `-`)
6
+ * stays, so `jane@mail.test` becomes `aaaa@aaaa.aaaa` and `DE00 0000` becomes `AA00 0000`. Numbers,
7
+ * booleans and null are kept: they are usually what made the render go wrong, and they rarely
8
+ * identify a person. Arrays and objects keep their keys and lengths.
9
+ *
10
+ * Paths select what is redacted, from the root of the data: dotted segments (`customer.email`),
11
+ * `*` for any one key or array item (`customer.*`), and `[]` for every item of an array
12
+ * (`items[].name`, the same as `items.*.name`). A path that ends on an object or array redacts
13
+ * everything below it. Without paths (or with an empty list) the whole data set is redacted.
14
+ */
15
+ export declare function redactData<T>(value: T, paths?: readonly string[]): T;
16
+ /** A placeholder of the same length with the character classes of `text` kept. */
17
+ export declare function redactString(text: string): string;
18
+ /** `items[].name` → `['items', '*', 'name']`; `lines[0].qty` → `['lines', '0', 'qty']`. */
19
+ export declare function parseRedactPath(path: string): string[];
@@ -0,0 +1,6 @@
1
+ import { type TemplateTypes } from './model.cjs';
2
+ /**
3
+ * `formfeed_templates.py`: a TypedDict per template and `typed(client)`, whose `renders.create`
4
+ * overloads check `data` for the known slugs and fall back to the plain signature otherwise.
5
+ */
6
+ export declare function emitPython(templates: TemplateTypes[]): string;