@openpolicy/cli 0.0.11 → 0.0.12

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.
Files changed (2) hide show
  1. package/dist/cli.js +120 -316
  2. package/package.json +2 -2
package/dist/cli.js CHANGED
@@ -1,10 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { defineCommand, runMain } from "citty";
3
+ import { mkdir, writeFile } from "node:fs/promises";
3
4
  import { join, resolve } from "node:path";
4
5
  import consola from "consola";
5
6
  import { existsSync, watch } from "node:fs";
6
- import { mkdir, writeFile } from "node:fs/promises";
7
- import { compilePolicy, expandOpenPolicyConfig, isOpenPolicyConfig, validatePrivacyPolicy, validateTermsOfService } from "@openpolicy/core";
7
+ import { compilePolicy, expandOpenPolicyConfig, isOpenPolicyConfig, validateCookiePolicy, validatePrivacyPolicy, validateTermsOfService } from "@openpolicy/core";
8
8
  //#region \0rolldown/runtime.js
9
9
  var __defProp = Object.defineProperty;
10
10
  var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -19,265 +19,109 @@ var __exportAll = (all, no_symbols) => {
19
19
  };
20
20
  //#endregion
21
21
  //#region package.json
22
- var version = "0.0.11";
22
+ var version = "0.0.12";
23
23
  //#endregion
24
24
  //#region src/commands/init.ts
