@shaclmate/cli 4.0.16 → 4.0.18

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/cli.js CHANGED
@@ -1,24 +1,15 @@
1
1
  #!/usr/bin/env node
2
- import * as fs from "node:fs";
3
- import PrefixMap from "@rdfjs/prefix-map/PrefixMap.js";
4
- import { AstJsonGenerator, Compiler, ShapesGraph, TsGenerator, ZodGenerator, } from "@shaclmate/compiler";
2
+ import { AstJsonGenerator, TsGenerator, ZodGenerator, } from "@shaclmate/compiler";
5
3
  import { command, option, restPositionals, run, string, subcommands, } from "cmd-ts";
6
4
  import { ExistingPath } from "cmd-ts/dist/cjs/batteries/fs.js";
7
- import { DataFactory, Parser, Store, Writer } from "n3";
8
- import { pino } from "pino";
9
- import SHACLValidator from "rdf-validate-shacl";
10
- import { shaclShaclDataset } from "./shaclShaclDataset.js";
11
- const inputFilePaths = restPositionals({
12
- displayName: "inputFilePaths",
13
- description: "paths to RDF files containing SHACL shapes",
5
+ import { generate } from "./commands/generate.js";
6
+ import { merge } from "./commands/merge.js";
7
+ import { validate } from "./commands/validate.js";
8
+ const inputPaths = restPositionals({
9
+ displayName: "inputPaths",
10
+ description: "paths to RDF files or directories of RDF files containing SHACL shapes",
14
11
  type: ExistingPath,
15
12
  });
16
- const logger = pino({
17
- level: process.env["NODE_ENV"] === "development" ||
18
- process.env["NODE_ENV"] === "test"
19
- ? "debug"
20
- : "info",
21
- }, pino["destination"] ? pino.destination(2) : undefined);
22
13
  const outputFilePath = option({
23
14
  defaultValue: () => "",
24
15
  description: "path to a file to write output to; if not specified, write to stdout",
@@ -26,113 +17,93 @@ const outputFilePath = option({
26
17
  short: "o",
27
18
  type: string,
28
19
  });
29
- function generate({ generator, inputFilePaths, outputFilePath, }) {
30
- if (inputFilePaths.length === 0) {
31
- throw new Error("must specify at least one input shapes graph file path");
32
- }
33
- const inputParser = new Parser();
34
- const dataset = new Store();
35
- const iriPrefixes = [];
36
- for (const inputFilePath of inputFilePaths) {
37
- dataset.addQuads(inputParser.parse(fs.readFileSync(inputFilePath).toString(), null, (prefix, prefixNode) => {
38
- const existingIriPrefix = iriPrefixes.find((iriPrefix) => iriPrefix[0] === prefix || iriPrefix[1].equals(prefixNode));
39
- if (existingIriPrefix) {
40
- if (existingIriPrefix[0] !== prefix ||
41
- !existingIriPrefix[1].equals(prefixNode)) {
42
- logger.warn("conflicting prefix %s: %s", prefix, prefixNode.value);
43
- }
44
- return;
45
- }
46
- iriPrefixes.push([prefix, prefixNode]);
47
- }));
48
- }
49
- const prefixMap = new PrefixMap(iriPrefixes, { factory: DataFactory });
50
- {
51
- const validationReport = new SHACLValidator(shaclShaclDataset, {}).validate(dataset);
52
- if (!validationReport.conforms) {
53
- process.stderr.write("input is not valid SHACL:\n");
54
- const n3WriterPrefixes = {};
55
- for (const prefixEntry of prefixMap.entries()) {
56
- n3WriterPrefixes[prefixEntry[0]] = prefixEntry[1].value;
57
- }
58
- const n3Writer = new Writer({
59
- format: "text/turtle",
60
- prefixes: n3WriterPrefixes,
61
- });
62
- for (const quad of validationReport.dataset) {
63
- n3Writer.addQuad(quad);
64
- }
65
- n3Writer.end((_error, result) => process.stderr.write(result));
66
- return;
67
- }
68
- }
69
- ShapesGraph.builder()
70
- .parseDataset(dataset, { prefixMap })
71
- .map((_) => _.build())
72
- .chain((shapesGraph) => new Compiler({ generator }).compile(shapesGraph))
73
- .ifLeft((error) => {
74
- throw error;
75
- })
76
- .ifRight((output) => {
77
- if (outputFilePath.length === 0) {
78
- process.stdout.write(output);
79
- }
80
- else {
81
- fs.writeFileSync(outputFilePath, output);
82
- }
83
- });
84
- }
85
20
  run(subcommands({
86
21
  cmds: {
87
22
  generate: subcommands({
23
+ name: "generate",
88
24
  cmds: {
89
25
  "ast-json": command({
90
26
  name: "ast-json",
91
27
  description: "generate AST JSON for the SHACL shapes graph",
92
28
  args: {
93
- inputFilePaths,
29
+ inputPaths,
94
30
  outputFilePath,
95
31
  },
96
- handler: async ({ inputFilePaths, outputFilePath }) => {
97
- generate({
32
+ handler: async ({ inputPaths, outputFilePath }) => {
33
+ (await generate({
98
34
  generator: new AstJsonGenerator(),
99
- inputFilePaths,
35
+ inputPaths,
100
36
  outputFilePath,
101
- });
37
+ })).unsafeCoerce();
102
38
  },
103
39
  }),
104
40
  ts: command({
105
41
  name: "ts",
106
42
  description: "generate TypeScript for the SHACL shapes graph",
107
43
  args: {
108
- inputFilePaths,
44
+ inputPaths,
109
45
  outputFilePath,
110
46
  },
111
- handler: async ({ inputFilePaths, outputFilePath }) => {
112
- generate({
47
+ handler: async ({ inputPaths, outputFilePath }) => {
48
+ (await generate({
113
49
  generator: new TsGenerator(),
114
- inputFilePaths,
50
+ inputPaths,
115
51
  outputFilePath,
116
- });
52
+ })).unsafeCoerce();
117
53
  },
118
54
  }),
119
55
  zod: command({
120
56
  name: "zod",
121
57
  description: "generate Zod schemas for the SHACL shapes graph",
122
58
  args: {
123
- inputFilePaths,
59
+ inputPaths,
124
60
  outputFilePath,
125
61
  },
126
- handler: async ({ inputFilePaths, outputFilePath }) => {
127
- generate({
62
+ handler: async ({ inputPaths, outputFilePath }) => {
63
+ (await generate({
128
64
  generator: new ZodGenerator(),
129
- inputFilePaths,
65
+ inputPaths,
130
66
  outputFilePath,
131
- });
67
+ })).unsafeCoerce();
132
68
  },
133
69
  }),
134
70
  },
135
- name: "generate",
71
+ }),
72
+ merge: command({
73
+ name: "merge",
74
+ description: "merge one or more RDF files",
75
+ args: {
76
+ inputPaths,
77
+ outputFilePath,
78
+ },
79
+ handler: async ({ inputPaths, outputFilePath }) => {
80
+ (await merge({
81
+ inputPaths,
82
+ outputFilePath,
83
+ })).unsafeCoerce();
84
+ },
85
+ }),
86
+ validate: command({
87
+ name: "validate",
88
+ description: "validate a data graph with a shapes graph",
89
+ args: {
90
+ dataGraphPaths: restPositionals({
91
+ description: "path(s) to a file or directory of data graph files",
92
+ type: ExistingPath,
93
+ }),
94
+ shapesGraphPath: option({
95
+ description: "path to a file or directory of shapes graph files",
96
+ short: "s",
97
+ long: "--shapes-graph",
98
+ type: ExistingPath,
99
+ }),
100
+ },
101
+ handler: async ({ dataGraphPaths, shapesGraphPath }) => {
102
+ (await validate({
103
+ dataGraphPaths,
104
+ shapesGraphPaths: [shapesGraphPath],
105
+ })).unsafeCoerce();
106
+ },
136
107
  }),
137
108
  },
138
109
  description: "shaclmate command line interface",
@@ -0,0 +1,8 @@
1
+ import type { Generator } from "@shaclmate/compiler";
2
+ import { type Either } from "purify-ts";
3
+ export declare function generate({ generator, inputPaths, outputFilePath, }: {
4
+ generator: Generator;
5
+ inputPaths: readonly string[];
6
+ outputFilePath: string;
7
+ }): Promise<Either<Error, void>>;
8
+ //# sourceMappingURL=generate.d.ts.map
@@ -0,0 +1,36 @@
1
+ import * as fs from "node:fs";
2
+ import Serializer from "@rdfjs/serializer-turtle";
3
+ import { Compiler, ShapesGraph } from "@shaclmate/compiler";
4
+ import { EitherAsync } from "purify-ts";
5
+ import SHACLValidator from "rdf-validate-shacl";
6
+ import { parseInputs } from "./parseInputs.js";
7
+ import { shaclShaclDataset } from "./shaclShaclDataset.js";
8
+ export async function generate({ generator, inputPaths, outputFilePath, }) {
9
+ return EitherAsync(async ({ liftEither }) => {
10
+ if (inputPaths.length === 0) {
11
+ throw new Error("must specify at least one input shapes graph file path");
12
+ }
13
+ const { dataset, prefixMap } = await liftEither(await parseInputs(inputPaths));
14
+ {
15
+ const validationReport = await new SHACLValidator(shaclShaclDataset, {}).validate(dataset);
16
+ if (!validationReport.conforms) {
17
+ process.stderr.write("input is not valid SHACL:\n");
18
+ process.stderr.write(new Serializer({
19
+ prefixes: prefixMap,
20
+ }).transform(validationReport.dataset));
21
+ return;
22
+ }
23
+ }
24
+ const output = await liftEither(ShapesGraph.builder()
25
+ .parseDataset(dataset, { prefixMap })
26
+ .map((_) => _.build())
27
+ .chain((shapesGraph) => new Compiler({ generator }).compile(shapesGraph)));
28
+ if (outputFilePath.length === 0) {
29
+ process.stdout.write(output);
30
+ }
31
+ else {
32
+ await fs.promises.writeFile(outputFilePath, output);
33
+ }
34
+ });
35
+ }
36
+ //# sourceMappingURL=generate.js.map
@@ -0,0 +1,6 @@
1
+ import { type Either } from "purify-ts";
2
+ export declare function merge({ inputPaths, outputFilePath, }: {
3
+ inputPaths: readonly string[];
4
+ outputFilePath: string;
5
+ }): Promise<Either<Error, void>>;
6
+ //# sourceMappingURL=merge.d.ts.map
@@ -0,0 +1,17 @@
1
+ import fs from "node:fs";
2
+ import Serializer from "@rdfjs/serializer-turtle";
3
+ import { EitherAsync } from "purify-ts";
4
+ import { parseInputs } from "./parseInputs.js";
5
+ export async function merge({ inputPaths, outputFilePath, }) {
6
+ return EitherAsync(async ({ liftEither }) => {
7
+ const { dataset, prefixMap } = await liftEither(await parseInputs(inputPaths));
8
+ const output = new Serializer({ prefixes: prefixMap }).transform(dataset);
9
+ if (outputFilePath.length === 0) {
10
+ process.stdout.write(output);
11
+ }
12
+ else {
13
+ await fs.promises.writeFile(outputFilePath, output);
14
+ }
15
+ });
16
+ }
17
+ //# sourceMappingURL=merge.js.map
@@ -0,0 +1,8 @@
1
+ import PrefixMap from "@rdfjs/prefix-map/PrefixMap.js";
2
+ import type { DatasetCore } from "@rdfjs/types";
3
+ import { Either } from "purify-ts";
4
+ export declare function parseInputs(inputPaths: readonly string[]): Promise<Either<Error, {
5
+ dataset: DatasetCore;
6
+ prefixMap: PrefixMap;
7
+ }>>;
8
+ //# sourceMappingURL=parseInputs.d.ts.map
@@ -0,0 +1,49 @@
1
+ import DataFactory from "@rdfjs/data-model";
2
+ import DatasetFactory from "@rdfjs/dataset";
3
+ import PrefixMap from "@rdfjs/prefix-map/PrefixMap.js";
4
+ import { RdfDirectory, RdfFileSystemEntry } from "@rdfx/fs";
5
+ import { Either, EitherAsync, Left } from "purify-ts";
6
+ import { logger } from "../logger.js";
7
+ export async function parseInputs(inputPaths) {
8
+ return EitherAsync(async ({ liftEither }) => {
9
+ const dataset = DatasetFactory.dataset();
10
+ const prefixMapInit = [];
11
+ for (const inputPath of inputPaths) {
12
+ const parseInputFileSystemEntry = async (inputFileSystemEntry) => {
13
+ if (inputFileSystemEntry instanceof RdfDirectory) {
14
+ for await (const file of inputFileSystemEntry.files({
15
+ recursive: true,
16
+ })) {
17
+ await parseInputFileSystemEntry(file);
18
+ }
19
+ return;
20
+ }
21
+ const inputFile = inputFileSystemEntry;
22
+ await liftEither(await new Promise((resolve) => {
23
+ const inputQuadStream = inputFile.parse();
24
+ inputQuadStream.on("data", (quad) => dataset.add(quad));
25
+ inputQuadStream.on("end", () => resolve(Either.of(null)));
26
+ inputQuadStream.on("error", (error) => resolve(Left(error)));
27
+ inputQuadStream.on("prefix", (prefix, prefixNode) => {
28
+ const existingPrefixMapEntry = prefixMapInit.find((prefixMapEntry) => prefixMapEntry[0] === prefix ||
29
+ prefixMapEntry[1].equals(prefixNode));
30
+ if (existingPrefixMapEntry) {
31
+ if (existingPrefixMapEntry[0] !== prefix ||
32
+ !existingPrefixMapEntry[1].equals(prefixNode)) {
33
+ logger.warn("conflicting prefix %s: %s", prefix, prefixNode.value);
34
+ }
35
+ return;
36
+ }
37
+ prefixMapInit.push([prefix, prefixNode]);
38
+ });
39
+ }));
40
+ };
41
+ await parseInputFileSystemEntry(await liftEither(await RdfFileSystemEntry.fromPath(inputPath)));
42
+ }
43
+ return {
44
+ dataset,
45
+ prefixMap: new PrefixMap(prefixMapInit, { factory: DataFactory }),
46
+ };
47
+ });
48
+ }
49
+ //# sourceMappingURL=parseInputs.js.map
@@ -0,0 +1,6 @@
1
+ import { type Either } from "purify-ts";
2
+ export declare function validate({ dataGraphPaths, shapesGraphPaths, }: {
3
+ dataGraphPaths: readonly string[];
4
+ shapesGraphPaths: readonly string[];
5
+ }): Promise<Either<Error, void>>;
6
+ //# sourceMappingURL=validate.d.ts.map
@@ -0,0 +1,94 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import Serializer from "@rdfjs/serializer-turtle";
5
+ import { EitherAsync } from "purify-ts";
6
+ import SHACLValidator from "rdf-validate-shacl";
7
+ import * as tmp from "tmp-promise";
8
+ import which from "which";
9
+ import { logger } from "../logger.js";
10
+ import { parseInputs } from "./parseInputs.js";
11
+ function execFileStreaming(cmd, args) {
12
+ return new Promise((resolve) => {
13
+ const child = spawn(cmd, args, { stdio: "inherit" });
14
+ child.on("close", (code) => {
15
+ resolve(code);
16
+ });
17
+ });
18
+ }
19
+ export async function validate({ dataGraphPaths, shapesGraphPaths, }) {
20
+ return EitherAsync(async ({ liftEither }) => {
21
+ const { dataset: dataGraphDataset, prefixMap: dataGraphPrefixMap } = await liftEither(await parseInputs(dataGraphPaths));
22
+ if (dataGraphDataset.size === 0) {
23
+ throw new Error("data graph is empty!");
24
+ }
25
+ logger.info("data graph size: %d", dataGraphDataset.size);
26
+ const { dataset: shapesGraphDataset } = await liftEither(await parseInputs(shapesGraphPaths));
27
+ if (shapesGraphDataset.size === 0) {
28
+ throw new Error("shapes graph is empty!");
29
+ }
30
+ logger.info("shapes graph size: %d", shapesGraphDataset.size);
31
+ const serializer = new Serializer({
32
+ prefixes: dataGraphPrefixMap,
33
+ });
34
+ logger.info("validating with rdf-shacl-validate");
35
+ const validationReport = await new SHACLValidator(shapesGraphDataset, {}).validate(dataGraphDataset);
36
+ if (validationReport.conforms) {
37
+ logger.info("validated with rdf-shacl-validate: conforms");
38
+ }
39
+ else {
40
+ logger.info("validated with rdf-shacl-validate: does not conform");
41
+ process.stderr.write(serializer.transform(validationReport.dataset));
42
+ return;
43
+ }
44
+ const jenaShaclFilePath = await which("shacl", { nothrow: true });
45
+ const pyshaclFilePath = await which("pyshacl", { nothrow: true });
46
+ if (jenaShaclFilePath === null && pyshaclFilePath === null) {
47
+ logger.debug("neither Jena nor pyshacl found on PATH");
48
+ return;
49
+ }
50
+ await tmp.withDir(async ({ path: tmpDirectoryPath }) => {
51
+ const dataGraphFilePath = path.join(tmpDirectoryPath, "data.ttl");
52
+ logger.debug("writing data graph to %s", dataGraphFilePath);
53
+ await fs.writeFile(dataGraphFilePath, serializer.transform(dataGraphDataset));
54
+ logger.debug("wrote data graph to %s", dataGraphFilePath);
55
+ const shapesGraphFilePath = path.join(tmpDirectoryPath, "shapes.ttl");
56
+ logger.debug("writing shapes graph to %s", shapesGraphFilePath);
57
+ await fs.writeFile(shapesGraphFilePath, serializer.transform(shapesGraphDataset));
58
+ logger.debug("wrote shapes graph to %s", shapesGraphFilePath);
59
+ if (jenaShaclFilePath !== null) {
60
+ const args = [
61
+ "validate",
62
+ "--data",
63
+ dataGraphFilePath,
64
+ "--shapes",
65
+ shapesGraphFilePath,
66
+ ];
67
+ logger.info("validating with Jena (args=%s)", args);
68
+ const code = await execFileStreaming(jenaShaclFilePath, args);
69
+ logger.info("validated with Jena: %s", code === 0 ? "conforms" : "does not conform");
70
+ if (code !== 0) {
71
+ return;
72
+ }
73
+ }
74
+ else {
75
+ logger.info("Jena not found on PATH, skipping");
76
+ }
77
+ if (pyshaclFilePath !== null) {
78
+ const args = ["-s", shapesGraphFilePath, dataGraphFilePath];
79
+ logger.info("validating with pyshacl (args=%s)", args);
80
+ const code = await execFileStreaming(pyshaclFilePath, args);
81
+ logger.info("validated with pyshacl: %s", code === 0 ? "conforms" : "does not conform");
82
+ if (code !== 0) {
83
+ return;
84
+ }
85
+ }
86
+ else {
87
+ logger.info("pyshacl not found on PATH, skipping");
88
+ }
89
+ }, {
90
+ unsafeCleanup: true,
91
+ });
92
+ });
93
+ }
94
+ //# sourceMappingURL=validate.js.map
@@ -0,0 +1,2 @@
1
+ export declare const logger: import("pino").Logger<never>;
2
+ //# sourceMappingURL=logger.d.ts.map
package/dist/logger.js ADDED
@@ -0,0 +1,14 @@
1
+ import { pino } from "pino";
2
+ export const logger = pino({
3
+ level: process.env["NODE_ENV"] === "development" ||
4
+ process.env["NODE_ENV"] === "test"
5
+ ? "debug"
6
+ : "info",
7
+ transport: {
8
+ target: "pino-pretty",
9
+ options: {
10
+ colorize: true,
11
+ },
12
+ },
13
+ }, pino["destination"] ? pino.destination(2) : undefined);
14
+ //# sourceMappingURL=logger.js.map
package/package.json CHANGED
@@ -3,22 +3,34 @@
3
3
  "shaclmate": "dist/cli.js"
4
4
  },
5
5
  "dependencies": {
6
- "@shaclmate/compiler": "4.0.16",
6
+ "@shaclmate/compiler": "4.0.18",
7
+ "@rdfjs/data-model": "~2.1.1",
8
+ "@rdfjs/dataset": "~2.0.2",
7
9
  "@rdfjs/prefix-map": "~0.1.2",
8
- "@rdfjs/types": "~2.0.1",
9
- "@types/n3": "~1.26.0",
10
- "@types/rdfjs__prefix-map": "~0.1.5",
11
- "cmd-ts": "~0.13.0",
12
- "n3": "~1.26.0",
10
+ "@rdfjs/serializer-turtle": "~1.1.5",
11
+ "@rdfx/fs": "0.0.7",
12
+ "cmd-ts": "~0.15.0",
13
13
  "pino": "~9.1.0",
14
- "rdf-validate-shacl": "0.5.8"
14
+ "pino-pretty": "~13.1.2",
15
+ "purify-ts": "~2.1.4",
16
+ "rdf-validate-shacl": "0.6.5",
17
+ "tmp-promise": "~3.0.3",
18
+ "which": "~6.0.1"
15
19
  },
16
20
  "description": "Command line program to generate TypeScript code from SHACL shapes",
17
- "devDependencies": {},
21
+ "devDependencies": {
22
+ "@rdfjs/types": "~2.0.1",
23
+ "@types/rdfjs__data-model": "~2.0.9",
24
+ "@types/rdfjs__dataset": "~2.0.7",
25
+ "@types/rdfjs__prefix-map": "~0.1.5",
26
+ "@types/which": "~3.0.4"
27
+ },
18
28
  "files": [
19
29
  "README.md",
20
30
  "dist/*.d.ts",
21
- "dist/*.js"
31
+ "dist/*.js",
32
+ "dist/commands/*.d.ts",
33
+ "dist/commands/*.js"
22
34
  ],
23
35
  "homepage": "https://github.com/minorg/shaclmate",
24
36
  "keywords": [
@@ -44,5 +56,5 @@
44
56
  },
45
57
  "type": "module",
46
58
  "types": "./dist/index.d.ts",
47
- "version": "4.0.16"
59
+ "version": "4.0.18"
48
60
  }