@openpolicy/vite 0.0.2 → 0.0.5

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/dist/index.d.ts CHANGED
@@ -1,6 +1,11 @@
1
- export declare function openPolicy(_options?: {
2
- formats?: Array<"jsx" | "pdf" | "markdown">;
3
- }): {
4
- name: string;
5
- };
1
+ import type { Plugin } from "vite";
2
+ import type { OutputFormat } from "@openpolicy/core";
3
+ export interface OpenPolicyOptions {
4
+ config?: string;
5
+ formats?: OutputFormat[];
6
+ outDir?: string;
7
+ }
8
+ export declare function writeScaffold(configPath: string): Promise<void>;
9
+ export declare function generatePolicies(configPath: string, outDir: string, formats: OutputFormat[]): Promise<void>;
10
+ export declare function openPolicy(options?: OpenPolicyOptions): Plugin;
6
11
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,QAAQ,CAAC,EAAE;IACrC,OAAO,CAAC,EAAE,KAAK,CAAC,KAAK,GAAG,KAAK,GAAG,UAAU,CAAC,CAAC;CAC5C;;EAEA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AACnC,OAAO,KAAK,EAAE,YAAY,EAAuB,MAAM,kBAAkB,CAAC;AAK1E,MAAM,WAAW,iBAAiB;IACjC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAC;IACzB,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AA0BD,wBAAsB,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAErE;AAED,wBAAsB,gBAAgB,CACrC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,YAAY,EAAE,GACrB,OAAO,CAAC,IAAI,CAAC,CA8Bf;AAED,wBAAgB,UAAU,CAAC,OAAO,GAAE,iBAAsB,GAAG,MAAM,CAoClE"}
package/dist/index.js CHANGED
@@ -1,7 +1,348 @@
1
+ // ../core/dist/index.js
2
+ function renderHTML(sections) {
3
+ return sections.map((section) => `<h2>${section.title}</h2><p>${section.body}</p>`).join(`
4
+ `);
5
+ }
6
+ function renderMarkdown(sections) {
7
+ return sections.map((section) => `## ${section.title}
8
+
9
+ ${section.body}`).join(`
10
+
11
+ ---
12
+
13
+ `);
14
+ }
15
+ function buildCcpaSupplement(config) {
16
+ if (!config.jurisdictions.includes("ca"))
17
+ return null;
18
+ return {
19
+ id: "ccpa-supplement",
20
+ title: "California Privacy Rights (CCPA)",
21
+ body: `This section applies to California residents under the California Consumer Privacy Act (CCPA) and the California Privacy Rights Act (CPRA).
22
+
23
+ **Categories of Personal Information Collected:** We collect the categories of personal information described in the "Information We Collect" section above.
24
+
25
+ **Your California Rights:**
26
+ - **Right to Know:** You may request information about the personal information we have collected about you, including the categories of sources, the business purpose for collection, and the categories of third parties with whom we share information.
27
+ - **Right to Delete:** You may request deletion of personal information we have collected from you, subject to certain exceptions.
28
+ - **Right to Opt-Out:** You may opt out of the sale or sharing of your personal information.
29
+ - **Right to Non-Discrimination:** We will not discriminate against you for exercising your California privacy rights.
30
+
31
+ To exercise your rights, contact us at ${config.company.contact}.`
32
+ };
33
+ }
34
+ function buildContact(config) {
35
+ return {
36
+ id: "contact",
37
+ title: "Contact Us",
38
+ body: `If you have questions or concerns about this Privacy Policy or our data practices, please contact us:
39
+
40
+ **${config.company.legalName}**
41
+ ${config.company.address}
42
+
43
+ Email: ${config.company.contact}`
44
+ };
45
+ }
46
+ function buildCookies(config) {
47
+ const enabled = [];
48
+ if (config.cookies.essential)
49
+ enabled.push("**Essential cookies** — required for the service to function");
50
+ if (config.cookies.analytics)
51
+ enabled.push("**Analytics cookies** — help us understand how visitors interact with our service");
52
+ if (config.cookies.marketing)
53
+ enabled.push("**Marketing cookies** — used to deliver relevant advertisements");
54
+ const body = enabled.length > 0 ? `We use the following types of cookies and tracking technologies:
55
+
56
+ ${enabled.map((e) => `- ${e}`).join(`
57
+ `)}` : "We do not use cookies or tracking technologies on our service.";
58
+ return {
59
+ id: "cookies",
60
+ title: "Cookies and Tracking",
61
+ body
62
+ };
63
+ }
64
+ function buildDataCollected(config) {
65
+ const entries = Object.entries(config.dataCollected);
66
+ const lines = entries.map(([category, items]) => {
67
+ const itemList = items.map((item) => ` - ${item}`).join(`
68
+ `);
69
+ return `**${category}:**
70
+ ${itemList}`;
71
+ });
72
+ return {
73
+ id: "data-collected",
74
+ title: "Information We Collect",
75
+ body: `We collect the following categories of information:
76
+
77
+ ${lines.join(`
78
+
79
+ `)}`
80
+ };
81
+ }
82
+ function buildDataRetention(config) {
83
+ const entries = Object.entries(config.retention);
84
+ const lines = entries.map(([category, period]) => `- **${category}:** ${period}`);
85
+ return {
86
+ id: "data-retention",
87
+ title: "Data Retention",
88
+ body: `We retain your information for the following periods:
89
+
90
+ ${lines.join(`
91
+ `)}`
92
+ };
93
+ }
94
+ function buildGdprSupplement(config) {
95
+ if (!config.jurisdictions.includes("eu"))
96
+ return null;
97
+ return {
98
+ id: "gdpr-supplement",
99
+ title: "GDPR Supplemental Disclosures",
100
+ body: `This section applies to individuals in the European Economic Area (EEA) under the General Data Protection Regulation (GDPR).
101
+
102
+ **Data Controller:** ${config.company.legalName}, ${config.company.address}
103
+
104
+ **Your GDPR Rights:** In addition to the rights listed above, you have the right to lodge a complaint with your local data protection authority if you believe we have not handled your data in accordance with applicable law.
105
+
106
+ **International Transfers:** If we transfer your personal data outside the EEA, we ensure adequate safeguards are in place in accordance with GDPR requirements.`
107
+ };
108
+ }
109
+ function buildIntroduction(config) {
110
+ return {
111
+ id: "introduction",
112
+ title: "Introduction",
113
+ body: `This Privacy Policy describes how ${config.company.name} ("we", "us", or "our") collects, uses, and shares information about you when you use our services.
114
+
115
+ **Effective Date:** ${config.effectiveDate}
116
+
117
+ If you have questions about this policy, please contact us at ${config.company.contact}.`
118
+ };
119
+ }
120
+ function buildLegalBasis(config) {
121
+ if (!config.jurisdictions.includes("eu"))
122
+ return null;
123
+ return {
124
+ id: "legal-basis",
125
+ title: "Legal Basis for Processing",
126
+ body: `We process your personal data under the following legal basis:
127
+
128
+ ${config.legalBasis}`
129
+ };
130
+ }
131
+ function buildThirdParties(config) {
132
+ const lines = config.thirdParties.map((tp) => `- **${tp.name}** — ${tp.purpose}`);
133
+ const body = lines.length > 0 ? `We share data with the following third-party services:
134
+
135
+ ${lines.join(`
136
+ `)}` : "We do not share your data with third-party services.";
137
+ return {
138
+ id: "third-parties",
139
+ title: "Third-Party Services",
140
+ body
141
+ };
142
+ }
143
+ var RIGHTS_LABELS = {
144
+ access: "Right to access your personal data",
145
+ rectification: "Right to correct inaccurate data",
146
+ erasure: "Right to request deletion of your data",
147
+ portability: "Right to receive your data in a portable format",
148
+ restriction: "Right to restrict how we process your data",
149
+ objection: "Right to object to processing",
150
+ opt_out_sale: "Right to opt out of the sale of your personal information",
151
+ non_discrimination: "Right to non-discriminatory treatment for exercising your rights"
152
+ };
153
+ function buildUserRights(config) {
154
+ const lines = config.userRights.map((right) => {
155
+ const label = RIGHTS_LABELS[right] ?? right;
156
+ return `- ${label}`;
157
+ });
158
+ return {
159
+ id: "user-rights",
160
+ title: "Your Rights",
161
+ body: `You have the following rights regarding your personal data:
162
+
163
+ ${lines.join(`
164
+ `)}`
165
+ };
166
+ }
167
+ var SECTION_BUILDERS = [
168
+ buildIntroduction,
169
+ buildDataCollected,
170
+ buildLegalBasis,
171
+ buildDataRetention,
172
+ buildCookies,
173
+ buildThirdParties,
174
+ buildUserRights,
175
+ buildGdprSupplement,
176
+ buildCcpaSupplement,
177
+ buildContact
178
+ ];
179
+ function compilePrivacyPolicy(config, options = { formats: ["markdown"] }) {
180
+ const sections = SECTION_BUILDERS.map((builder) => builder(config)).filter((s) => s !== null);
181
+ return options.formats.map((format) => {
182
+ switch (format) {
183
+ case "markdown":
184
+ return { format, content: renderMarkdown(sections), sections };
185
+ case "html":
186
+ return { format, content: renderHTML(sections), sections };
187
+ case "pdf":
188
+ throw new Error("pdf format is not yet implemented");
189
+ case "jsx":
190
+ throw new Error("jsx format is not yet implemented");
191
+ default:
192
+ throw new Error(`Unsupported format: ${format}`);
193
+ }
194
+ });
195
+ }
196
+ function validatePrivacyPolicy(config) {
197
+ const issues = [];
198
+ if (!config.effectiveDate)
199
+ issues.push({ level: "error", message: "effectiveDate is required" });
200
+ if (!config.company.name)
201
+ issues.push({ level: "error", message: "company.name is required" });
202
+ if (!config.company.legalName)
203
+ issues.push({ level: "error", message: "company.legalName is required" });
204
+ if (!config.company.address)
205
+ issues.push({ level: "error", message: "company.address is required" });
206
+ if (!config.company.contact)
207
+ issues.push({ level: "error", message: "company.contact is required" });
208
+ if (Object.keys(config.dataCollected).length === 0)
209
+ issues.push({
210
+ level: "error",
211
+ message: "dataCollected must have at least one entry"
212
+ });
213
+ if (config.userRights.length === 0)
214
+ issues.push({
215
+ level: "warning",
216
+ message: "userRights is empty — consider listing applicable rights"
217
+ });
218
+ if (config.jurisdictions.includes("eu")) {
219
+ if (!config.legalBasis)
220
+ issues.push({ level: "error", message: "GDPR requires a legalBasis" });
221
+ for (const right of [
222
+ "access",
223
+ "rectification",
224
+ "erasure",
225
+ "portability",
226
+ "restriction",
227
+ "objection"
228
+ ]) {
229
+ if (!config.userRights.includes(right))
230
+ issues.push({
231
+ level: "warning",
232
+ message: `GDPR recommends including the "${right}" right`
233
+ });
234
+ }
235
+ }
236
+ if (config.jurisdictions.includes("ca")) {
237
+ for (const right of [
238
+ "access",
239
+ "erasure",
240
+ "opt_out_sale",
241
+ "non_discrimination"
242
+ ]) {
243
+ if (!config.userRights.includes(right))
244
+ issues.push({
245
+ level: "warning",
246
+ message: `CCPA recommends including the "${right}" right`
247
+ });
248
+ }
249
+ }
250
+ return issues;
251
+ }
252
+ function compilePolicy(input, options) {
253
+ switch (input.type) {
254
+ case "privacy": {
255
+ const { type: _, ...config } = input;
256
+ return compilePrivacyPolicy(config, options);
257
+ }
258
+ }
259
+ }
260
+
1
261
  // src/index.ts