25
- var init_exports = /* @__PURE__ */ __exportAll({ initCommand: () => initCommand });
26
- function toJurisdictions(choice) {
27
- if (choice === "gdpr") return ["eu"];
28
- if (choice === "ccpa") return ["ca"];
29
- if (choice === "both") return ["eu", "ca"];
30
- return ["us"];
31
- }
32
- function toDataCollected(categories) {
33
- const groups = {};
34
- for (const cat of categories) {
35
- const mapping = DATA_CATEGORY_MAP[cat];
36
- if (!mapping) continue;
37
- groups[mapping.group] = [...groups[mapping.group] ?? [], mapping.label];
38
- }
39
- return Object.keys(groups).length > 0 ? groups : { "Personal Information": ["Email address"] };
40
- }
41
- function toUserRights(jurisdictions) {
42
- const rights = new Set(["access", "erasure"]);
43
- if (jurisdictions.includes("eu")) for (const r of [
44
- "rectification",
45
- "portability",
46
- "restriction",
47
- "objection"
48
- ]) rights.add(r);
49
- if (jurisdictions.includes("ca")) for (const r of ["opt_out_sale", "non_discrimination"]) rights.add(r);
50
- return Array.from(rights);
51
- }
52
- function renderPrivacyConfig(values) {
53
- const dataLines = Object.entries(values.dataCollected).map(([k, v]) => ` ${JSON.stringify(k)}: ${JSON.stringify(v)},`).join("\n");
54
- return `import { definePrivacyPolicy } from "@openpolicy/sdk";
55
-
56
- export default definePrivacyPolicy({
57
- effectiveDate: "${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}",
58
- company: {
59
- name: ${JSON.stringify(values.companyName)},
60
- legalName: ${JSON.stringify(values.legalName)},
61
- address: ${JSON.stringify(values.address)},
62
- contact: ${JSON.stringify(values.contact)},
63
- },
64
- dataCollected: {
65
- ${dataLines}
66
- },
67
- legalBasis: ${JSON.stringify(values.legalBasis)},
68
- retention: {
69
- "All personal data": "As long as necessary for the purposes described in this policy",
70
- },
71
- cookies: {
72
- essential: true,
73
- analytics: ${values.hasCookies},
74
- marketing: false,
75
- },
76
- thirdParties: [],
77
- userRights: ${JSON.stringify(values.userRights)},
78
- jurisdictions: ${JSON.stringify(values.jurisdictions)},
25
+ var init_exports = /* @__PURE__ */ __exportAll({
26
+ getOpenPolicyTemplate: () => getOpenPolicyTemplate,
27
+ initCommand: () => initCommand
79
28
  });
80
- `;
81
- }
82
- function renderTermsConfig(values) {
83
- return `import { defineTermsOfService } from "@openpolicy/sdk";
29
+ function getOpenPolicyTemplate(companyName, contactEmail, policies) {
30
+ const today = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
31
+ return `import { defineConfig } from "@openpolicy/sdk";
84
32
 
85
- export default defineTermsOfService({
86
- effectiveDate: "${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}",
87
- company: {
88
- name: ${JSON.stringify(values.companyName)},
89
- legalName: ${JSON.stringify(values.legalName)},
90
- address: ${JSON.stringify(values.address)},
91
- contact: ${JSON.stringify(values.contact)},
92
- },
93
- acceptance: {
94
- methods: ["using the service", "creating an account"],
95
- },
96
- eligibility: {
97
- minimumAge: 13,
98
- },
99
- accounts: {
100
- registrationRequired: false,
101
- userResponsibleForCredentials: true,
102
- companyCanTerminate: true,
103
- },
104
- prohibitedUses: [
105
- "Violating any applicable laws or regulations",
106
- "Infringing on intellectual property rights",
107
- "Transmitting harmful or malicious content",
108
- ],
109
- intellectualProperty: {
110
- companyOwnsService: true,
111
- usersMayNotCopy: true,
112
- },
113
- termination: {
114
- companyCanTerminate: true,
115
- userCanTerminate: true,
116
- },
117
- disclaimers: {
118
- serviceProvidedAsIs: true,
119
- noWarranties: true,
120
- },
121
- limitationOfLiability: {
122
- excludesIndirectDamages: true,
123
- },
124
- governingLaw: {
125
- jurisdiction: ${JSON.stringify(values.jurisdiction)},
126
- },
127
- changesPolicy: {
128
- noticeMethod: "email or prominent notice on our website",
129
- noticePeriodDays: 30,
130
- },
131
- });
132
- `;
133
- }
134
- var DATA_CATEGORY_MAP, initCommand;
135
- var init_init = __esmMin((() => {
136
- DATA_CATEGORY_MAP = {
137
- name: {
138
- group: "Personal Information",
139
- label: "Full name"
140
- },
141
- email: {
142
- group: "Personal Information",
143
- label: "Email address"
33
+ export default defineConfig({
34
+ company: {
35
+ name: "${companyName}",
36
+ legalName: "${companyName}",
37
+ address: "",
38
+ contact: "${contactEmail}",
39
+ },${policies.includes("privacy") ? `
40
+ privacy: {
41
+ effectiveDate: "${today}",
42
+ dataCollected: {
43
+ "Personal Information": ["Email address"],
144
44
  },
145
- ip_address: {
146
- group: "Technical Data",
147
- label: "IP address"
45
+ legalBasis: "Legitimate interests",
46
+ retention: {
47
+ "All personal data": "As long as necessary for the purposes described in this policy",
148
48
  },
149
- device_info: {
150
- group: "Technical Data",
151
- label: "Device type and browser"
49
+ cookies: { essential: true, analytics: false, marketing: false },
50
+ thirdParties: [],
51
+ userRights: ["access", "erasure"],
52
+ jurisdictions: ["us"],
53
+ },` : ""}${policies.includes("terms") ? `
54
+ terms: {
55
+ effectiveDate: "${today}",
56
+ acceptance: { methods: ["using the service", "creating an account"] },
57
+ eligibility: { minimumAge: 13 },
58
+ accounts: {
59
+ registrationRequired: false,
60
+ userResponsibleForCredentials: true,
61
+ companyCanTerminate: true,
152
62
  },
153
- location: {
154
- group: "Location Data",
155
- label: "Approximate location"
63
+ prohibitedUses: [
64
+ "Violating any applicable laws or regulations",
65
+ "Infringing on intellectual property rights",
66
+ "Transmitting harmful or malicious content",
67
+ ],
68
+ intellectualProperty: { companyOwnsService: true, usersMayNotCopy: true },
69
+ termination: { companyCanTerminate: true, userCanTerminate: true },
70
+ disclaimers: { serviceProvidedAsIs: true, noWarranties: true },
71
+ limitationOfLiability: { excludesIndirectDamages: true },
72
+ governingLaw: { jurisdiction: "Delaware, USA" },
73
+ changesPolicy: {
74
+ noticeMethod: "email or prominent notice on our website",
75
+ noticePeriodDays: 30,
156
76
  },
157
- payment_info: {
158
- group: "Financial Data",
159
- label: "Payment card details"
160
- },
161
- usage_data: {
162
- group: "Usage Data",
163
- label: "Pages visited and features used"
164
- }
165
- };
77
+ },` : ""}${policies.includes("cookie") ? `
78
+ cookie: {
79
+ effectiveDate: "${today}",
80
+ cookies: { essential: true, analytics: false, functional: false, marketing: false },
81
+ jurisdictions: ["us"],
82
+ },` : ""}
83
+ });
84
+ `;
85
+ }
86
+ var initCommand;
87
+ var init_init = __esmMin((() => {
166
88
  initCommand = defineCommand({
167
89
  meta: {
168
90
  name: "init",
169
91
  description: "Interactively create a policy config file"
170
92
  },
171
- args: {
172
- out: {
173
- type: "string",
174
- description: "Output path for generated config",
175
- default: ""
176
- },
177
- yes: {
178
- type: "boolean",
179
- description: "Skip prompts and use defaults (CI mode)",
180
- default: false
181
- },
182
- type: {
183
- type: "string",
184
- description: "Policy type: \"privacy\" or \"terms\"",
185
- default: "privacy"
186
- }
187
- },
93
+ args: { out: {
94
+ type: "string",
95
+ description: "Output path for generated config",
96
+ default: "openpolicy.ts"
97
+ } },
188
98
  async run({ args }) {
189
- const policyType = args.type === "terms" ? "terms" : "privacy";
190
- const defaultOut = policyType === "terms" ? "./terms.config.ts" : "./privacy.config.ts";
191
- consola.start(`OpenPolicy init wizard (${policyType})`);
192
- const companyName = String(await consola.prompt("Company name?", {
193
- type: "text",
194
- cancel: "reject"
195
- }));
196
- const legalName = String(await consola.prompt("Legal entity name?", {
197
- type: "text",
198
- cancel: "reject",
199
- initial: companyName
200
- }));
201
- const address = String(await consola.prompt("Company address?", {
99
+ consola.box("Welcome to OpenPolicy\nGenerate privacy policies, terms of service, and cookie policies from a single config file.");
100
+ consola.start("Let's get you set up.");
101
+ const source = getOpenPolicyTemplate(String(await consola.prompt("Company name?", {
202
102
  type: "text",
203
103
  cancel: "reject"
204
- }));
205
- const contact = String(await consola.prompt(policyType === "terms" ? "Legal contact email?" : "Privacy contact email?", {
104
+ })), String(await consola.prompt("Contact email?", {
206
105
  type: "text",
207
106
  cancel: "reject"
107
+ })), await consola.prompt("Which policies do you need?", {
108
+ type: "multiselect",
109
+ cancel: "reject",
110
+ options: [
111
+ "privacy",
112
+ "terms",
113
+ "cookie"
114
+ ]
208
115
  }));
209
- let source;
210
- if (policyType === "terms") source = renderTermsConfig({
211
- companyName,
212
- legalName,
213
- address,
214
- contact,
215
- jurisdiction: String(await consola.prompt("Governing law jurisdiction? (e.g. Delaware, USA)", {
216
- type: "text",
217
- cancel: "reject",
218
- initial: "Delaware, USA"
219
- }))
220
- });
221
- else {
222
- const jurisdictionChoice = String(await consola.prompt("Jurisdiction?", {
223
- type: "select",
224
- cancel: "reject",
225
- options: [
226
- "gdpr",
227
- "ccpa",
228
- "both"
229
- ]
230
- }));
231
- const dataCategories = await consola.prompt("Data categories collected?", {
232
- type: "multiselect",
233
- cancel: "reject",
234
- options: [
235
- "name",
236
- "email",
237
- "ip_address",
238
- "device_info",
239
- "location",
240
- "payment_info",
241
- "usage_data"
242
- ]
243
- });
244
- const hasCookies = Boolean(await consola.prompt("Does your app use cookies?", {
245
- type: "confirm",
246
- cancel: "reject",
247
- initial: true
248
- }));
249
- const jurisdictions = toJurisdictions(jurisdictionChoice);
250
- const dataCollected = toDataCollected(dataCategories);
251
- const userRights = toUserRights(jurisdictions);
252
- source = renderPrivacyConfig({
253
- companyName,
254
- legalName,
255
- address,
256
- contact,
257
- jurisdictions,
258
- dataCollected,
259
- legalBasis: jurisdictions.includes("eu") ? "Legitimate interests and consent" : "",
260
- hasCookies,
261
- userRights
262
- });
263
- }
264
- const outPath = resolve(args.out || defaultOut);
265
- await Bun.write(outPath, source);
116
+ const outPath = resolve(args.out);
117
+ await writeFile(outPath, source);
266
118
  consola.success(`Config written to ${outPath}`);
119
+ consola.info(`Open ${outPath} and fill in your company's details — address, legal name, and any policy-specific fields.`);
120
+ consola.info(`When you're ready, run:\n\n openpolicy generate ${args.out}\n\nto compile your policies to HTML or Markdown.`);
267
121
  }
268
122
  });
269
123
  }));
270
124
  //#endregion
271
- //#region src/utils/detect-type.ts
272
- function detectType(explicitType, configPath) {
273
- if (explicitType === "privacy" || explicitType === "terms" || explicitType === "cookie") return explicitType;
274
- const lower = configPath.toLowerCase();
275
- if (lower.includes("cookie")) return "cookie";
276
- if (lower.includes("terms")) return "terms";
277
- return "privacy";
278
- }
279
- var init_detect_type = __esmMin((() => {}));
280
- //#endregion
281
125
  //#region src/utils/load-config.ts
282
126
  async function loadConfig(configPath, bustCache = false) {
283
127
  const absPath = resolve(configPath);
@@ -305,28 +149,13 @@ var init_load_config = __esmMin((() => {}));
305
149
  //#endregion
306
150
  //#region src/commands/generate.ts
307
151
  var generate_exports = /* @__PURE__ */ __exportAll({ generateCommand: () => generateCommand });
308
- function toPolicyInput(policyType, config) {
309
- if (policyType === "terms") return {
310
- type: "terms",
311
- ...config
312
- };
313
- if (policyType === "cookie") return {
314
- type: "cookie",
315
- ...config
316
- };
317
- return {
318
- type: "privacy",
319
- ...config
320
- };
321
- }
322
- async function generateFromConfig(configPath, formats, outDir, explicitType, bustCache = false) {
323
- if (!existsSync(configPath)) return false;
152
+ async function generateFromConfig(configPath, formats, outDir, bustCache = false) {
324
153
  const config = await loadConfig(configPath, bustCache);
325
154
  if (isOpenPolicyConfig(config)) {
326
155
  const inputs = expandOpenPolicyConfig(config);
327
156
  if (inputs.length === 0) {
328
157
  consola.warn(`Unified config has no privacy or terms sections: ${configPath}`);
329
- return true;
158
+ return;
330
159
  }
331
160
  await mkdir(outDir, { recursive: true });
332
161
  for (const input of inputs) {
@@ -339,23 +168,12 @@ async function generateFromConfig(configPath, formats, outDir, explicitType, bus
339
168
  consola.success(`Written: ${outPath}`);
340
169
  }
341
170
  }
342
- return true;
343
- }
344
- const policyType = detectType(explicitType, configPath);
345
- consola.start(`Generating ${policyType} policy from ${configPath} → formats: ${formats.join(", ")}`);
346
- const outputFilename = policyType === "terms" ? "terms-of-service" : policyType === "cookie" ? "cookie-policy" : "privacy-policy";
347
- const results = compilePolicy(toPolicyInput(policyType, config), { formats });
348
- await mkdir(outDir, { recursive: true });
349
- for (const result of results) {
350
- const outPath = join(outDir, `${outputFilename}.${result.format === "markdown" ? "md" : result.format}`);
351
- await writeFile(outPath, result.content, "utf-8");
352
- consola.success(`Written: ${outPath}`);
171
+ return;
353
172
  }
354
- return true;
173
+ throw new Error(`[openpolicy] Config must use defineConfig() (OpenPolicyConfig): ${configPath}`);
355
174
  }
356
175
  var generateCommand;
357
176
  var init_generate = __esmMin((() => {
358
- init_detect_type();
359
177
  init_load_config();
360
178
  generateCommand = defineCommand({
361
179
  meta: {
@@ -365,8 +183,8 @@ var init_generate = __esmMin((() => {
365
183
  args: {
366
184
  config: {
367
185
  type: "positional",
368
- description: "Path(s) to policy config file(s), comma-separated",
369
- default: "./openpolicy.ts,./policy.config.ts,./terms.config.ts"
186
+ description: "Path to policy config file",
187
+ default: "./openpolicy.ts"
370
188
  },
371
189
  format: {
372
190
  type: "string",
@@ -378,43 +196,32 @@ var init_generate = __esmMin((() => {
378
196
  description: "Output directory",
379
197
  default: "./output"
380
198
  },
381
- type: {
382
- type: "string",
383
- description: "Policy type: \"privacy\", \"terms\", or \"cookie\" (auto-detected from filename if omitted)",
384
- default: ""
385
- },
386
199
  watch: {
387
200
  type: "boolean",
388
- description: "Watch config files and regenerate on changes",
201
+ description: "Watch config file and regenerate on changes",
389
202
  default: false
390
203
  }
391
204
  },
392
205
  async run({ args }) {
393
206
  const formats = args.format.split(",").map((f) => f.trim()).filter(Boolean);
394
207
  const outDir = args.out;
395
- const explicitType = args.type || void 0;
396
- const configPaths = args.config.split(",").map((p) => p.trim()).filter(Boolean);
397
- const hasMultipleConfigs = configPaths.length > 1;
398
- const watchablePaths = [];
399
- for (const configPath of configPaths) if (await generateFromConfig(configPath, formats, outDir, explicitType)) watchablePaths.push(configPath);
400
- else if (hasMultipleConfigs) consola.warn(`Config not found, skipping: ${configPath}`);
401
- else throw new Error(`Config not found: ${configPath}`);
208
+ const configPath = args.config;
209
+ if (!existsSync(configPath)) throw new Error(`Config not found: ${configPath}`);
210
+ await generateFromConfig(configPath, formats, outDir);
402
211
  consola.success(`Policy generation complete → ${outDir}`);
403
- if (args.watch && watchablePaths.length > 0) {
212
+ if (args.watch) {
404
213
  consola.info("Watching for changes...");
405
- for (const configPath of watchablePaths) {
406
- let debounceTimer = null;
407
- watch(configPath, () => {
408
- if (debounceTimer) clearTimeout(debounceTimer);
409
- debounceTimer = setTimeout(async () => {
410
- try {
411
- await generateFromConfig(configPath, formats, outDir, explicitType, true);
412
- } catch (err) {
413
- consola.error(`Error regenerating ${configPath}:`, err);
414
- }
415
- }, 100);
416
- });
417
- }
214
+ let debounceTimer = null;
215
+ watch(configPath, () => {
216
+ if (debounceTimer) clearTimeout(debounceTimer);
217
+ debounceTimer = setTimeout(async () => {
218
+ try {
219
+ await generateFromConfig(configPath, formats, outDir, true);
220
+ } catch (err) {
221
+ consola.error(`Error regenerating ${configPath}:`, err);
222
+ }
223
+ }, 100);
224
+ });
418
225
  }
419
226
  }
420
227
  });
@@ -424,7 +231,6 @@ var init_generate = __esmMin((() => {
424
231
  var validate_exports = /* @__PURE__ */ __exportAll({ validateCommand: () => validateCommand });
425
232
  var validateCommand;
426
233
  var init_validate = __esmMin((() => {
427
- init_detect_type();
428
234
  init_load_config();
429
235
  validateCommand = defineCommand({
430
236
  meta: {
@@ -441,31 +247,29 @@ var init_validate = __esmMin((() => {
441
247
  type: "string",
442
248
  description: "Jurisdiction to validate against: gdpr, ccpa, or all",
443
249
  default: "all"
444
- },
445
- type: {
446
- type: "string",
447
- description: "Policy type: \"privacy\" or \"terms\" (auto-detected from filename if omitted)",
448
- default: ""
449
250
  }
450
251
  },
451
252
  async run({ args }) {
452
253
  const configPath = args.config ?? "./policy.config.ts";
453
- const policyType = detectType(args.type || void 0, configPath);
454
- consola.start(`Validating ${policyType} policy: ${configPath}`);
455
254
  const config = await loadConfig(configPath);
456
- const issues = policyType === "terms" ? validateTermsOfService(config) : validatePrivacyPolicy(config);
457
- if (issues.length === 0) {
458
- consola.success("Config is valid — no issues found.");
459
- return;
460
- }
461
- for (const issue of issues) if (issue.level === "error") consola.error(issue.message);
462
- else consola.warn(issue.message);
463
- const errors = issues.filter((i) => i.level === "error");
464
- if (errors.length > 0) {
465
- consola.fail(`Validation failed with ${errors.length} error(s).`);
466
- process.exit(1);
255
+ if (!isOpenPolicyConfig(config)) throw new Error(`[openpolicy] Config must use defineConfig() (OpenPolicyConfig): ${configPath}`);
256
+ const inputs = expandOpenPolicyConfig(config);
257
+ let totalErrors = 0;
258
+ for (const input of inputs) {
259
+ consola.start(`Validating ${input.type} policy: ${configPath}`);
260
+ const issues = input.type === "terms" ? validateTermsOfService(input) : input.type === "cookie" ? validateCookiePolicy(input) : validatePrivacyPolicy(input);
261
+ if (issues.length === 0) {
262
+ consola.success(`${input.type}: no issues found.`);
263
+ continue;
264
+ }
265
+ for (const issue of issues) if (issue.level === "error") consola.error(issue.message);
266
+ else consola.warn(issue.message);
267
+ const errors = issues.filter((i) => i.level === "error");
268
+ totalErrors += errors.length;
269
+ if (errors.length > 0) consola.fail(`${input.type}: validation failed with ${errors.length} error(s).`);
270
+ else consola.success(`${input.type}: validation passed with warnings.`);
467
271
  }
468
- consola.success("Validation passed with warnings.");
272
+ if (totalErrors > 0) process.exit(1);
469
273
  }
470
274
  });
471
275
  }));
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@openpolicy/cli",
3
- "version": "0.0.11",
3
+ "version": "0.0.12",
4
4
  "type": "module",
5
5
  "description": "CLI for generating and validating OpenPolicy privacy policy documents",
6
- "license": "MIT",
6
+ "license": "GPL-3.0-only",
7
7
  "repository": {
8
8
  "type": "git",
9
9
  "url": "https://github.com/jamiedavenport/openpolicy",