@reqlan/cli 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/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # reqlan CLI (`reqlan` / `rq`)
2
+
3
+ Command-line interface for parsing `.rq` files and analysing the workspace requirement graph.
4
+
5
+ ## Commands
6
+
7
+ ```bash
8
+ reqlan parse <file> [--json]
9
+ reqlan analyse [--file <path> | --idea <name>] [--depth <n>] [--cwd <dir>] [--json]
10
+ reqlan analyze # alias of analyse
11
+ reqlan search <query> [--limit <n>] [--cwd <dir>] [--json]
12
+ ```
13
+
14
+ Workspace-scoped commands use `@reqlan/analytical`'s `AnalysisApi` (same headless path as MCP). Set `REQLAN_WORKSPACE` or pass `--cwd` to override the workspace root.
15
+
16
+ ## Build
17
+
18
+ From the monorepo root:
19
+
20
+ ```bash
21
+ pnpm run build
22
+ # or
23
+ pnpm --filter @reqlan/cli run build
24
+ node packages/cli/bin/cli.js --help
25
+ ```
package/bin/cli.js ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from 'node:url';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ await import(join(__dirname, '..', 'out', 'main.js'));
@@ -0,0 +1,11 @@
1
+ import { Command } from 'clipanion';
2
+ export declare class AnalyseCommand extends Command {
3
+ static paths: string[][];
4
+ static usage: import("clipanion").Usage;
5
+ file: string | undefined;
6
+ idea: string | undefined;
7
+ depth: string;
8
+ cwd: string | undefined;
9
+ json: boolean;
10
+ execute(): Promise<number>;
11
+ }
@@ -0,0 +1,123 @@
1
+ import { Command, Option } from 'clipanion';
2
+ import { withAnalysisApi } from '../runtime.js';
3
+ import { emit } from '../output.js';
4
+ function formatIdeaList(apiFormat, ideas, heading) {
5
+ if (ideas.length === 0) {
6
+ return `${heading}\n(none)`;
7
+ }
8
+ return `${heading}\n${ideas.map(idea => apiFormat(idea)).join('\n\n')}`;
9
+ }
10
+ function formatFileContext(formatIdea, related) {
11
+ return [
12
+ formatIdeaList(formatIdea, related.ideasInFile, '## Ideas in file'),
13
+ formatIdeaList(formatIdea, related.referencingIdeas, '## Referencing ideas'),
14
+ formatIdeaList(formatIdea, related.commentLinkedIdeas, '## Comment-linked ideas'),
15
+ formatIdeaList(formatIdea, related.folderReferencingIdeas, '## Folder-referencing ideas')
16
+ ].join('\n\n');
17
+ }
18
+ function formatGraph(formatIdea, slice) {
19
+ const nodes = slice.nodes.map(idea => formatIdea(idea)).join('\n\n');
20
+ const edges = slice.edges
21
+ .map(edge => `${edge.sourceId} -[${edge.kind}]-> ${edge.targetId ?? edge.targetFile ?? '?'}`)
22
+ .join('\n');
23
+ return `## Graph (center ${slice.centerId}, depth ${slice.depth})\n\n### Nodes\n${nodes || '(none)'}\n\n### Edges\n${edges || '(none)'}`;
24
+ }
25
+ function formatCompletion(formatIdea, summary) {
26
+ const byStatus = Object.entries(summary.byStatus)
27
+ .map(([status, count]) => ` ${status}: ${count}`)
28
+ .join('\n');
29
+ return [
30
+ `## Workspace completion`,
31
+ `Total: ${summary.total}`,
32
+ `By status:\n${byStatus || ' (none)'}`,
33
+ formatIdeaList(formatIdea, summary.outstanding.slice(0, 12), '## Outstanding (sample)'),
34
+ formatIdeaList(formatIdea, summary.deprecated.slice(0, 12), '## Deprecated (sample)')
35
+ ].join('\n\n');
36
+ }
37
+ export class AnalyseCommand extends Command {
38
+ constructor() {
39
+ super(...arguments);
40
+ this.file = Option.String('-f,--file', {
41
+ description: 'Analyse requirements related to a file path'
42
+ });
43
+ this.idea = Option.String('-i,--idea', {
44
+ description: 'Analyse the local graph around a named requirement'
45
+ });
46
+ this.depth = Option.String('--depth', '2', {
47
+ description: 'Hop depth for --idea graph (default 2)'
48
+ });
49
+ this.cwd = Option.String('--cwd', {
50
+ description: 'Workspace root (default: walk from cwd, or REQLAN_WORKSPACE)'
51
+ });
52
+ this.json = Option.Boolean('--json', false, {
53
+ description: 'Emit machine-readable JSON'
54
+ });
55
+ }
56
+ async execute() {
57
+ if (this.file && this.idea) {
58
+ this.context.stderr.write('Specify only one of --file or --idea.\n');
59
+ return 1;
60
+ }
61
+ try {
62
+ await withAnalysisApi(this.cwd, async (api) => {
63
+ if (this.file) {
64
+ const related = await api.getFileContext(this.file);
65
+ if (this.json) {
66
+ emit(related, true);
67
+ }
68
+ else {
69
+ emit(formatFileContext(idea => api.formatIdea(idea), related), false);
70
+ }
71
+ return;
72
+ }
73
+ if (this.idea) {
74
+ const depth = Number.parseInt(this.depth, 10);
75
+ const slice = await api.summarizeSubtree(this.idea, Number.isFinite(depth) ? depth : 2);
76
+ if (!slice) {
77
+ if (this.json) {
78
+ emit({ idea: this.idea, found: false }, true);
79
+ }
80
+ else {
81
+ this.context.stdout.write(`No requirement matched "${this.idea}".\n`);
82
+ }
83
+ return;
84
+ }
85
+ if (this.json) {
86
+ emit(slice, true);
87
+ }
88
+ else {
89
+ emit(formatGraph(idea => api.formatIdea(idea), slice), false);
90
+ }
91
+ return;
92
+ }
93
+ const summary = await api.getCompletionStatus();
94
+ if (this.json) {
95
+ emit(summary, true);
96
+ }
97
+ else {
98
+ emit(formatCompletion(idea => api.formatIdea(idea), summary), false);
99
+ }
100
+ });
101
+ return 0;
102
+ }
103
+ catch (error) {
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ this.context.stderr.write(`${message}\n`);
106
+ return 1;
107
+ }
108
+ }
109
+ }
110
+ AnalyseCommand.paths = [['analyse'], ['analyze']];
111
+ AnalyseCommand.usage = Command.Usage({
112
+ description: 'Analyse a file, idea, or the whole workspace requirement graph.',
113
+ details: `
114
+ Without flags, reports workspace completion status.
115
+ Use --file for file-related requirements, or --idea for a named requirement subtree.
116
+ `,
117
+ examples: [
118
+ ['Workspace completion', '$0 analyse'],
119
+ ['File context', '$0 analyse --file ./reqlan\\ rq/cli/cli_package.rq'],
120
+ ['Idea subtree', '$0 analyze --idea cli_package']
121
+ ]
122
+ });
123
+ //# sourceMappingURL=analyse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analyse.js","sourceRoot":"","sources":["../../src/commands/analyse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import { Command } from 'clipanion';
2
+ export declare class ParseCommand extends Command {
3
+ static paths: string[][];
4
+ static usage: import("clipanion").Usage;
5
+ file: string;
6
+ json: boolean;
7
+ execute(): Promise<number>;
8
+ }
@@ -0,0 +1,63 @@
1
+ import { Command, Option } from 'clipanion';
2
+ import { createReqlanServices } from '@reqlan/language';
3
+ import { NodeFileSystem } from 'langium/node';
4
+ import { parseDocument } from '../util.js';
5
+ import { emit } from '../output.js';
6
+ function elementSummary(model) {
7
+ return model.elements.map(element => ({
8
+ type: element.$type,
9
+ name: 'name' in element ? element.name : undefined
10
+ }));
11
+ }
12
+ export class ParseCommand extends Command {
13
+ constructor() {
14
+ super(...arguments);
15
+ this.file = Option.String({ required: true });
16
+ this.json = Option.Boolean('--json', false, {
17
+ description: 'Emit machine-readable JSON'
18
+ });
19
+ }
20
+ async execute() {
21
+ const services = createReqlanServices(NodeFileSystem).Reqlan;
22
+ try {
23
+ const { document, diagnostics, errorCount } = await parseDocument(this.file, services);
24
+ const model = document.parseResult?.value;
25
+ const payload = {
26
+ file: this.file,
27
+ ok: errorCount === 0,
28
+ errorCount,
29
+ diagnostics,
30
+ elements: model ? elementSummary(model) : []
31
+ };
32
+ if (this.json) {
33
+ emit(payload, true);
34
+ }
35
+ else if (errorCount > 0) {
36
+ this.context.stderr.write('There are validation errors:\n');
37
+ for (const d of diagnostics.filter(x => x.severity === 1)) {
38
+ this.context.stderr.write(`line ${d.line}: ${d.message} [${d.text}]\n`);
39
+ }
40
+ }
41
+ else {
42
+ const lines = payload.elements.map(el => el.name ? `${el.type} ${el.name}` : el.type);
43
+ this.context.stdout.write(`Parsed ${this.file}: ${lines.length} top-level element(s)\n` +
44
+ (lines.length > 0 ? lines.map(l => ` - ${l}`).join('\n') + '\n' : ''));
45
+ }
46
+ return errorCount > 0 ? 1 : 0;
47
+ }
48
+ catch (error) {
49
+ const message = error instanceof Error ? error.message : String(error);
50
+ this.context.stderr.write(`${message}\n`);
51
+ return 1;
52
+ }
53
+ }
54
+ }
55
+ ParseCommand.paths = [['parse']];
56
+ ParseCommand.usage = Command.Usage({
57
+ description: 'Parse a .rq file and print diagnostics or an AST summary.',
58
+ examples: [
59
+ ['Parse a requirement file', '$0 parse ./path/to/file.rq'],
60
+ ['JSON output', '$0 parse ./path/to/file.rq --json']
61
+ ]
62
+ });
63
+ //# sourceMappingURL=parse.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse.js","sourceRoot":"","sources":["../../src/commands/parse.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { Command } from 'clipanion';
2
+ export declare class SearchCommand extends Command {
3
+ static paths: string[][];
4
+ static usage: import("clipanion").Usage;
5
+ query: string;
6
+ limit: string;
7
+ cwd: string | undefined;
8
+ json: boolean;
9
+ execute(): Promise<number>;
10
+ }
@@ -0,0 +1,61 @@
1
+ import { Command, Option } from 'clipanion';
2
+ import { withAnalysisApi } from '../runtime.js';
3
+ import { emit } from '../output.js';
4
+ export class SearchCommand extends Command {
5
+ constructor() {
6
+ super(...arguments);
7
+ this.query = Option.String({ required: true });
8
+ this.limit = Option.String('--limit', '8', {
9
+ description: 'Maximum number of matches (default 8)'
10
+ });
11
+ this.cwd = Option.String('--cwd', {
12
+ description: 'Workspace root (default: walk from cwd, or REQLAN_WORKSPACE)'
13
+ });
14
+ this.json = Option.Boolean('--json', false, {
15
+ description: 'Emit machine-readable JSON'
16
+ });
17
+ }
18
+ async execute() {
19
+ const limit = Number.parseInt(this.limit, 10);
20
+ try {
21
+ await withAnalysisApi(this.cwd, async (api) => {
22
+ const matches = await api.searchRequirements(this.query, Number.isFinite(limit) && limit > 0 ? limit : 8);
23
+ if (this.json) {
24
+ emit(matches.map(match => ({
25
+ idea: match.idea,
26
+ score: match.score,
27
+ reasons: match.reasons
28
+ })), true);
29
+ return;
30
+ }
31
+ if (matches.length === 0) {
32
+ this.context.stdout.write(`No requirements matched "${this.query}".\n`);
33
+ return;
34
+ }
35
+ const body = matches
36
+ .map(match => {
37
+ const reasons = match.reasons?.length ? `\nReasons: ${match.reasons.join(', ')}` : '';
38
+ const score = match.score !== undefined ? `\nScore: ${match.score}` : '';
39
+ return `${api.formatIdea(match.idea)}${score}${reasons}`;
40
+ })
41
+ .join('\n\n');
42
+ emit(body, false);
43
+ });
44
+ return 0;
45
+ }
46
+ catch (error) {
47
+ const message = error instanceof Error ? error.message : String(error);
48
+ this.context.stderr.write(`${message}\n`);
49
+ return 1;
50
+ }
51
+ }
52
+ }
53
+ SearchCommand.paths = [['search']];
54
+ SearchCommand.usage = Command.Usage({
55
+ description: 'Search requirements by keyword across names, summaries, tags, and references.',
56
+ examples: [
57
+ ['Search ideas', '$0 search "cli package"'],
58
+ ['Limit results', '$0 search parse --limit 5 --json']
59
+ ]
60
+ });
61
+ //# sourceMappingURL=search.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../src/commands/search.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { Model } from 'reqlan-language';
2
+ export declare function generateJavaScript(model: Model, filePath: string, destination: string | undefined): string;
@@ -0,0 +1,19 @@
1
+ import { expandToNode, joinToNode, toString } from 'langium/generate';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import { extractDestinationAndName } from './util.js';
5
+ export function generateJavaScript(model, filePath, destination) {
6
+ const data = extractDestinationAndName(filePath, destination);
7
+ const generatedFilePath = `${path.join(data.destination, data.name)}.js`;
8
+ const fileNode = expandToNode `
9
+ "use strict";
10
+
11
+ ${joinToNode(model.elements, (element) => `// ${element.$type}`, { appendNewLineIfNotEmpty: true })}
12
+ `.appendNewLineIfNotEmpty();
13
+ if (!fs.existsSync(data.destination)) {
14
+ fs.mkdirSync(data.destination, { recursive: true });
15
+ }
16
+ fs.writeFileSync(generatedFilePath, toString(fileNode));
17
+ return generatedFilePath;
18
+ }
19
+ //# sourceMappingURL=generator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"generator.js","sourceRoot":"","sources":["../src/generator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACtE,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,yBAAyB,EAAE,MAAM,WAAW,CAAC;AAEtD,MAAM,UAAU,kBAAkB,CAAC,KAAY,EAAE,QAAgB,EAAE,WAA+B;IAC9F,MAAM,IAAI,GAAG,yBAAyB,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;IAC9D,MAAM,iBAAiB,GAAG,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC;IAEzE,MAAM,QAAQ,GAAG,YAAY,CAAA;;;UAGvB,UAAU,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,OAAwB,EAAE,EAAE,CAAC,MAAM,OAAO,CAAC,KAAK,EAAE,EAAE,EAAE,uBAAuB,EAAE,IAAI,EAAE,CAAC;KACvH,CAAC,uBAAuB,EAAE,CAAC;IAE5B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC;QACnC,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACxD,CAAC;IACD,EAAE,CAAC,aAAa,CAAC,iBAAiB,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC;IACxD,OAAO,iBAAiB,CAAC;AAC7B,CAAC"}
package/out/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export {};
package/out/main.js ADDED
@@ -0,0 +1,21 @@
1
+ import { Cli, Builtins } from 'clipanion';
2
+ import { readFileSync } from 'node:fs';
3
+ import { dirname, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { ParseCommand } from './commands/parse.js';
6
+ import { AnalyseCommand } from './commands/analyse.js';
7
+ import { SearchCommand } from './commands/search.js';
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const packageJson = JSON.parse(readFileSync(resolve(__dirname, '..', 'package.json'), 'utf8'));
10
+ const cli = new Cli({
11
+ binaryLabel: 'reqlan',
12
+ binaryName: 'reqlan',
13
+ binaryVersion: packageJson.version
14
+ });
15
+ cli.register(Builtins.HelpCommand);
16
+ cli.register(Builtins.VersionCommand);
17
+ cli.register(ParseCommand);
18
+ cli.register(AnalyseCommand);
19
+ cli.register(SearchCommand);
20
+ await cli.runExit(process.argv.slice(2));
21
+ //# sourceMappingURL=main.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.js","sourceRoot":"","sources":["../src/main.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAC1D,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAE5F,CAAC;AAEF,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC;IAChB,WAAW,EAAE,QAAQ;IACrB,UAAU,EAAE,QAAQ;IACpB,aAAa,EAAE,WAAW,CAAC,OAAO;CACrC,CAAC,CAAC;AAEH,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACnC,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;AACtC,GAAG,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC;AAC3B,GAAG,CAAC,QAAQ,CAAC,cAAc,CAAC,CAAC;AAC7B,GAAG,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC;AAE5B,MAAM,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC"}
@@ -0,0 +1 @@
1
+ export declare function emit(value: unknown, asJson: boolean): void;
package/out/output.js ADDED
@@ -0,0 +1,12 @@
1
+ export function emit(value, asJson) {
2
+ if (asJson) {
3
+ console.log(JSON.stringify(value, null, 2));
4
+ return;
5
+ }
6
+ if (typeof value === 'string') {
7
+ console.log(value);
8
+ return;
9
+ }
10
+ console.log(String(value));
11
+ }
12
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,IAAI,CAAC,KAAc,EAAE,MAAe;IAChD,IAAI,MAAM,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAC5C,OAAO;IACX,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC5B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACnB,OAAO;IACX,CAAC;IACD,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAC/B,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { AnalysisApi } from '@reqlan/analytical';
2
+ export declare function resolveWorkspaceRoot(cwd?: string): string;
3
+ export declare function withAnalysisApi<T>(cwd: string | undefined, run: (api: AnalysisApi) => Promise<T>): Promise<T>;
package/out/runtime.js ADDED
@@ -0,0 +1,35 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { activateAnalysisRuntime, AnalysisApi, createAnalysisRuntime, deactivateAnalysisRuntime } from '@reqlan/analytical';
4
+ export function resolveWorkspaceRoot(cwd) {
5
+ const fromEnv = process.env.REQLAN_WORKSPACE?.trim();
6
+ if (fromEnv) {
7
+ return resolve(fromEnv);
8
+ }
9
+ let dir = resolve(cwd ?? process.cwd());
10
+ for (;;) {
11
+ if (existsSync(resolve(dir, '.git')) || existsSync(resolve(dir, 'pnpm-workspace.yaml'))) {
12
+ return dir;
13
+ }
14
+ const parent = dirname(dir);
15
+ if (parent === dir) {
16
+ return resolve(cwd ?? process.cwd());
17
+ }
18
+ dir = parent;
19
+ }
20
+ }
21
+ export async function withAnalysisApi(cwd, run) {
22
+ const workspaceRoot = resolveWorkspaceRoot(cwd);
23
+ const runtime = createAnalysisRuntime({
24
+ workspaceRoot,
25
+ storagePath: process.env.REQLAN_INDEX_PATH
26
+ });
27
+ await activateAnalysisRuntime(runtime);
28
+ try {
29
+ return await run(new AnalysisApi(runtime));
30
+ }
31
+ finally {
32
+ await deactivateAnalysisRuntime(runtime);
33
+ }
34
+ }
35
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EACH,uBAAuB,EACvB,WAAW,EACX,qBAAqB,EACrB,yBAAyB,EAE5B,MAAM,oBAAoB,CAAC;AAE5B,MAAM,UAAU,oBAAoB,CAAC,GAAY;IAC7C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,IAAI,EAAE,CAAC;IACrD,IAAI,OAAO,EAAE,CAAC;QACV,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC;IAC5B,CAAC;IACD,IAAI,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACxC,SAAS,CAAC;QACN,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,IAAI,UAAU,CAAC,OAAO,CAAC,GAAG,EAAE,qBAAqB,CAAC,CAAC,EAAE,CAAC;YACtF,OAAO,GAAG,CAAC;QACf,CAAC;QACD,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;YACjB,OAAO,OAAO,CAAC,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QACzC,CAAC;QACD,GAAG,GAAG,MAAM,CAAC;IACjB,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,eAAe,CACjC,GAAuB,EACvB,GAAqC;IAErC,MAAM,aAAa,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;IAChD,MAAM,OAAO,GAAoB,qBAAqB,CAAC;QACnD,aAAa;QACb,WAAW,EAAE,OAAO,CAAC,GAAG,CAAC,iBAAiB;KAC7C,CAAC,CAAC;IACH,MAAM,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,CAAC;QACD,OAAO,MAAM,GAAG,CAAC,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC;IAC/C,CAAC;YAAS,CAAC;QACP,MAAM,yBAAyB,CAAC,OAAO,CAAC,CAAC;IAC7C,CAAC;AACL,CAAC"}
package/out/util.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import type { AstNode, LangiumCoreServices, LangiumDocument } from 'langium';
2
+ export interface ParseResult {
3
+ document: LangiumDocument;
4
+ diagnostics: Array<{
5
+ severity: number;
6
+ line: number;
7
+ character: number;
8
+ message: string;
9
+ text: string;
10
+ }>;
11
+ errorCount: number;
12
+ }
13
+ export declare function parseDocument(fileName: string, services: LangiumCoreServices): Promise<ParseResult>;
14
+ export declare function extractAstNode<T extends AstNode>(fileName: string, services: LangiumCoreServices): Promise<T>;
package/out/util.js ADDED
@@ -0,0 +1,35 @@
1
+ import * as path from 'node:path';
2
+ import * as fs from 'node:fs';
3
+ import { URI } from 'langium';
4
+ export async function parseDocument(fileName, services) {
5
+ const extensions = services.LanguageMetaData.fileExtensions;
6
+ if (!extensions.includes(path.extname(fileName))) {
7
+ throw new Error(`Please choose a file with one of these extensions: ${extensions.join(', ')}.`);
8
+ }
9
+ if (!fs.existsSync(fileName)) {
10
+ throw new Error(`File ${fileName} does not exist.`);
11
+ }
12
+ const document = await services.shared.workspace.LangiumDocuments.getOrCreateDocument(URI.file(path.resolve(fileName)));
13
+ await services.shared.workspace.DocumentBuilder.build([document], { validation: true });
14
+ const diagnostics = (document.diagnostics ?? []).map(d => ({
15
+ severity: d.severity ?? 0,
16
+ line: d.range.start.line + 1,
17
+ character: d.range.start.character + 1,
18
+ message: typeof d.message === 'string' ? d.message : d.message.value,
19
+ text: document.textDocument.getText(d.range)
20
+ }));
21
+ const errorCount = diagnostics.filter(d => d.severity === 1).length;
22
+ return { document, diagnostics, errorCount };
23
+ }
24
+ export async function extractAstNode(fileName, services) {
25
+ const { document, errorCount, diagnostics } = await parseDocument(fileName, services);
26
+ if (errorCount > 0) {
27
+ const details = diagnostics
28
+ .filter(d => d.severity === 1)
29
+ .map(d => `line ${d.line}: ${d.message}`)
30
+ .join('\n');
31
+ throw new Error(`Validation errors:\n${details}`);
32
+ }
33
+ return document.parseResult?.value;
34
+ }
35
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AAc9B,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,QAAgB,EAAE,QAA6B;IAC/E,MAAM,UAAU,GAAG,QAAQ,CAAC,gBAAgB,CAAC,cAAc,CAAC;IAC5D,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QAC/C,MAAM,IAAI,KAAK,CAAC,sDAAsD,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACpG,CAAC;IAED,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,QAAQ,QAAQ,kBAAkB,CAAC,CAAC;IACxD,CAAC;IAED,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,gBAAgB,CAAC,mBAAmB,CACjF,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CACnC,CAAC;IACF,MAAM,QAAQ,CAAC,MAAM,CAAC,SAAS,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IAExF,MAAM,WAAW,GAAG,CAAC,QAAQ,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvD,QAAQ,EAAE,CAAC,CAAC,QAAQ,IAAI,CAAC;QACzB,IAAI,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;QAC5B,SAAS,EAAE,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,GAAG,CAAC;QACtC,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK;QACpE,IAAI,EAAE,QAAQ,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;KAC/C,CAAC,CAAC,CAAC;IACJ,MAAM,UAAU,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAEpE,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAE,UAAU,EAAE,CAAC;AACjD,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CAChC,QAAgB,EAChB,QAA6B;IAE7B,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;IACtF,IAAI,UAAU,GAAG,CAAC,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,WAAW;aACtB,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC;aAC7B,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;aACxC,IAAI,CAAC,IAAI,CAAC,CAAC;QAChB,MAAM,IAAI,KAAK,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC;IACtD,CAAC;IACD,OAAO,QAAQ,CAAC,WAAW,EAAE,KAAU,CAAC;AAC5C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@reqlan/cli",
3
+ "description": "CLI for parsing and analysing reqlan requirement graphs",
4
+ "version": "0.1.0",
5
+ "type": "module",
6
+ "license": "AGPL-3.0-only",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/littletuna4/reqlan.git"
10
+ },
11
+ "engines": {
12
+ "node": ">=20.10.0",
13
+ "npm": ">=10.2.3"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "out"
18
+ ],
19
+ "bin": {
20
+ "reqlan": "./bin/cli.js",
21
+ "rq": "./bin/cli.js"
22
+ },
23
+ "publishConfig": {
24
+ "access": "public"
25
+ },
26
+ "dependencies": {
27
+ "clipanion": "3.2.1",
28
+ "langium": "~4.3.0",
29
+ "@reqlan/language": "1.4.0",
30
+ "@reqlan/analytical": "0.4.0"
31
+ },
32
+ "devDependencies": {
33
+ "shx": "~0.4.0"
34
+ },
35
+ "scripts": {
36
+ "clean": "shx rm -fr *.tsbuildinfo out",
37
+ "build": "tsc -b tsconfig.json",
38
+ "build:clean": "pnpm run clean && pnpm run build"
39
+ }
40
+ }