2
- function openPolicy(_options) {
3
- return { name: "openpolicy" };
262
+ import { resolve, join } from "node:path";
263
+ import { writeFile, mkdir, access } from "node:fs/promises";
264
+ var SCAFFOLD_TEMPLATE = `import { definePrivacyPolicy } from "@openpolicy/sdk";
265
+
266
+ export default definePrivacyPolicy({
267
+ effectiveDate: "${new Date().toISOString().slice(0, 10)}",
268
+ company: {
269
+ name: "Your Company",
270
+ legalName: "Your Company, Inc.",
271
+ address: "123 Main St, City, State, ZIP",
272
+ contact: "privacy@yourcompany.com",
273
+ },
274
+ dataCollected: {
275
+ "Personal Information": ["Full name", "Email address"],
276
+ },
277
+ legalBasis: "Legitimate interests and consent",
278
+ retention: {
279
+ "All personal data": "As long as necessary for the purposes described in this policy",
280
+ },
281
+ cookies: { essential: true, analytics: false, marketing: false },
282
+ thirdParties: [],
283
+ userRights: ["access", "erasure"],
284
+ jurisdictions: ["us"],
285
+ });
286
+ `;
287
+ async function writeScaffold(configPath) {
288
+ await writeFile(configPath, SCAFFOLD_TEMPLATE, "utf8");
289
+ }
290
+ async function generatePolicies(configPath, outDir, formats) {
291
+ const mod = await import(`${configPath}?t=${Date.now()}`);
292
+ const config = mod["default"] ?? mod["module.exports"] ?? mod;
293
+ if (config === null || config === undefined || typeof config !== "object") {
294
+ throw new Error(`[openpolicy] Config must export a non-null object: ${configPath}`);
295
+ }
296
+ const issues = validatePrivacyPolicy(config);
297
+ for (const issue of issues) {
298
+ if (issue.level === "error") {
299
+ throw new Error(`[openpolicy] Validation error: ${issue.message}`);
300
+ }
301
+ console.warn(`[openpolicy] Warning: ${issue.message}`);
302
+ }
303
+ const results = compilePolicy({ type: "privacy", ...config }, { formats });
304
+ await mkdir(outDir, { recursive: true });
305
+ for (const result of results) {
306
+ const ext = result.format === "markdown" ? "md" : result.format;
307
+ await writeFile(join(outDir, `privacy-policy.${ext}`), result.content, "utf8");
308
+ }
309
+ }
310
+ function openPolicy(options = {}) {
311
+ const formats = options.formats ?? ["markdown"];
312
+ let resolvedConfigPath;
313
+ let resolvedOutDir;
314
+ return {
315
+ name: "openpolicy",
316
+ configResolved(config) {
317
+ resolvedConfigPath = resolve(config.root, options.config ?? "privacy.config.ts");
318
+ resolvedOutDir = resolve(config.root, options.outDir ?? "public/policies");
319
+ },
320
+ async buildStart() {
321
+ const configExists = await access(resolvedConfigPath).then(() => true, () => false);
322
+ if (!configExists) {
323
+ await writeScaffold(resolvedConfigPath);
324
+ console.log(`[openpolicy] Scaffolded config at ${resolvedConfigPath}`);
325
+ return;
326
+ }
327
+ await generatePolicies(resolvedConfigPath, resolvedOutDir, formats);
328
+ },
329
+ configureServer(server) {
330
+ server.watcher.add(resolvedConfigPath);
331
+ server.watcher.on("change", async (path) => {
332
+ if (path !== resolvedConfigPath)
333
+ return;
334
+ try {
335
+ await generatePolicies(resolvedConfigPath, resolvedOutDir, formats);
336
+ console.log("[openpolicy] Policies regenerated");
337
+ } catch (err) {
338
+ console.error("[openpolicy]", err);
339
+ }
340
+ });
341
+ }
342
+ };
4
343
  }
5
344
  export {
6
- openPolicy
345
+ writeScaffold,
346
+ openPolicy,
347
+ generatePolicies
7
348
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpolicy/vite",
3
- "version": "0.0.2",
3
+ "version": "0.0.5",
4
4
  "type": "module",
5
5
  "description": "Vite plugin for compiling OpenPolicy privacy policy documents at build time",
6
6
  "license": "MIT",
@@ -14,18 +14,14 @@
14
14
  "README.md"
15
15
  ],
16
16
  "exports": {
17
- ".": "./src/index.ts"
18
- },
19
- "publishConfig": {
20
- "exports": {
21
- ".": {
22
- "import": "./dist/index.js",
23
- "types": "./dist/index.d.ts"
24
- }
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "types": "./dist/index.d.ts"
25
20
  }
26
21
  },
27
22
  "scripts": {
28
- "build": "bun build ./src/index.ts --outdir dist && tsc --emitDeclarationOnly",
23
+ "dev": "bun build ./src/index.ts --outdir dist --watch",
24
+ "build": "bun build ./src/index.ts --outdir dist --target node && tsc --emitDeclarationOnly",
29
25
  "check-types": "tsc --noEmit",
30
26
  "test": "bun test"
31
27
  },
@@ -33,6 +29,8 @@
33
29
  "vite": ">=4.0.0"
34
30
  },
35
31
  "devDependencies": {
36
- "@openpolicy/tooling": "workspace:*"
32
+ "@openpolicy/core": "workspace:*",
33
+ "@openpolicy/tooling": "workspace:*",
34
+ "vite": "^5.0.0"
37
35
  }
38
36
  }