diff --git a/AGENTS.md b/AGENTS.md index c11c9083..63af1cb9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -99,7 +99,7 @@ The `@nx/js:library` generator’s output diverges from the conventions in this - `name` → `@lde/` - `description` — write something useful - `repository.directory` → `packages/` - - `version` → `0.0.0` from the get-go (do NOT keep the sibling’s version). nx release bumps from there via conventional commits, so the introducing `feat:` commit lands the first release at `0.1.0`; any higher starting version overshoots it. This must be in place before the PR merges — see [Releasing a new package](#releasing-a-new-package). + - `version` → `0.1.0` from the get-go (do NOT keep the sibling’s version). nx release bumps from there via conventional commits, so the introducing `feat:` commit lands the first release at `0.1.0`; any higher starting version overshoots it. This must be in place before the PR merges — see [Releasing a new package](#releasing-a-new-package). - `dependencies` and `peerDependencies` — replace with what the new package actually needs 4. **Replace the source.** Empty out `src/` and `test/`, write the new code. 5. **Update `tsconfig.lib.json` `references`** to match the new package’s actual `@lde/*` peers. @@ -129,8 +129,6 @@ For releasing the new package’s first version, see [Releasing a new package](# `.github/workflows/release.yml` publishes existing packages on every push to main, but the CI workflow alone cannot bring up a brand-new `@lde/` package: npm’s Trusted Publisher configuration can only be added to a package that already exists on the registry. The first version has to be published manually by a maintainer; CI takes over from the second version onwards. -The package’s `version` must already be `0.0.0` before the PR merges (set in [Creating New Packages](#creating-new-packages) step 3) so the introducing `feat:` commit publishes `0.1.0`. - One-time bootstrap for a new package (do this once it has been merged to main): 1. **Publish the first version manually.** From a maintainer’s machine: diff --git a/README.md b/README.md index 627c6bf2..497cfeeb 100644 --- a/README.md +++ b/README.md @@ -127,6 +127,11 @@ await pipeline.run(); npm Import data dumps to a local SPARQL endpoint for querying + + @lde/sparql-anything + npm + Convert tabular and other non-RDF sources to RDF with the SPARQL Anything CLI + Publication – Serve and document your data @lde/fastify-rdf @@ -222,6 +227,7 @@ graph TD distribution-probe --> dataset pipeline --> distribution-probe sparql-importer --> dataset + sparql-anything --> task-runner distribution-health --> distribution-probe distribution-health --> sparql-importer end diff --git a/package-lock.json b/package-lock.json index 833e9344..d772a60d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24956,6 +24956,10 @@ "resolved": "packages/search-typesense", "link": true }, + "node_modules/@lde/sparql-anything": { + "resolved": "packages/sparql-anything", + "link": true + }, "node_modules/@lde/sparql-importer": { "resolved": "packages/sparql-importer", "link": true @@ -40828,7 +40832,7 @@ }, "packages/dataset-registry-client": { "name": "@lde/dataset-registry-client", - "version": "0.8.3", + "version": "0.8.4", "license": "MIT", "dependencies": { "@lde/dataset": "0.7.7", @@ -40854,10 +40858,10 @@ }, "packages/distribution-health": { "name": "@lde/distribution-health", - "version": "0.1.4", + "version": "0.2.0", "license": "MIT", "dependencies": { - "@lde/distribution-probe": "0.1.12", + "@lde/distribution-probe": "0.2.0", "@lde/sparql-importer": "0.6.5", "tslib": "^2.3.0" }, @@ -40867,11 +40871,11 @@ }, "packages/distribution-monitor": { "name": "@lde/distribution-monitor", - "version": "0.1.15", + "version": "0.2.0", "license": "MIT", "dependencies": { "@lde/dataset": "0.7.7", - "@lde/distribution-probe": "0.1.12", + "@lde/distribution-probe": "0.2.0", "c12": "^3.3.4", "commander": "^15.0.0", "cron": "^4.1.0", @@ -40898,7 +40902,7 @@ }, "packages/distribution-probe": { "name": "@lde/distribution-probe", - "version": "0.1.12", + "version": "0.2.0", "license": "MIT", "dependencies": { "@lde/dataset": "0.7.7", @@ -41568,7 +41572,7 @@ }, "packages/docgen": { "name": "@lde/docgen", - "version": "0.6.17", + "version": "0.6.18", "license": "MIT", "dependencies": { "@tpluscode/rdf-ns-builders": "^5.0.0", @@ -41598,7 +41602,7 @@ }, "packages/fastify-rdf": { "name": "@lde/fastify-rdf", - "version": "0.4.5", + "version": "0.4.6", "license": "MIT", "dependencies": { "@fastify/accepts": "^5.0.0", @@ -42317,13 +42321,13 @@ }, "packages/pipeline": { "name": "@lde/pipeline", - "version": "0.30.21", + "version": "0.31.0", "license": "MIT", "dependencies": { "@lde/dataset": "0.7.7", - "@lde/dataset-registry-client": "0.8.3", - "@lde/distribution-health": "0.1.4", - "@lde/distribution-probe": "0.1.12", + "@lde/dataset-registry-client": "0.8.4", + "@lde/distribution-health": "0.2.0", + "@lde/distribution-probe": "0.2.0", "@lde/sparql-importer": "0.6.5", "@lde/sparql-server": "0.4.11", "@rdfjs/namespace": "^2.0.1", @@ -42343,7 +42347,7 @@ }, "packages/pipeline-console-reporter": { "name": "@lde/pipeline-console-reporter", - "version": "0.21.21", + "version": "0.22.0", "license": "MIT", "dependencies": { "chalk": "^5.4.1", @@ -42354,7 +42358,7 @@ }, "peerDependencies": { "@lde/dataset": "0.7.7", - "@lde/pipeline": "0.30.21" + "@lde/pipeline": "0.31.0" } }, "packages/pipeline-console-reporter/node_modules/ansi-regex": { @@ -42534,7 +42538,7 @@ }, "packages/pipeline-shacl-sampler": { "name": "@lde/pipeline-shacl-sampler", - "version": "0.4.22", + "version": "0.5.0", "license": "MIT", "dependencies": { "@rdfjs/types": "^2.0.1", @@ -42545,7 +42549,7 @@ }, "peerDependencies": { "@lde/dataset": "0.7.7", - "@lde/pipeline": "0.30.21" + "@lde/pipeline": "0.31.0" } }, "packages/pipeline-shacl-sampler/node_modules/n3": { @@ -42563,7 +42567,7 @@ }, "packages/pipeline-shacl-validator": { "name": "@lde/pipeline-shacl-validator", - "version": "0.12.22", + "version": "0.13.0", "license": "MIT", "dependencies": { "@rdfjs/types": "^2.0.1", @@ -42577,7 +42581,7 @@ }, "peerDependencies": { "@lde/dataset": "0.7.7", - "@lde/pipeline": "0.30.21" + "@lde/pipeline": "0.31.0" } }, "packages/pipeline-shacl-validator/node_modules/n3": { @@ -42596,7 +42600,7 @@ }, "packages/pipeline-void": { "name": "@lde/pipeline-void", - "version": "0.28.21", + "version": "0.29.0", "license": "MIT", "dependencies": { "@rdfjs/types": "^2.0.1", @@ -42607,7 +42611,7 @@ }, "peerDependencies": { "@lde/dataset": "0.7.7", - "@lde/pipeline": "0.30.21" + "@lde/pipeline": "0.31.0" } }, "packages/pipeline-void/node_modules/n3": { @@ -42664,7 +42668,7 @@ }, "packages/search": { "name": "@lde/search", - "version": "0.1.0", + "version": "0.1.1", "license": "MIT", "dependencies": { "@lde/text-normalization": "0.1.0", @@ -42704,6 +42708,15 @@ "node": ">=12.0" } }, + "packages/sparql-anything": { + "name": "@lde/sparql-anything", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "@lde/task-runner": "0.2.11", + "tslib": "^2.3.0" + } + }, "packages/sparql-importer": { "name": "@lde/sparql-importer", "version": "0.6.5", @@ -42717,7 +42730,7 @@ }, "packages/sparql-qlever": { "name": "@lde/sparql-qlever", - "version": "0.14.9", + "version": "0.14.10", "license": "MIT", "dependencies": { "@lde/dataset": "0.7.7", @@ -42727,7 +42740,7 @@ "@lde/task-runner": "0.2.11", "@lde/task-runner-docker": "0.2.13", "@lde/task-runner-native": "0.2.14", - "@lde/wait-for-sparql": "0.2.12", + "@lde/wait-for-sparql": "0.2.13", "rdf-parse": "^5.0.0", "rdf-serialize": "^5.1.0", "tslib": "^2.3.0", @@ -43478,7 +43491,7 @@ }, "packages/wait-for-sparql": { "name": "@lde/wait-for-sparql", - "version": "0.2.12", + "version": "0.2.13", "license": "MIT", "dependencies": { "fetch-sparql-endpoint": "^7.1.0", diff --git a/packages/sparql-anything/README.md b/packages/sparql-anything/README.md new file mode 100644 index 00000000..546e2a40 --- /dev/null +++ b/packages/sparql-anything/README.md @@ -0,0 +1,45 @@ +# SPARQL Anything + +Convert tabular and other non-RDF sources to RDF with the [SPARQL Anything](https://sparql-anything.cc) CLI. + +The `SparqlAnythingConverter` runs the SPARQL Anything jar **once per input chunk** to bound memory use, then concatenates the per-chunk N-Triples outputs into a single file. Processes are spawned through an [`@lde/task-runner`](../task-runner), so the same converter works on the host, in Docker, or anywhere else a `TaskRunner` is implemented. + +## Usage + +```typescript +import { SparqlAnythingConverter } from '@lde/sparql-anything'; +import { NativeTaskRunner } from '@lde/task-runner-native'; + +const converter = new SparqlAnythingConverter({ + queryFile: 'config/places.rq', // CONSTRUCT query; `{SOURCE}` is replaced per chunk + jarPath: 'bin/sparql-anything.jar', + adminCodesFile: 'data/admin-codes.ttl', // loaded into the default graph via --load + taskRunner: new NativeTaskRunner(), +}); + +await converter.convert( + ['data/geonames_aa.csv', 'data/geonames_ab.csv'], + 'output/geonames.nt', +); +``` + +For each chunk, the converter: + +1. Replaces the literal `{SOURCE}` in the query file with the chunk's path and writes the result to a temporary `.rq` file. +2. Runs `java -jar -q --load --format NT --output .nt`. +3. Waits for the process; a non-zero exit **aborts the whole conversion** so a crashed chunk can never be silently dropped from the output. + +Finally, the per-chunk `.nt` files are concatenated, in the order the chunks were given, into the output path. The concatenation streams, so multi-gigabyte outputs do not have to fit in memory. N-Triples has no prefixes or document structure, so concatenating per-chunk files always yields a single valid document. + +## Options + +| Option | Type | Description | +| ---------------- | ------------------ | -------------------------------------------------------------------------------- | +| `queryFile` | `string` | Path to the SPARQL CONSTRUCT query. The literal `{SOURCE}` is replaced per chunk | +| `jarPath` | `string` | Path to the SPARQL Anything CLI jar | +| `adminCodesFile` | `string` | Path to the Turtle file loaded into the default graph (`--load`) | +| `taskRunner` | `TaskRunner` | Runs the SPARQL Anything process for each chunk | + +## Chunking + +The converter consumes pre-split chunk files; it does not split sources itself. Producing the chunks (and the `adminCodesFile`) is the caller's responsibility. diff --git a/packages/sparql-anything/eslint.config.mjs b/packages/sparql-anything/eslint.config.mjs new file mode 100644 index 00000000..2dcaf60c --- /dev/null +++ b/packages/sparql-anything/eslint.config.mjs @@ -0,0 +1,22 @@ +import baseConfig from '../../eslint.config.mjs'; + +export default [ + ...baseConfig, + { + files: ['**/*.json'], + rules: { + '@nx/dependency-checks': [ + 'error', + { + ignoredFiles: [ + '{projectRoot}/eslint.config.{js,cjs,mjs}', + '{projectRoot}/vite.config.{js,ts,mjs,mts}', + ], + }, + ], + }, + languageOptions: { + parser: await import('jsonc-eslint-parser'), + }, + }, +]; diff --git a/packages/sparql-anything/package.json b/packages/sparql-anything/package.json new file mode 100644 index 00000000..3dcc1fba --- /dev/null +++ b/packages/sparql-anything/package.json @@ -0,0 +1,31 @@ +{ + "name": "@lde/sparql-anything", + "version": "0.1.0", + "description": "Convert tabular and other non-RDF sources to RDF with the SPARQL Anything CLI, running one process per input chunk via an @lde/task-runner.", + "repository": { + "url": "git+https://github.com/ldelements/lde.git", + "directory": "packages/sparql-anything" + }, + "license": "MIT", + "type": "module", + "exports": { + "./package.json": "./package.json", + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "development": "./src/index.ts", + "default": "./dist/index.js" + } + }, + "main": "./dist/index.js", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "files": [ + "dist", + "!**/*.tsbuildinfo" + ], + "dependencies": { + "@lde/task-runner": "0.2.11", + "tslib": "^2.3.0" + } +} diff --git a/packages/sparql-anything/src/index.ts b/packages/sparql-anything/src/index.ts new file mode 100644 index 00000000..07dce4d9 --- /dev/null +++ b/packages/sparql-anything/src/index.ts @@ -0,0 +1,4 @@ +export { + SparqlAnythingConverter, + type SparqlAnythingConverterOptions, +} from './sparql-anything-converter.js'; diff --git a/packages/sparql-anything/src/sparql-anything-converter.ts b/packages/sparql-anything/src/sparql-anything-converter.ts new file mode 100644 index 00000000..9d5e6b18 --- /dev/null +++ b/packages/sparql-anything/src/sparql-anything-converter.ts @@ -0,0 +1,92 @@ +import { TaskRunner } from '@lde/task-runner'; +import { createReadStream, createWriteStream } from 'node:fs'; +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { pipeline } from 'node:stream/promises'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; + +/** Placeholder in the query file that is replaced with each chunk's path. */ +const SOURCE_PLACEHOLDER = '{SOURCE}'; + +/** Configuration for a {@link SparqlAnythingConverter}. */ +export interface SparqlAnythingConverterOptions { + /** + * Path to the SPARQL CONSTRUCT query run for every chunk. The literal + * `{SOURCE}` is replaced with the chunk's path before each run. + */ + queryFile: string; + /** Path to the SPARQL Anything CLI jar. */ + jarPath: string; + /** Path to the Turtle file loaded into the default graph (`--load`). */ + adminCodesFile: string; + /** Runs the SPARQL Anything process for each chunk. */ + taskRunner: TaskRunner; +} + +/** + * Converts tabular (or other non-RDF) source chunks to N-Triples with the + * SPARQL Anything CLI, running one process per chunk to bound memory use, then + * concatenating the per-chunk outputs into a single file. + */ +export class SparqlAnythingConverter { + private readonly queryFile: string; + private readonly jarPath: string; + private readonly adminCodesFile: string; + private readonly taskRunner: TaskRunner; + + constructor(options: SparqlAnythingConverterOptions) { + this.queryFile = options.queryFile; + this.jarPath = options.jarPath; + this.adminCodesFile = options.adminCodesFile; + this.taskRunner = options.taskRunner; + } + + /** + * Converts each chunk to N-Triples and concatenates the results, in the order + * given, into `outputPath`. + */ + async convert(chunkPaths: string[], outputPath: string): Promise { + const query = await readFile(this.queryFile, 'utf-8'); + const tempDir = await mkdtemp(join(tmpdir(), 'sparql-anything-')); + try { + const chunkOutputs: string[] = []; + for (const [index, chunkPath] of chunkPaths.entries()) { + const queryPath = join(tempDir, `query-${index}.rq`); + await writeFile( + queryPath, + query.replaceAll(SOURCE_PLACEHOLDER, chunkPath), + ); + const chunkOutput = `${chunkPath}.nt`; + const task = await this.taskRunner.run( + `java -jar ${this.jarPath} -q ${queryPath} --load ${this.adminCodesFile} --format NT --output ${chunkOutput}`, + ); + // wait() rejects on a non-zero exit, aborting convert() before the + // crashed chunk's missing output can be silently concatenated. + await this.taskRunner.wait(task); + chunkOutputs.push(chunkOutput); + } + await concatenate(chunkOutputs, outputPath); + } finally { + await rm(tempDir, { recursive: true, force: true }); + } + } +} + +/** + * Concatenates `inputPaths` into `outputPath`, streaming so multi-GB outputs do + * not have to fit in memory. N-Triples has no prefixes or document structure, so + * concatenating per-chunk files yields a single valid document. + */ +async function concatenate( + inputPaths: string[], + outputPath: string, +): Promise { + const output = createWriteStream(outputPath); + try { + for (const inputPath of inputPaths) { + await pipeline(createReadStream(inputPath), output, { end: false }); + } + } finally { + output.end(); + } +} diff --git a/packages/sparql-anything/test/sparql-anything-converter.test.ts b/packages/sparql-anything/test/sparql-anything-converter.test.ts new file mode 100644 index 00000000..87261550 --- /dev/null +++ b/packages/sparql-anything/test/sparql-anything-converter.test.ts @@ -0,0 +1,166 @@ +import { SparqlAnythingConverter } from '../src/index.js'; +import { TaskRunner } from '@lde/task-runner'; +import { describe, it, expect, beforeEach, afterEach } from 'vitest'; +import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { tmpdir } from 'node:os'; + +/** + * Records the commands it is asked to run and simulates SPARQL Anything: + * it captures the query passed via `-q ` and writes the `--output ` + * the converter later concatenates. The output content is the output path + * itself, so concatenation order is observable. + */ +class FakeTaskRunner implements TaskRunner<{ command: string }> { + readonly commands: string[] = []; + readonly queries: string[] = []; + + /** When set, `wait()` rejects for commands whose output path contains this. */ + constructor(private readonly failOutputContaining?: string) {} + + async run(command: string): Promise<{ command: string }> { + this.commands.push(command); + const queryFile = tokenAfter(command, '-q'); + if (queryFile) { + this.queries.push(await readFile(queryFile, 'utf-8')); + } + const outputFile = tokenAfter(command, '--output'); + if (outputFile) { + await writeFile(outputFile, `${outputFile}\n`); + } + return { command }; + } + + async wait(task: { command: string }): Promise { + if ( + this.failOutputContaining && + task.command.includes(this.failOutputContaining) + ) { + throw new Error('Process failed with code 1'); + } + return ''; + } + + async stop(): Promise { + return null; + } +} + +/** Reads the whitespace-delimited token following `flag` in a command string. */ +function tokenAfter(command: string, flag: string): string | undefined { + const tokens = command.split(/\s+/); + const index = tokens.indexOf(flag); + return index >= 0 ? tokens[index + 1] : undefined; +} + +describe('SparqlAnythingConverter', () => { + let workDir: string; + let queryFile: string; + + beforeEach(async () => { + workDir = await mkdtemp(join(tmpdir(), 'sparql-anything-test-')); + queryFile = join(workDir, 'places.rq'); + await writeFile( + queryFile, + 'CONSTRUCT { ?s ?p ?o } WHERE { fx:location "{SOURCE}" }', + ); + }); + + afterEach(async () => { + await rm(workDir, { recursive: true, force: true }); + }); + + it('runs SPARQL Anything for a chunk with the SPARQL Anything CLI contract', async () => { + const taskRunner = new FakeTaskRunner(); + const converter = new SparqlAnythingConverter({ + queryFile, + jarPath: '/bin/sparql-anything.jar', + adminCodesFile: '/data/admin-codes.ttl', + taskRunner, + }); + const chunk = join(workDir, 'geonames_aa.csv'); + await writeFile(chunk, 'header\nrow'); + + await converter.convert([chunk], join(workDir, 'geonames.nt')); + + expect(taskRunner.commands).toHaveLength(1); + const command = taskRunner.commands[0]; + expect(command).toContain('java -jar /bin/sparql-anything.jar'); + expect(command).toContain('--load /data/admin-codes.ttl'); + expect(command).toContain('--format NT'); + expect(command).toContain(`--output ${chunk}.nt`); + expect(command).toMatch(/-q \S+\.rq/); + }); + + it('substitutes the chunk path into the query, leaving no placeholder', async () => { + const taskRunner = new FakeTaskRunner(); + const converter = new SparqlAnythingConverter({ + queryFile, + jarPath: '/bin/sparql-anything.jar', + adminCodesFile: '/data/admin-codes.ttl', + taskRunner, + }); + const chunk = join(workDir, 'geonames_aa.csv'); + await writeFile(chunk, 'header\nrow'); + + await converter.convert([chunk], join(workDir, 'geonames.nt')); + + expect(taskRunner.queries).toHaveLength(1); + expect(taskRunner.queries[0]).toContain(`fx:location "${chunk}"`); + expect(taskRunner.queries[0]).not.toContain('{SOURCE}'); + }); + + it('runs every chunk and concatenates their outputs in order', async () => { + const taskRunner = new FakeTaskRunner(); + const converter = new SparqlAnythingConverter({ + queryFile, + jarPath: '/bin/sparql-anything.jar', + adminCodesFile: '/data/admin-codes.ttl', + taskRunner, + }); + const chunks = [ + join(workDir, 'geonames_aa.csv'), + join(workDir, 'geonames_ab.csv'), + join(workDir, 'geonames_ac.csv'), + ]; + for (const chunk of chunks) { + await writeFile(chunk, 'header\nrow'); + } + const outputPath = join(workDir, 'geonames.nt'); + + await converter.convert(chunks, outputPath); + + expect(taskRunner.commands).toHaveLength(3); + // The FakeTaskRunner writes each chunk's `.nt` path as that file's content, + // so the concatenated output reflects the order the chunks were processed. + const output = await readFile(outputPath, 'utf-8'); + expect(output).toBe(chunks.map((chunk) => `${chunk}.nt\n`).join('')); + }); + + it('aborts without writing output when a chunk fails', async () => { + const chunks = [ + join(workDir, 'geonames_aa.csv'), + join(workDir, 'geonames_ab.csv'), + join(workDir, 'geonames_ac.csv'), + ]; + for (const chunk of chunks) { + await writeFile(chunk, 'header\nrow'); + } + const taskRunner = new FakeTaskRunner('geonames_ab.csv.nt'); + const converter = new SparqlAnythingConverter({ + queryFile, + jarPath: '/bin/sparql-anything.jar', + adminCodesFile: '/data/admin-codes.ttl', + taskRunner, + }); + const outputPath = join(workDir, 'geonames.nt'); + + await expect(converter.convert(chunks, outputPath)).rejects.toThrow( + 'Process failed', + ); + + // The second chunk failed, so the third never ran and no output was merged. + expect(taskRunner.commands).toHaveLength(2); + await expect(readFile(outputPath, 'utf-8')).rejects.toThrow(); + }); +}); diff --git a/packages/sparql-anything/tsconfig.json b/packages/sparql-anything/tsconfig.json new file mode 100644 index 00000000..62ebbd94 --- /dev/null +++ b/packages/sparql-anything/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../../tsconfig.base.json", + "files": [], + "include": [], + "references": [ + { + "path": "./tsconfig.lib.json" + }, + { + "path": "./tsconfig.spec.json" + } + ] +} diff --git a/packages/sparql-anything/tsconfig.lib.json b/packages/sparql-anything/tsconfig.lib.json new file mode 100644 index 00000000..0173f144 --- /dev/null +++ b/packages/sparql-anything/tsconfig.lib.json @@ -0,0 +1,30 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/tsconfig.lib.tsbuildinfo", + "emitDeclarationOnly": false, + "types": ["node"] + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../task-runner/tsconfig.lib.json" + } + ], + "exclude": [ + "vite.config.ts", + "vite.config.mts", + "vitest.config.ts", + "vitest.config.mts", + "test/**/*.test.ts", + "test/**/*.spec.ts", + "test/**/*.test.tsx", + "test/**/*.spec.tsx", + "test/**/*.test.js", + "test/**/*.spec.js", + "test/**/*.test.jsx", + "test/**/*.spec.jsx" + ] +} diff --git a/packages/sparql-anything/tsconfig.spec.json b/packages/sparql-anything/tsconfig.spec.json new file mode 100644 index 00000000..421e6769 --- /dev/null +++ b/packages/sparql-anything/tsconfig.spec.json @@ -0,0 +1,23 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./out-tsc/vitest", + "types": ["node"] + }, + "include": [ + "test/**/*.test.ts", + "test/**/*.spec.ts", + "test/**/*.test.tsx", + "test/**/*.spec.tsx", + "test/**/*.test.js", + "test/**/*.spec.js", + "test/**/*.test.jsx", + "test/**/*.spec.jsx", + "test/**/*.d.ts" + ], + "references": [ + { + "path": "./tsconfig.lib.json" + } + ] +} diff --git a/packages/sparql-anything/vite.config.ts b/packages/sparql-anything/vite.config.ts new file mode 100644 index 00000000..a10fe535 --- /dev/null +++ b/packages/sparql-anything/vite.config.ts @@ -0,0 +1,21 @@ +/// +import { defineConfig, mergeConfig } from 'vite'; +import baseConfig from '../../vite.base.config.js'; + +export default mergeConfig( + baseConfig, + defineConfig({ + root: __dirname, + cacheDir: '../../node_modules/.vite/packages/sparql-anything', + test: { + coverage: { + thresholds: { + functions: 100, + lines: 100, + branches: 100, + statements: 100, + }, + }, + }, + }), +); diff --git a/tsconfig.json b/tsconfig.json index 0b6d2b2c..2b0d7b37 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -76,6 +76,9 @@ }, { "path": "./packages/search" + }, + { + "path": "./packages/sparql-anything" } ] }