@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.
- package/README.md +67 -2
- package/dist/cli.js +96 -29
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,68 @@
|
|
|
1
|
-
#
|
|
1
|
+
# `@openpolicy/cli`
|
|
2
2
|
|
|
3
|
-
|
|
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 {
|
|
6
|
-
import {
|
|
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
|
-
|
|
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(
|
|
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 \"
|
|
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 =
|
|
333
|
-
const outDir = args.out
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
const
|
|
338
|
-
const
|
|
339
|
-
|
|
340
|
-
|
|
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
|
|
477
|
+
version,
|
|
411
478
|
description: "Generate and validate privacy policy documents"
|
|
412
479
|
},
|
|
413
480
|
subCommands: {
|