@openpolicy/cli 0.0.9 → 0.0.11

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 (3) hide show
  1. package/README.md +67 -2
  2. package/dist/cli.js +96 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,3 +1,68 @@
1
- # OpenPolicy CLI
1
+ # `@openpolicy/cli`
2
2
 
3
- This is the CLI package for the OpenPolicy project.
3
+ > CLI for generating and validating [OpenPolicy](https://openpolicy.sh) policy documents.
4
+
5
+ Compile privacy policies and terms of service to Markdown or HTML from the command line — no Vite or Astro required.
6
+
7
+ ## Install
8
+
9
+ ```sh
10
+ bun add -D @openpolicy/cli
11
+ bun add @openpolicy/sdk
12
+ # or: npm install --save-dev @openpolicy/cli @openpolicy/sdk
13
+ ```
14
+
15
+ Or run without installing:
16
+
17
+ ```sh
18
+ bunx @openpolicy/cli --help
19
+ ```
20
+
21
+ ## Commands
22
+
23
+ ### `init` — interactive setup wizard
24
+
25
+ Creates a starter config file with placeholder content:
26
+
27
+ ```sh
28
+ openpolicy init # privacy policy (default)
29
+ openpolicy init --type terms # terms of service
30
+ ```
31
+
32
+ ### `generate` — compile a policy
33
+
34
+ ```sh
35
+ openpolicy generate ./privacy.config.ts --format markdown,html --out ./public/policies
36
+ openpolicy generate ./terms.config.ts --format markdown --out ./public/policies
37
+ ```
38
+
39
+ | Flag | Default | Description |
40
+ |---|---|---|
41
+ | `--format` | `markdown` | Comma-separated output formats: `markdown`, `html` |
42
+ | `--out` | `./public/policies` | Output directory |
43
+ | `--type` | auto-detected | Override policy type: `privacy` or `terms` |
44
+ | `--watch` | `false` | Watch config files and regenerate on changes |
45
+
46
+ Policy type is auto-detected from the filename — files containing `"terms"` compile as terms of service.
47
+
48
+ ### `validate` — check a policy config
49
+
50
+ ```sh
51
+ openpolicy validate ./privacy.config.ts
52
+ openpolicy validate ./privacy.config.ts --jurisdiction gdpr
53
+ ```
54
+
55
+ | Flag | Default | Description |
56
+ |---|---|---|
57
+ | `--jurisdiction` | `all` | Validate against: `gdpr`, `ccpa`, or `all` |
58
+ | `--type` | auto-detected | Override policy type: `privacy` or `terms` |
59
+
60
+ ## Documentation
61
+
62
+ [openpolicy.sh/docs/getting-started/cli](https://openpolicy.sh/docs/getting-started/cli)
63
+
64
+ ## Links
65
+
66
+ - [GitHub](https://github.com/jamiedavenport/openpolicy)
67
+ - [openpolicy.sh](https://openpolicy.sh)
68
+ - [npm](https://www.npmjs.com/package/@openpolicy/cli)
package/dist/cli.js CHANGED
@@ -2,8 +2,9 @@
2
2
  import { defineCommand, runMain } from "citty";
3
3
  import { join, resolve } from "node:path";
4
4
  import consola from "consola";
5
- import { compilePolicy, validatePrivacyPolicy, validateTermsOfService } from "@openpolicy/core";
6
- import { existsSync } from "node:fs";
5
+ 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
8
  //#region \0rolldown/runtime.js
8
9
  var __defProp = Object.defineProperty;
9
10
  var __esmMin = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
@@ -17,6 +18,9 @@ var __exportAll = (all, no_symbols) => {
17
18
  return target;
18
19
  };
19
20
  //#endregion
21
+ //#region package.json
22
+ var version = "0.0.11";
23
+ //#endregion
20
24
  //#region src/commands/init.ts
21
25
  var init_exports = /* @__PURE__ */ __exportAll({ initCommand: () => initCommand });
22
26
  function toJurisdictions(choice) {
@@ -266,21 +270,25 @@ var init_init = __esmMin((() => {
266
270
  //#endregion
267
271
  //#region src/utils/detect-type.ts
268
272
  function detectType(explicitType, configPath) {
269
- if (explicitType === "privacy" || explicitType === "terms") return explicitType;
270
- return configPath.toLowerCase().includes("terms") ? "terms" : "privacy";
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";
271
278
  }
272
279
  var init_detect_type = __esmMin((() => {}));
273
280
  //#endregion
274
281
  //#region src/utils/load-config.ts
275
- async function loadConfig(configPath) {
282
+ async function loadConfig(configPath, bustCache = false) {
276
283
  const absPath = resolve(configPath);
277
284
  if (!existsSync(absPath)) {
278
285
  consola.error(`Config file not found: ${absPath}`);
279
286
  process.exit(1);
280
287
  }
288
+ const importPath = bustCache ? `${absPath}?t=${Date.now()}` : absPath;
281
289
  let mod;
282
290
  try {
283
- mod = await import(absPath);
291
+ mod = await import(importPath);
284
292
  } catch (err) {
285
293
  consola.error(`Failed to load config: ${absPath}`);
286
294
  consola.error(err);
@@ -297,6 +305,54 @@ var init_load_config = __esmMin((() => {}));
297
305
  //#endregion
298
306
  //#region src/commands/generate.ts
299
307
  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;
324
+ const config = await loadConfig(configPath, bustCache);
325
+ if (isOpenPolicyConfig(config)) {
326
+ const inputs = expandOpenPolicyConfig(config);
327
+ if (inputs.length === 0) {
328
+ consola.warn(`Unified config has no privacy or terms sections: ${configPath}`);
329
+ return true;
330
+ }
331
+ await mkdir(outDir, { recursive: true });
332
+ for (const input of inputs) {
333
+ const outputFilename = input.type === "terms" ? "terms-of-service" : input.type === "cookie" ? "cookie-policy" : "privacy-policy";
334
+ consola.start(`Generating ${input.type} policy from ${configPath} → formats: ${formats.join(", ")}`);
335
+ const results = compilePolicy(input, { formats });
336
+ for (const result of results) {
337
+ const outPath = join(outDir, `${outputFilename}.${result.format === "markdown" ? "md" : result.format}`);
338
+ await writeFile(outPath, result.content, "utf-8");
339
+ consola.success(`Written: ${outPath}`);
340
+ }
341
+ }
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}`);
353
+ }
354
+ return true;
355
+ }
300
356
  var generateCommand;
301
357
  var init_generate = __esmMin((() => {
302
358
  init_detect_type();
@@ -309,8 +365,8 @@ var init_generate = __esmMin((() => {
309
365
  args: {
310
366
  config: {
311
367
  type: "positional",
312
- description: "Path to policy config file",
313
- default: "./policy.config.ts"
368
+ description: "Path(s) to policy config file(s), comma-separated",
369
+ default: "./openpolicy.ts,./policy.config.ts,./terms.config.ts"
314
370
  },
315
371
  format: {
316
372
  type: "string",
@@ -324,31 +380,42 @@ var init_generate = __esmMin((() => {
324
380
  },
325
381
  type: {
326
382
  type: "string",
327
- description: "Policy type: \"privacy\" or \"terms\" (auto-detected from filename if omitted)",
383
+ description: "Policy type: \"privacy\", \"terms\", or \"cookie\" (auto-detected from filename if omitted)",
328
384
  default: ""
385
+ },
386
+ watch: {
387
+ type: "boolean",
388
+ description: "Watch config files and regenerate on changes",
389
+ default: false
329
390
  }
330
391
  },
331
392
  async run({ args }) {
332
- const formats = (args.format ?? "markdown").split(",").map((f) => f.trim()).filter(Boolean);
333
- const outDir = args.out ?? "./output";
334
- const configPath = args.config ?? "./policy.config.ts";
335
- const policyType = detectType(args.type || void 0, configPath);
336
- consola.start(`Generating ${policyType} policy from ${configPath} → formats: ${formats.join(", ")}`);
337
- const config = await loadConfig(configPath);
338
- const outputFilename = policyType === "terms" ? "terms-of-service" : "privacy-policy";
339
- const results = compilePolicy(policyType === "terms" ? {
340
- type: "terms",
341
- ...config
342
- } : {
343
- type: "privacy",
344
- ...config
345
- }, { formats });
346
- for (const result of results) {
347
- const outPath = join(outDir, `${outputFilename}.${result.format === "markdown" ? "md" : result.format}`);
348
- await Bun.write(outPath, result.content);
349
- consola.success(`Written: ${outPath}`);
350
- }
393
+ const formats = args.format.split(",").map((f) => f.trim()).filter(Boolean);
394
+ 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}`);
351
402
  consola.success(`Policy generation complete → ${outDir}`);
403
+ if (args.watch && watchablePaths.length > 0) {
404
+ 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
+ }
418
+ }
352
419
  }
353
420
  });
354
421
  }));
@@ -407,7 +474,7 @@ var init_validate = __esmMin((() => {
407
474
  runMain(defineCommand({
408
475
  meta: {
409
476
  name: "openpolicy",
410
- version: "0.0.1",
477
+ version,
411
478
  description: "Generate and validate privacy policy documents"
412
479
  },
413
480
  subCommands: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpolicy/cli",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "description": "CLI for generating and validating OpenPolicy privacy policy documents",
6
6
  "license": "MIT",