@shaclmate/cli 4.0.17 → 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,26 +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 { RdfDirectory, RdfFileSystemEntry } from "@rdfx/fs";
5
- import { AstJsonGenerator, Compiler, ShapesGraph, TsGenerator, ZodGenerator, } from "@shaclmate/compiler";
2
+ import { AstJsonGenerator, TsGenerator, ZodGenerator, } from "@shaclmate/compiler";
6
3
  import { command, option, restPositionals, run, string, subcommands, } from "cmd-ts";
7
4
  import { ExistingPath } from "cmd-ts/dist/cjs/batteries/fs.js";
8
- import { DataFactory, Parser, Store, Writer } from "n3";
9
- import { pino } from "pino";
10
- import { EitherAsync } from "purify-ts";
11
- import SHACLValidator from "rdf-validate-shacl";
12
- import { shaclShaclDataset } from "./shaclShaclDataset.js";
5
+ import { generate } from "./commands/generate.js";
6
+ import { merge } from "./commands/merge.js";
7
+ import { validate } from "./commands/validate.js";
13
8
  const inputPaths = restPositionals({
14
9
  displayName: "inputPaths",
15
10
  description: "paths to RDF files or directories of RDF files containing SHACL shapes",
16
11
  type: ExistingPath,
17
12
  });
