sparqlc-cli 0.0.0-stage → 0.1.0

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/CHANGELOG.md ADDED
@@ -0,0 +1,14 @@
1
+ # sparqlc-cli
2
+
3
+ ## 0.1.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 2885ca4: Add `sparqlc-cli` package containing `sparqlc-tsc` (a drop-in `tsc` replacement for SPARQL query/update imports) and `sparqlc` (a CLI SPARQL compiler).
8
+ Extract shared declaration generator `toDeclaration` into `sparqlc/declarations.js` and update `ts-plugin-sparqlc` to use it.
9
+
10
+ ### Patch Changes
11
+
12
+ - Updated dependencies [2885ca4]
13
+ - Updated dependencies [2885ca4]
14
+ - sparqlc@0.3.0
package/README.md CHANGED
@@ -1,3 +1,91 @@
1
- # Temporary Holding Version
1
+ # sparqlc-cli
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ CLI utilities for `sparqlc`:
4
+ - **`sparqlc-tsc`**: A drop-in `tsc` replacement that provides type-checking and emit support for `.rq` and `.ru` SPARQL imports compiled with `sparqlc`.
5
+ - **`sparqlc`**: A standalone command-line compiler to compile a SPARQL query/update file to JavaScript module code and output to stdout.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install --save-dev sparqlc-cli sparqlc typescript
11
+ ```
12
+
13
+ ---
14
+
15
+ ## `sparqlc-tsc`
16
+
17
+ ### Background
18
+
19
+ While [`ts-plugin-sparqlc`](https://github.com/hypermedia-app/sparqlc/tree/master/packages/ts-plugin-sparqlc) provides rich types in IDE editors via TypeScript Language Service plugins, plain `tsc` on the command line does not load language service plugins. As a result, command-line builds and CI runs fall back to untyped generic declarations or fail with implicit-any errors.
20
+
21
+ `sparqlc-tsc` wraps TypeScript's CLI entry point with a custom virtual file system that serves generated declaration files (`foo.d.rq.ts` / `foo.d.ru.ts`) from memory and copies `.rq` and `.ru` files to `outDir` when emitting JavaScript.
22
+
23
+ ### Peer Dependency Notice
24
+
25
+ `sparqlc-tsc` relies on TypeScript 5.x or 6.x. It interfaces with TypeScript's CLI via `ts.executeCommandLine` and `program.getCommonSourceDirectory()`, which are available on the `ts` namespace in TypeScript 5 and 6.
26
+
27
+ ### Configuration
28
+
29
+ In your `tsconfig.json`, enable `allowArbitraryExtensions`:
30
+
31
+ ```json
32
+ {
33
+ "compilerOptions": {
34
+ "target": "ES2022",
35
+ "module": "NodeNext",
36
+ "moduleResolution": "NodeNext",
37
+ "allowArbitraryExtensions": true
38
+ }
39
+ }
40
+ ```
41
+
42
+ `allowArbitraryExtensions` is required for TypeScript to look up virtual `foo.d.rq.ts` / `foo.d.ru.ts` declarations for `./foo.rq` / `./foo.ru` imports.
43
+
44
+ ### Usage
45
+
46
+ Use `sparqlc-tsc` anywhere you would use `tsc`:
47
+
48
+ ```json
49
+ {
50
+ "scripts": {
51
+ "build": "sparqlc-tsc",
52
+ "typecheck": "sparqlc-tsc --noEmit",
53
+ "watch": "sparqlc-tsc --watch"
54
+ }
55
+ }
56
+ ```
57
+
58
+ All standard `tsc` flags, project references (`-b`), incremental builds, and `--watch` are fully supported.
59
+
60
+ ### Query Copying
61
+
62
+ When emitting JavaScript with `outDir`, `sparqlc-tsc` copies all `.rq` and `.ru` files referenced by your TypeScript program to `outDir`, mirroring the directory structure of the emitted JavaScript files.
63
+
64
+ - Unchanged queries are skipped on incremental builds.
65
+ - When `--listEmittedFiles` is passed, copied queries are logged with `TSFILE: ...`.
66
+ - No copies are performed under `--noEmit`, `--emitDeclarationOnly`, or `noEmitOnError` with diagnostics errors.
67
+
68
+ ### Limitations
69
+
70
+ - **Statically analysable imports only:** Only queries referenced through statically analysable imports (e.g. `import q from './query.rq'` or `import('./query.rq')` with string literals) are tracked and copied. Dynamic expressions such as ``import(`./queries/${name}.rq`)`` are not copied automatically.
71
+ - **`tsc -b --clean`:** TypeScript's clean command does not track or remove copied `.rq` / `.ru` asset files from `outDir`.
72
+ - **`outFile` unsupported:** Bundle output via `outFile` is not supported for query file copying; `outDir` must be used.
73
+ - **TypeScript internal coupling:** `sparqlc-tsc` hooks into TypeScript's CLI mechanisms (`ts.executeCommandLine`, `ts.sys`, and `program.getCommonSourceDirectory`).
74
+
75
+ ---
76
+
77
+ ## `sparqlc`
78
+
79
+ A command-line compiler for compiling individual SPARQL queries and updates into JavaScript module source code.
80
+
81
+ ### Usage
82
+
83
+ ```bash
84
+ sparqlc path/to/query.rq
85
+ ```
86
+
87
+ Pass `-b` or `--base <iri>` to specify a base IRI for resolving relative IRIs:
88
+
89
+ ```bash
90
+ sparqlc --base http://example.org/api/ path/to/query.rq > dist/query.js
91
+ ```
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { run } from '../index.js'
3
+
4
+ run()
package/bin/sparqlc.js ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ import { runCompilerCli } from '../sparqlc.js'
3
+
4
+ runCompilerCli()
package/index.d.ts ADDED
@@ -0,0 +1,20 @@
1
+ import ts from 'typescript';
2
+ declare module 'typescript' {
3
+ function executeCommandLine(system: ts.System, cb: (programOrBuilder: ts.Program | ts.BuilderProgram | unknown) => void, commandLineArgs: string[]): void;
4
+ interface Program {
5
+ getCommonSourceDirectory(): string;
6
+ }
7
+ }
8
+ export declare const virtualDts: RegExp;
9
+ export declare function toSourcePath(dtsPath: string): string;
10
+ export interface SparqlSysOptions {
11
+ cache?: Map<string, {
12
+ mtime: number;
13
+ content: string;
14
+ }>;
15
+ }
16
+ export declare function createSparqlSys(tsModule: typeof ts, baseSys?: ts.System, options?: SparqlSysOptions): ts.System;
17
+ export declare function hasSparqlImports(tsModule: typeof ts, sourceFile: ts.SourceFile): boolean;
18
+ export declare function createCopySparqlFiles(tsModule: typeof ts, baseSys?: ts.System): (programOrBuilder: ts.Program | ts.BuilderProgram | unknown) => void;
19
+ export declare function copySparqlFiles(tsModule: typeof ts, baseSys: ts.System, programOrBuilder: ts.Program | ts.BuilderProgram | unknown): void;
20
+ export declare function run(args?: string[], tsModule?: typeof ts, baseSys?: ts.System): void;
package/index.js ADDED
@@ -0,0 +1,167 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import ts from 'typescript';
4
+ import { toDeclaration } from 'sparqlc/declarations.js';
5
+ export const virtualDts = /\.d\.r([qu])\.ts$/;
6
+ export function toSourcePath(dtsPath) {
7
+ return dtsPath.replace(virtualDts, '.r$1');
8
+ }
9
+ export function createSparqlSys(tsModule, baseSys = tsModule.sys, options = {}) {
10
+ const cache = options.cache ?? new Map();
11
+ function readVirtual(dtsPath) {
12
+ const sourcePath = toSourcePath(dtsPath);
13
+ const mtime = baseSys.getModifiedTime?.(sourcePath)?.getTime();
14
+ if (mtime === undefined || !baseSys.fileExists(sourcePath))
15
+ return undefined;
16
+ const cached = cache.get(dtsPath);
17
+ if (cached?.mtime === mtime)
18
+ return cached.content;
19
+ let content;
20
+ try {
21
+ const source = baseSys.readFile(sourcePath);
22
+ if (source === undefined)
23
+ return undefined;
24
+ content = toDeclaration(source);
25
+ }
26
+ catch (err) {
27
+ const message = err instanceof Error ? err.message : String(err);
28
+ baseSys.write(`[sparqlc-tsc] ${sourcePath}: ${message}${baseSys.newLine}`);
29
+ content = 'export {}\n';
30
+ }
31
+ cache.set(dtsPath, { mtime, content });
32
+ return content;
33
+ }
34
+ const sys = {
35
+ ...baseSys,
36
+ fileExists: (filePath) => (virtualDts.test(filePath) ? readVirtual(filePath) !== undefined : baseSys.fileExists(filePath)),
37
+ readFile: (filePath, encoding) => (virtualDts.test(filePath) ? readVirtual(filePath) : baseSys.readFile(filePath, encoding)),
38
+ realpath: baseSys.realpath ? (filePath) => (virtualDts.test(filePath) ? filePath : baseSys.realpath(filePath)) : undefined,
39
+ getModifiedTime: baseSys.getModifiedTime
40
+ ? (filePath) => baseSys.getModifiedTime(virtualDts.test(filePath) ? toSourcePath(filePath) : filePath)
41
+ : undefined,
42
+ readDirectory: (dirPath, extensions, excludes, includes, depth) => {
43
+ const files = baseSys.readDirectory(dirPath, extensions, excludes, includes, depth);
44
+ if (!extensions || extensions.some(ext => ext.includes('.d.ts') || ext.includes('.ts'))) {
45
+ const sparqlFiles = baseSys.readDirectory(dirPath, ['.rq', '.ru'], excludes, includes, depth);
46
+ const virtualFiles = sparqlFiles.map(f => f.replace(/\.r([qu])$/, '.d.r$1.ts'));
47
+ return Array.from(new Set([...files, ...virtualFiles]));
48
+ }
49
+ return files;
50
+ },
51
+ watchFile: baseSys.watchFile
52
+ ? (filePath, callback, pollingInterval, watchOptions) => {
53
+ if (virtualDts.test(filePath)) {
54
+ return baseSys.watchFile(toSourcePath(filePath), (name, kind, mtime) => {
55
+ cache.delete(filePath);
56
+ callback(filePath, kind, mtime);
57
+ }, pollingInterval, watchOptions);
58
+ }
59
+ return baseSys.watchFile(filePath, callback, pollingInterval, watchOptions);
60
+ }
61
+ : undefined,
62
+ watchDirectory: baseSys.watchDirectory
63
+ ? (dirPath, callback, recursive, watchOptions) => {
64
+ return baseSys.watchDirectory(dirPath, (fileName) => {
65
+ callback(fileName);
66
+ if (/\.r[qu]$/.test(fileName)) {
67
+ const virt = fileName.replace(/\.r([qu])$/, '.d.r$1.ts');
68
+ cache.delete(virt);
69
+ callback(virt);
70
+ }
71
+ }, recursive, watchOptions);
72
+ }
73
+ : undefined,
74
+ };
75
+ return sys;
76
+ }
77
+ export function hasSparqlImports(tsModule, sourceFile) {
78
+ let found = false;
79
+ function visit(node) {
80
+ if (found)
81
+ return;
82
+ if (tsModule.isImportDeclaration(node) && tsModule.isStringLiteral(node.moduleSpecifier)) {
83
+ if (/\.r[qu]($|\?)/.test(node.moduleSpecifier.text)) {
84
+ found = true;
85
+ return;
86
+ }
87
+ }
88
+ if (tsModule.isExportDeclaration(node) && node.moduleSpecifier && tsModule.isStringLiteral(node.moduleSpecifier)) {
89
+ if (/\.r[qu]($|\?)/.test(node.moduleSpecifier.text)) {
90
+ found = true;
91
+ return;
92
+ }
93
+ }
94
+ if (tsModule.isCallExpression(node)) {
95
+ const isImportOrRequire = node.expression.kind === tsModule.SyntaxKind.ImportKeyword
96
+ || (tsModule.isIdentifier(node.expression) && node.expression.text === 'require');
97
+ if (isImportOrRequire && node.arguments.length > 0 && tsModule.isStringLiteral(node.arguments[0])) {
98
+ if (/\.r[qu]($|\?)/.test(node.arguments[0].text)) {
99
+ found = true;
100
+ return;
101
+ }
102
+ }
103
+ }
104
+ tsModule.forEachChild(node, visit);
105
+ }
106
+ visit(sourceFile);
107
+ return found;
108
+ }
109
+ export function createCopySparqlFiles(tsModule, baseSys = tsModule.sys) {
110
+ const warnedProjects = new Set();
111
+ return function copySparqlFiles(programOrBuilder) {
112
+ const program = programOrBuilder?.getProgram?.()
113
+ ?? programOrBuilder;
114
+ if (!program || typeof program.getSourceFiles !== 'function')
115
+ return;
116
+ const options = program.getCompilerOptions();
117
+ // Check allowArbitraryExtensions warning
118
+ if (!options.allowArbitraryExtensions) {
119
+ const configKey = String(options.configFilePath
120
+ ?? program.getRootFileNames?.()?.slice()?.sort()?.join(';')
121
+ ?? program.getCurrentDirectory?.()
122
+ ?? 'default');
123
+ if (!warnedProjects.has(configKey)) {
124
+ const hasSparql = program.getSourceFiles().some(sf => !sf.isDeclarationFile && hasSparqlImports(tsModule, sf));
125
+ if (hasSparql) {
126
+ baseSys.write(`[sparqlc-tsc] Warning: 'allowArbitraryExtensions' is not enabled in compiler options. SPARQL imports will not use generated types and will fall back to untyped declarations.${baseSys.newLine}`);
127
+ warnedProjects.add(configKey);
128
+ }
129
+ }
130
+ }
131
+ if (!options.outDir || options.noEmit || options.emitDeclarationOnly)
132
+ return;
133
+ if (options.noEmitOnError && tsModule.getPreEmitDiagnostics(program).some(d => d.category === tsModule.DiagnosticCategory.Error))
134
+ return;
135
+ const sourceDir = program.getCommonSourceDirectory();
136
+ const normalizedSourceDir = path.resolve(sourceDir).split(path.sep).join('/');
137
+ for (const file of program.getSourceFiles()) {
138
+ if (!virtualDts.test(file.fileName))
139
+ continue;
140
+ const source = toSourcePath(file.fileName);
141
+ const normalizedSource = path.resolve(source).split(path.sep).join('/');
142
+ const rel = path.relative(normalizedSourceDir, normalizedSource).split(path.sep).join('/');
143
+ if (rel.startsWith('..') || path.isAbsolute(rel)) {
144
+ baseSys.write(`[sparqlc-tsc] Error: SPARQL file '${source}' is outside the common source directory ('${normalizedSourceDir}') and cannot be copied to outDir.${baseSys.newLine}`);
145
+ continue;
146
+ }
147
+ const target = path.resolve(options.outDir, rel).split(path.sep).join('/');
148
+ const content = baseSys.readFile(source);
149
+ if (content === undefined || baseSys.readFile(target) === content)
150
+ continue;
151
+ fs.mkdirSync(path.dirname(target), { recursive: true });
152
+ baseSys.writeFile(target, content);
153
+ if (options.listEmittedFiles) {
154
+ baseSys.write(`TSFILE: ${target}${baseSys.newLine}`);
155
+ }
156
+ }
157
+ };
158
+ }
159
+ export function copySparqlFiles(tsModule, baseSys, programOrBuilder) {
160
+ const handler = createCopySparqlFiles(tsModule, baseSys);
161
+ handler(programOrBuilder);
162
+ }
163
+ export function run(args = process.argv.slice(2), tsModule = ts, baseSys = tsModule.sys) {
164
+ const sys = createSparqlSys(tsModule, baseSys);
165
+ const copyCb = createCopySparqlFiles(tsModule, baseSys);
166
+ tsModule.executeCommandLine(sys, copyCb, args);
167
+ }
package/package.json CHANGED
@@ -1,6 +1,47 @@
1
1
  {
2
2
  "name": "sparqlc-cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "sparqlc-tsc": "./bin/sparqlc-tsc.js",
8
+ "sparqlc": "./bin/sparqlc.js"
9
+ },
10
+ "files": [
11
+ "CHANGELOG.md",
12
+ "bin",
13
+ "*.js",
14
+ "*.d.ts"
15
+ ],
16
+ "scripts": {
17
+ "build": "tsc",
18
+ "test": "mocha",
19
+ "prepack": "npm run build"
20
+ },
21
+ "author": "Tomasz Pluskiewicz",
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/hypermedia-app/sparqlc",
26
+ "directory": "packages/sparqlc-cli"
27
+ },
28
+ "dependencies": {
29
+ "commander": "13 - 15",
30
+ "sparqlc": "^0.3.0"
31
+ },
32
+ "peerDependencies": {
33
+ "typescript": "5 || 6"
34
+ },
35
+ "devDependencies": {
36
+ "@types/chai": "^5",
37
+ "@types/node": "^22.13.10",
38
+ "chai": "^6",
39
+ "typescript": "^5.9.3"
40
+ },
41
+ "mocha": {
42
+ "spec": "test/**/*.test.ts",
43
+ "require": [
44
+ "tsx"
45
+ ]
46
+ }
47
+ }
package/sparqlc.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare function createCompilerCommand(): Command;
3
+ export declare function runCompilerCli(argv?: string[]): void;
package/sparqlc.js ADDED
@@ -0,0 +1,21 @@
1
+ import fs from 'node:fs';
2
+ import { Command } from 'commander';
3
+ import { compile } from 'sparqlc';
4
+ export function createCompilerCommand() {
5
+ const program = new Command();
6
+ program
7
+ .name('sparqlc')
8
+ .description('Compile a SPARQL query/update file to a JavaScript module')
9
+ .argument('<file>', 'SPARQL file to compile')
10
+ .option('-b, --base <iri>', 'Base IRI for resolving relative IRIs')
11
+ .action((file, options) => {
12
+ const source = fs.readFileSync(file, 'utf-8');
13
+ const compiled = compile(source, { base: options.base });
14
+ process.stdout.write(compiled.code);
15
+ });
16
+ return program;
17
+ }
18
+ export function runCompilerCli(argv = process.argv) {
19
+ const program = createCompilerCommand();
20
+ program.parse(argv);
21
+ }