18
- const logger = pino({
19
- level: process.env["NODE_ENV"] === "development" ||
20
- process.env["NODE_ENV"] === "test"
21
- ? "debug"
22
- : "info",
23
- }, pino["destination"] ? pino.destination(2) : undefined);
24
13
  const outputFilePath = option({
25
14
  defaultValue: () => "",
26
15
  description: "path to a file to write output to; if not specified, write to stdout",
@@ -28,84 +17,10 @@ const outputFilePath = option({
28
17
  short: "o",
29
18
  type: string,
30
19
  });
31
- function generate({ generator, inputFiles, outputFilePath, }) {
32
- if (inputFiles.length === 0) {
33
- throw new Error("must specify at least one input shapes graph file path");
34
- }
35
- const inputParser = new Parser();
36
- const dataset = new Store();
37
- const iriPrefixes = [];
38
- for (const inputFile of inputFiles) {
39
- dataset.addQuads(inputParser.parse(fs.readFileSync(inputFile.path).toString(), null, (prefix, prefixNode) => {
40
- const existingIriPrefix = iriPrefixes.find((iriPrefix) => iriPrefix[0] === prefix || iriPrefix[1].equals(prefixNode));
41
- if (existingIriPrefix) {
42
- if (existingIriPrefix[0] !== prefix ||
43
- !existingIriPrefix[1].equals(prefixNode)) {
44
- logger.warn("conflicting prefix %s: %s", prefix, prefixNode.value);
45
- }
46
- return;
47
- }
48
- iriPrefixes.push([prefix, prefixNode]);
49
- }));
50
- }
51
- const prefixMap = new PrefixMap(iriPrefixes, { factory: DataFactory });
52
- {
53
- const validationReport = new SHACLValidator(shaclShaclDataset, {}).validate(dataset);
54
- if (!validationReport.conforms) {
55
- process.stderr.write("input is not valid SHACL:\n");
56
- const n3WriterPrefixes = {};
57
- for (const prefixEntry of prefixMap.entries()) {
58
- n3WriterPrefixes[prefixEntry[0]] = prefixEntry[1].value;
59
- }
60
- const n3Writer = new Writer({
61
- format: "text/turtle",
62
- prefixes: n3WriterPrefixes,
63
- });
64
- for (const quad of validationReport.dataset) {
65
- n3Writer.addQuad(quad);
66
- }
67
- n3Writer.end((_error, result) => process.stderr.write(result));
68
- return;
69
- }
70
- }
71
- ShapesGraph.builder()
72
- .parseDataset(dataset, { prefixMap })
73
- .map((_) => _.build())
74
- .chain((shapesGraph) => new Compiler({ generator }).compile(shapesGraph))
75
- .ifLeft((error) => {
76
- throw error;
77
- })
78
- .ifRight((output) => {
79
- if (outputFilePath.length === 0) {
80
- process.stdout.write(output);
81
- }
82
- else {
83
- fs.writeFileSync(outputFilePath, output);
84
- }
85
- });
86
- }
87
- async function resolveInputPaths(inputPaths) {
88
- return EitherAsync(async ({ liftEither }) => {
89
- const inputFiles = [];
90
- for (const inputPath of inputPaths) {
91
- const fileSystemEntry = await liftEither(await RdfFileSystemEntry.fromPath(inputPath));
92
- if (fileSystemEntry instanceof RdfDirectory) {
93
- for await (const inputFile of fileSystemEntry.files({
94
- recursive: true,
95
- })) {
96
- inputFiles.push(inputFile);
97
- }
98
- }
99
- else {
100
- inputFiles.push(fileSystemEntry);
101
- }
102
- }
103
- return inputFiles;
104
- });
105
- }
106
20
  run(subcommands({
107
21
  cmds: {
108
22
  generate: subcommands({
23
+ name: "generate",
109
24
  cmds: {
110
25
  "ast-json": command({
111
26
  name: "ast-json",
@@ -115,11 +30,11 @@ run(subcommands({
115
30
  outputFilePath,
116
31
  },
117
32
  handler: async ({ inputPaths, outputFilePath }) => {
118
- generate({
33
+ (await generate({
119
34
  generator: new AstJsonGenerator(),
120
- inputFiles: (await resolveInputPaths(inputPaths)).unsafeCoerce(),
35
+ inputPaths,
121
36
  outputFilePath,
122
- });
37
+ })).unsafeCoerce();
123
38
  },
124
39
  }),
125
40
  ts: command({
@@ -130,11 +45,11 @@ run(subcommands({
130
45
  outputFilePath,
131
46
  },
132
47
  handler: async ({ inputPaths, outputFilePath }) => {
133
- generate({
48
+ (await generate({
134
49
  generator: new TsGenerator(),
135
- inputFiles: (await resolveInputPaths(inputPaths)).unsafeCoerce(),
50
+ inputPaths,
136
51
  outputFilePath,
137
- });
52
+ })).unsafeCoerce();
138
53
  },
139
54
  }),
140
55
  zod: command({
@@ -145,15 +60,50 @@ run(subcommands({
145
60
  outputFilePath,
146
61
  },
147
62
  handler: async ({ inputPaths, outputFilePath }) => {
148
- generate({
63
+ (await generate({
149
64
  generator: new ZodGenerator(),
150
- inputFiles: (await resolveInputPaths(inputPaths)).unsafeCoerce(),
65
+ inputPaths,
151
66
  outputFilePath,
152
- });
67
+ })).unsafeCoerce();
153
68
  },
154
69
  }),
155
70
  },
156
- 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
+ },
157
107
  }),
158
108
  },
159
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,24 +3,34 @@
3
3
  "shaclmate": "dist/cli.js"
4
4
  },
5
5
  "dependencies": {
6
- "@shaclmate/compiler": "4.0.17",
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",
10
+ "@rdfjs/serializer-turtle": "~1.1.5",
9
11
  "@rdfx/fs": "0.0.7",
10
- "@types/n3": "~1.26.0",
11
- "@types/rdfjs__prefix-map": "~0.1.5",
12
- "cmd-ts": "~0.13.0",
13
- "n3": "~1.26.0",
12
+ "cmd-ts": "~0.15.0",
14
13
  "pino": "~9.1.0",
14
+ "pino-pretty": "~13.1.2",
15
15
  "purify-ts": "~2.1.4",
16
- "rdf-validate-shacl": "0.5.8"
16
+ "rdf-validate-shacl": "0.6.5",
17
+ "tmp-promise": "~3.0.3",
18
+ "which": "~6.0.1"
17
19
  },
18
20
  "description": "Command line program to generate TypeScript code from SHACL shapes",
19
- "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
+ },
20
28
  "files": [
21
29
  "README.md",
22
30
  "dist/*.d.ts",
23
- "dist/*.js"
31
+ "dist/*.js",
32
+ "dist/commands/*.d.ts",
33
+ "dist/commands/*.js"
24
34
  ],
25
35
  "homepage": "https://github.com/minorg/shaclmate",
26
36
  "keywords": [
@@ -46,5 +56,5 @@
46
56
  },
47
57
  "type": "module",
48
58
  "types": "./dist/index.d.ts",
49
- "version": "4.0.17"
59
+ "version": "4.0.18"
50
60
  }