@yangfei_93sky/biocli 0.2.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/LICENSE +21 -0
- package/README.md +197 -0
- package/dist/batch.d.ts +20 -0
- package/dist/batch.js +69 -0
- package/dist/build-manifest.d.ts +38 -0
- package/dist/build-manifest.js +186 -0
- package/dist/cache.d.ts +28 -0
- package/dist/cache.js +126 -0
- package/dist/cli-manifest.json +1500 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.js +336 -0
- package/dist/clis/_shared/common.d.ts +8 -0
- package/dist/clis/_shared/common.js +13 -0
- package/dist/clis/_shared/eutils.d.ts +9 -0
- package/dist/clis/_shared/eutils.js +9 -0
- package/dist/clis/_shared/organism-db.d.ts +23 -0
- package/dist/clis/_shared/organism-db.js +58 -0
- package/dist/clis/_shared/xml-helpers.d.ts +58 -0
- package/dist/clis/_shared/xml-helpers.js +266 -0
- package/dist/clis/aggregate/enrichment.d.ts +7 -0
- package/dist/clis/aggregate/enrichment.js +105 -0
- package/dist/clis/aggregate/gene-dossier.d.ts +13 -0
- package/dist/clis/aggregate/gene-dossier.js +248 -0
- package/dist/clis/aggregate/gene-profile.d.ts +16 -0
- package/dist/clis/aggregate/gene-profile.js +305 -0
- package/dist/clis/aggregate/literature-brief.d.ts +7 -0
- package/dist/clis/aggregate/literature-brief.js +79 -0
- package/dist/clis/aggregate/variant-dossier.d.ts +11 -0
- package/dist/clis/aggregate/variant-dossier.js +161 -0
- package/dist/clis/aggregate/variant-interpret.d.ts +10 -0
- package/dist/clis/aggregate/variant-interpret.js +210 -0
- package/dist/clis/aggregate/workflow-prepare.d.ts +12 -0
- package/dist/clis/aggregate/workflow-prepare.js +228 -0
- package/dist/clis/aggregate/workflow-scout.d.ts +13 -0
- package/dist/clis/aggregate/workflow-scout.js +175 -0
- package/dist/clis/clinvar/search.d.ts +8 -0
- package/dist/clis/clinvar/search.js +61 -0
- package/dist/clis/clinvar/variant.d.ts +7 -0
- package/dist/clis/clinvar/variant.js +53 -0
- package/dist/clis/enrichr/analyze.d.ts +7 -0
- package/dist/clis/enrichr/analyze.js +48 -0
- package/dist/clis/ensembl/lookup.d.ts +6 -0
- package/dist/clis/ensembl/lookup.js +38 -0
- package/dist/clis/ensembl/vep.d.ts +7 -0
- package/dist/clis/ensembl/vep.js +86 -0
- package/dist/clis/ensembl/xrefs.d.ts +6 -0
- package/dist/clis/ensembl/xrefs.js +36 -0
- package/dist/clis/gene/fetch.d.ts +10 -0
- package/dist/clis/gene/fetch.js +96 -0
- package/dist/clis/gene/info.d.ts +7 -0
- package/dist/clis/gene/info.js +37 -0
- package/dist/clis/gene/search.d.ts +7 -0
- package/dist/clis/gene/search.js +71 -0
- package/dist/clis/geo/dataset.d.ts +7 -0
- package/dist/clis/geo/dataset.js +55 -0
- package/dist/clis/geo/download.d.ts +17 -0
- package/dist/clis/geo/download.js +115 -0
- package/dist/clis/geo/samples.d.ts +7 -0
- package/dist/clis/geo/samples.js +57 -0
- package/dist/clis/geo/search.d.ts +8 -0
- package/dist/clis/geo/search.js +66 -0
- package/dist/clis/kegg/convert.d.ts +7 -0
- package/dist/clis/kegg/convert.js +37 -0
- package/dist/clis/kegg/disease.d.ts +6 -0
- package/dist/clis/kegg/disease.js +57 -0
- package/dist/clis/kegg/link.d.ts +7 -0
- package/dist/clis/kegg/link.js +36 -0
- package/dist/clis/kegg/pathway.d.ts +6 -0
- package/dist/clis/kegg/pathway.js +37 -0
- package/dist/clis/pubmed/abstract.d.ts +7 -0
- package/dist/clis/pubmed/abstract.js +42 -0
- package/dist/clis/pubmed/cited-by.d.ts +7 -0
- package/dist/clis/pubmed/cited-by.js +77 -0
- package/dist/clis/pubmed/fetch.d.ts +6 -0
- package/dist/clis/pubmed/fetch.js +36 -0
- package/dist/clis/pubmed/info.yaml +22 -0
- package/dist/clis/pubmed/related.d.ts +7 -0
- package/dist/clis/pubmed/related.js +81 -0
- package/dist/clis/pubmed/search.d.ts +8 -0
- package/dist/clis/pubmed/search.js +63 -0
- package/dist/clis/snp/lookup.d.ts +7 -0
- package/dist/clis/snp/lookup.js +57 -0
- package/dist/clis/sra/download.d.ts +18 -0
- package/dist/clis/sra/download.js +217 -0
- package/dist/clis/sra/run.d.ts +8 -0
- package/dist/clis/sra/run.js +77 -0
- package/dist/clis/sra/search.d.ts +8 -0
- package/dist/clis/sra/search.js +83 -0
- package/dist/clis/string/enrichment.d.ts +7 -0
- package/dist/clis/string/enrichment.js +50 -0
- package/dist/clis/string/network.d.ts +7 -0
- package/dist/clis/string/network.js +47 -0
- package/dist/clis/string/partners.d.ts +4 -0
- package/dist/clis/string/partners.js +44 -0
- package/dist/clis/taxonomy/lookup.d.ts +8 -0
- package/dist/clis/taxonomy/lookup.js +54 -0
- package/dist/clis/uniprot/fetch.d.ts +7 -0
- package/dist/clis/uniprot/fetch.js +82 -0
- package/dist/clis/uniprot/search.d.ts +6 -0
- package/dist/clis/uniprot/search.js +65 -0
- package/dist/clis/uniprot/sequence.d.ts +7 -0
- package/dist/clis/uniprot/sequence.js +51 -0
- package/dist/commander-adapter.d.ts +27 -0
- package/dist/commander-adapter.js +286 -0
- package/dist/completion.d.ts +19 -0
- package/dist/completion.js +117 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.js +94 -0
- package/dist/databases/enrichr.d.ts +28 -0
- package/dist/databases/enrichr.js +131 -0
- package/dist/databases/ensembl.d.ts +14 -0
- package/dist/databases/ensembl.js +106 -0
- package/dist/databases/index.d.ts +45 -0
- package/dist/databases/index.js +49 -0
- package/dist/databases/kegg.d.ts +26 -0
- package/dist/databases/kegg.js +136 -0
- package/dist/databases/ncbi.d.ts +28 -0
- package/dist/databases/ncbi.js +144 -0
- package/dist/databases/string-db.d.ts +19 -0
- package/dist/databases/string-db.js +105 -0
- package/dist/databases/uniprot.d.ts +13 -0
- package/dist/databases/uniprot.js +110 -0
- package/dist/discovery.d.ts +32 -0
- package/dist/discovery.js +235 -0
- package/dist/doctor.d.ts +19 -0
- package/dist/doctor.js +151 -0
- package/dist/errors.d.ts +68 -0
- package/dist/errors.js +105 -0
- package/dist/execution.d.ts +15 -0
- package/dist/execution.js +178 -0
- package/dist/hooks.d.ts +48 -0
- package/dist/hooks.js +58 -0
- package/dist/main.d.ts +13 -0
- package/dist/main.js +31 -0
- package/dist/ncbi-fetch.d.ts +10 -0
- package/dist/ncbi-fetch.js +10 -0
- package/dist/output.d.ts +18 -0
- package/dist/output.js +394 -0
- package/dist/pipeline/executor.d.ts +22 -0
- package/dist/pipeline/executor.js +40 -0
- package/dist/pipeline/index.d.ts +6 -0
- package/dist/pipeline/index.js +6 -0
- package/dist/pipeline/registry.d.ts +16 -0
- package/dist/pipeline/registry.js +31 -0
- package/dist/pipeline/steps/fetch.d.ts +21 -0
- package/dist/pipeline/steps/fetch.js +160 -0
- package/dist/pipeline/steps/transform.d.ts +26 -0
- package/dist/pipeline/steps/transform.js +92 -0
- package/dist/pipeline/steps/xml-parse.d.ts +12 -0
- package/dist/pipeline/steps/xml-parse.js +27 -0
- package/dist/pipeline/template.d.ts +35 -0
- package/dist/pipeline/template.js +312 -0
- package/dist/rate-limiter.d.ts +56 -0
- package/dist/rate-limiter.js +120 -0
- package/dist/registry-api.d.ts +15 -0
- package/dist/registry-api.js +13 -0
- package/dist/registry.d.ts +90 -0
- package/dist/registry.js +100 -0
- package/dist/schema.d.ts +80 -0
- package/dist/schema.js +72 -0
- package/dist/spinner.d.ts +19 -0
- package/dist/spinner.js +37 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.js +27 -0
- package/dist/utils.d.ts +16 -0
- package/dist/utils.js +40 -0
- package/dist/validate.d.ts +29 -0
- package/dist/validate.js +136 -0
- package/dist/verify.d.ts +20 -0
- package/dist/verify.js +131 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +36 -0
- package/dist/xml-parser.d.ts +19 -0
- package/dist/xml-parser.js +119 -0
- package/dist/yaml-schema.d.ts +40 -0
- package/dist/yaml-schema.js +62 -0
- package/package.json +68 -0
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for biocli.
|
|
3
|
+
*
|
|
4
|
+
* HttpContext is the primary execution context passed to every command
|
|
5
|
+
* function. It wraps database-aware HTTP fetching with built-in rate
|
|
6
|
+
* limiting, authentication injection, and response parsing.
|
|
7
|
+
*/
|
|
8
|
+
/** Metadata that commands can attach to results for the rendering layer. */
|
|
9
|
+
export interface ResultMeta {
|
|
10
|
+
/** Total matching items from the API (e.g. esearch count), for "3 of N" display. */
|
|
11
|
+
totalCount?: number;
|
|
12
|
+
/** The original search query, used for keyword highlighting. */
|
|
13
|
+
query?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Wraps a result array with optional display metadata.
|
|
17
|
+
*
|
|
18
|
+
* Commands return this via `withMeta(rows, meta)` — the commander-adapter
|
|
19
|
+
* extracts the meta and passes it to the output renderer.
|
|
20
|
+
*/
|
|
21
|
+
export interface ResultWithMeta<T = unknown> {
|
|
22
|
+
readonly __resultMeta: true;
|
|
23
|
+
rows: T[];
|
|
24
|
+
meta: ResultMeta;
|
|
25
|
+
}
|
|
26
|
+
/** Wrap command results with display metadata. */
|
|
27
|
+
export declare function withMeta<T>(rows: T[], meta: ResultMeta): ResultWithMeta<T>;
|
|
28
|
+
/** Type guard for ResultWithMeta. */
|
|
29
|
+
export declare function hasResultMeta(v: unknown): v is ResultWithMeta;
|
|
30
|
+
/**
|
|
31
|
+
* Standard result envelope for aggregation/workflow commands.
|
|
32
|
+
*
|
|
33
|
+
* Every high-level biocli command should return this shape so that
|
|
34
|
+
* AI agents and downstream scripts can consume results reliably.
|
|
35
|
+
*/
|
|
36
|
+
export interface BiocliResult<T = unknown> {
|
|
37
|
+
/** Primary result data. */
|
|
38
|
+
data: T;
|
|
39
|
+
/** Cross-database identifiers for the queried entity. */
|
|
40
|
+
ids: Record<string, string>;
|
|
41
|
+
/** Which databases contributed data. */
|
|
42
|
+
sources: string[];
|
|
43
|
+
/** Non-fatal issues: partial failures, ambiguous matches, missing fields. */
|
|
44
|
+
warnings: string[];
|
|
45
|
+
/** ISO timestamp of when the query was executed. */
|
|
46
|
+
queriedAt: string;
|
|
47
|
+
/** Organism context (scientific name). */
|
|
48
|
+
organism?: string;
|
|
49
|
+
/** The original query input. */
|
|
50
|
+
query: string;
|
|
51
|
+
}
|
|
52
|
+
/** Create a BiocliResult envelope. */
|
|
53
|
+
export declare function wrapResult<T>(data: T, opts: {
|
|
54
|
+
ids?: Record<string, string>;
|
|
55
|
+
sources?: string[];
|
|
56
|
+
warnings?: string[];
|
|
57
|
+
organism?: string;
|
|
58
|
+
query: string;
|
|
59
|
+
}): BiocliResult<T>;
|
|
60
|
+
/** Options for a single HTTP request. Generic across all database backends. */
|
|
61
|
+
export interface FetchOptions {
|
|
62
|
+
/** HTTP method (defaults to 'GET'). */
|
|
63
|
+
method?: string;
|
|
64
|
+
/** Additional HTTP headers. */
|
|
65
|
+
headers?: Record<string, string>;
|
|
66
|
+
/** URL query parameters (merged with any already in the URL). */
|
|
67
|
+
params?: Record<string, string>;
|
|
68
|
+
/** Request body (for POST requests). */
|
|
69
|
+
body?: string;
|
|
70
|
+
/** If true, skip the rate limiter for this request. */
|
|
71
|
+
skipRateLimit?: boolean;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* @deprecated Use FetchOptions instead. Kept for backward compatibility
|
|
75
|
+
* with existing NCBI adapters.
|
|
76
|
+
*/
|
|
77
|
+
export type NcbiFetchOptions = FetchOptions;
|
|
78
|
+
/**
|
|
79
|
+
* HTTP execution context provided to every command function.
|
|
80
|
+
*
|
|
81
|
+
* Each database backend creates its own HttpContext with the appropriate
|
|
82
|
+
* rate limiter, authentication, and response parsers baked in.
|
|
83
|
+
*/
|
|
84
|
+
export interface HttpContext {
|
|
85
|
+
/** Database backend ID this context is bound to (e.g. 'ncbi', 'uniprot'). */
|
|
86
|
+
databaseId: string;
|
|
87
|
+
/** Make an HTTP request and return the raw Response. */
|
|
88
|
+
fetch(url: string, opts?: FetchOptions): Promise<Response>;
|
|
89
|
+
/** Fetch a URL and parse the response body as XML, returning the parsed object. */
|
|
90
|
+
fetchXml(url: string, opts?: FetchOptions): Promise<unknown>;
|
|
91
|
+
/** Fetch a URL and parse the response body as JSON. */
|
|
92
|
+
fetchJson(url: string, opts?: FetchOptions): Promise<unknown>;
|
|
93
|
+
/** Fetch a URL and return the raw text body. */
|
|
94
|
+
fetchText(url: string, opts?: FetchOptions): Promise<string>;
|
|
95
|
+
/** Database-specific credentials (NCBI: api_key, email; etc.). */
|
|
96
|
+
credentials?: Record<string, string>;
|
|
97
|
+
/** @deprecated Use credentials?.api_key instead. */
|
|
98
|
+
apiKey?: string;
|
|
99
|
+
/** @deprecated Use credentials?.email instead. */
|
|
100
|
+
email?: string;
|
|
101
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core type definitions for biocli.
|
|
3
|
+
*
|
|
4
|
+
* HttpContext is the primary execution context passed to every command
|
|
5
|
+
* function. It wraps database-aware HTTP fetching with built-in rate
|
|
6
|
+
* limiting, authentication injection, and response parsing.
|
|
7
|
+
*/
|
|
8
|
+
/** Wrap command results with display metadata. */
|
|
9
|
+
export function withMeta(rows, meta) {
|
|
10
|
+
return { __resultMeta: true, rows, meta };
|
|
11
|
+
}
|
|
12
|
+
/** Type guard for ResultWithMeta. */
|
|
13
|
+
export function hasResultMeta(v) {
|
|
14
|
+
return typeof v === 'object' && v !== null && v.__resultMeta === true;
|
|
15
|
+
}
|
|
16
|
+
/** Create a BiocliResult envelope. */
|
|
17
|
+
export function wrapResult(data, opts) {
|
|
18
|
+
return {
|
|
19
|
+
data,
|
|
20
|
+
ids: opts.ids ?? {},
|
|
21
|
+
sources: opts.sources ?? [],
|
|
22
|
+
warnings: opts.warnings ?? [],
|
|
23
|
+
queriedAt: new Date().toISOString(),
|
|
24
|
+
organism: opts.organism,
|
|
25
|
+
query: opts.query,
|
|
26
|
+
};
|
|
27
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple utility functions for biocli.
|
|
3
|
+
*/
|
|
4
|
+
/** Clamp a numeric value to the inclusive range [min, max]. */
|
|
5
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
6
|
+
/** Return a promise that resolves after the given number of milliseconds. */
|
|
7
|
+
export declare function sleep(ms: number): Promise<void>;
|
|
8
|
+
/** Type guard: returns true if the value is a non-null plain object. */
|
|
9
|
+
export declare function isRecord(v: unknown): v is Record<string, unknown>;
|
|
10
|
+
/**
|
|
11
|
+
* Map an array through an async function with bounded concurrency.
|
|
12
|
+
*
|
|
13
|
+
* At most `concurrency` invocations of `fn` run simultaneously.
|
|
14
|
+
* Results are returned in the same order as the input items.
|
|
15
|
+
*/
|
|
16
|
+
export declare function mapConcurrent<T, R>(items: T[], fn: (item: T, index: number) => Promise<R>, concurrency: number): Promise<R[]>;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple utility functions for biocli.
|
|
3
|
+
*/
|
|
4
|
+
/** Clamp a numeric value to the inclusive range [min, max]. */
|
|
5
|
+
export function clamp(value, min, max) {
|
|
6
|
+
if (min > max)
|
|
7
|
+
throw new RangeError(`clamp: min (${min}) must be <= max (${max})`);
|
|
8
|
+
return value < min ? min : value > max ? max : value;
|
|
9
|
+
}
|
|
10
|
+
/** Return a promise that resolves after the given number of milliseconds. */
|
|
11
|
+
export function sleep(ms) {
|
|
12
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
13
|
+
}
|
|
14
|
+
/** Type guard: returns true if the value is a non-null plain object. */
|
|
15
|
+
export function isRecord(v) {
|
|
16
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Map an array through an async function with bounded concurrency.
|
|
20
|
+
*
|
|
21
|
+
* At most `concurrency` invocations of `fn` run simultaneously.
|
|
22
|
+
* Results are returned in the same order as the input items.
|
|
23
|
+
*/
|
|
24
|
+
export async function mapConcurrent(items, fn, concurrency) {
|
|
25
|
+
if (concurrency < 1)
|
|
26
|
+
throw new RangeError('concurrency must be >= 1');
|
|
27
|
+
if (items.length === 0)
|
|
28
|
+
return [];
|
|
29
|
+
const results = new Array(items.length);
|
|
30
|
+
let nextIndex = 0;
|
|
31
|
+
async function worker() {
|
|
32
|
+
while (nextIndex < items.length) {
|
|
33
|
+
const idx = nextIndex++;
|
|
34
|
+
results[idx] = await fn(items[idx], idx);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => worker());
|
|
38
|
+
await Promise.all(workers);
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML adapter definition validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates that YAML CLI definitions have the correct structure and
|
|
5
|
+
* reference only known pipeline steps.
|
|
6
|
+
*/
|
|
7
|
+
export interface ValidationError {
|
|
8
|
+
file: string;
|
|
9
|
+
errors: string[];
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Validate a single YAML CLI definition file.
|
|
13
|
+
*
|
|
14
|
+
* Checks:
|
|
15
|
+
* - File is valid YAML
|
|
16
|
+
* - Top-level is an object
|
|
17
|
+
* - Required fields present (name or can be derived from filename)
|
|
18
|
+
* - Pipeline steps reference known step handlers
|
|
19
|
+
* - Args have valid types
|
|
20
|
+
*
|
|
21
|
+
* @returns Array of error messages (empty if valid)
|
|
22
|
+
*/
|
|
23
|
+
export declare function validateYamlCli(filePath: string): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Validate all YAML CLI definitions in a directory (recursively scanning site subdirs).
|
|
26
|
+
*
|
|
27
|
+
* @returns Array of ValidationError objects for files with issues
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateAll(cliDir: string): ValidationError[];
|
package/dist/validate.js
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML adapter definition validator.
|
|
3
|
+
*
|
|
4
|
+
* Validates that YAML CLI definitions have the correct structure and
|
|
5
|
+
* reference only known pipeline steps.
|
|
6
|
+
*/
|
|
7
|
+
import * as fs from 'node:fs';
|
|
8
|
+
import * as path from 'node:path';
|
|
9
|
+
import yaml from 'js-yaml';
|
|
10
|
+
import { isRecord } from './utils.js';
|
|
11
|
+
import { getKnownStepNames } from './pipeline/index.js';
|
|
12
|
+
/**
|
|
13
|
+
* Validate a single YAML CLI definition file.
|
|
14
|
+
*
|
|
15
|
+
* Checks:
|
|
16
|
+
* - File is valid YAML
|
|
17
|
+
* - Top-level is an object
|
|
18
|
+
* - Required fields present (name or can be derived from filename)
|
|
19
|
+
* - Pipeline steps reference known step handlers
|
|
20
|
+
* - Args have valid types
|
|
21
|
+
*
|
|
22
|
+
* @returns Array of error messages (empty if valid)
|
|
23
|
+
*/
|
|
24
|
+
export function validateYamlCli(filePath) {
|
|
25
|
+
const errors = [];
|
|
26
|
+
let content;
|
|
27
|
+
try {
|
|
28
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
errors.push(`Cannot read file: ${err instanceof Error ? err.message : String(err)}`);
|
|
32
|
+
return errors;
|
|
33
|
+
}
|
|
34
|
+
let parsed;
|
|
35
|
+
try {
|
|
36
|
+
parsed = yaml.load(content);
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
errors.push(`Invalid YAML: ${err instanceof Error ? err.message : String(err)}`);
|
|
40
|
+
return errors;
|
|
41
|
+
}
|
|
42
|
+
if (!isRecord(parsed)) {
|
|
43
|
+
errors.push('Top-level value must be an object (mapping)');
|
|
44
|
+
return errors;
|
|
45
|
+
}
|
|
46
|
+
const def = parsed;
|
|
47
|
+
// Check description
|
|
48
|
+
if (!def.description) {
|
|
49
|
+
errors.push('Missing "description" field');
|
|
50
|
+
}
|
|
51
|
+
// Validate strategy if present
|
|
52
|
+
const validStrategies = ['public', 'api_key'];
|
|
53
|
+
if (def.strategy && !validStrategies.includes(def.strategy.toLowerCase())) {
|
|
54
|
+
errors.push(`Invalid strategy "${def.strategy}". Must be one of: ${validStrategies.join(', ')}`);
|
|
55
|
+
}
|
|
56
|
+
// Validate args
|
|
57
|
+
if (def.args) {
|
|
58
|
+
if (!isRecord(def.args)) {
|
|
59
|
+
errors.push('"args" must be an object mapping arg names to definitions');
|
|
60
|
+
}
|
|
61
|
+
else {
|
|
62
|
+
const validArgTypes = ['str', 'string', 'int', 'number', 'bool', 'boolean'];
|
|
63
|
+
for (const [argName, argDef] of Object.entries(def.args)) {
|
|
64
|
+
if (argDef && typeof argDef === 'object' && 'type' in argDef) {
|
|
65
|
+
const argType = argDef.type;
|
|
66
|
+
if (argType && !validArgTypes.includes(argType)) {
|
|
67
|
+
errors.push(`Arg "${argName}": invalid type "${argType}". Must be one of: ${validArgTypes.join(', ')}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
// Validate pipeline steps
|
|
74
|
+
if (def.pipeline) {
|
|
75
|
+
if (!Array.isArray(def.pipeline)) {
|
|
76
|
+
errors.push('"pipeline" must be an array of step objects');
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const knownSteps = getKnownStepNames();
|
|
80
|
+
for (let i = 0; i < def.pipeline.length; i++) {
|
|
81
|
+
const step = def.pipeline[i];
|
|
82
|
+
if (!isRecord(step)) {
|
|
83
|
+
errors.push(`Pipeline step ${i}: must be an object`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
const stepNames = Object.keys(step);
|
|
87
|
+
if (stepNames.length === 0) {
|
|
88
|
+
errors.push(`Pipeline step ${i}: empty step object`);
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const stepName = stepNames[0];
|
|
92
|
+
if (!knownSteps.includes(stepName)) {
|
|
93
|
+
errors.push(`Pipeline step ${i}: unknown step "${stepName}". Known steps: ${knownSteps.join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
// Validate columns
|
|
99
|
+
if (def.columns !== undefined && !Array.isArray(def.columns)) {
|
|
100
|
+
errors.push('"columns" must be an array of strings');
|
|
101
|
+
}
|
|
102
|
+
// Validate timeout
|
|
103
|
+
if (def.timeout !== undefined) {
|
|
104
|
+
if (typeof def.timeout !== 'number' || def.timeout <= 0) {
|
|
105
|
+
errors.push('"timeout" must be a positive number (seconds)');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return errors;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate all YAML CLI definitions in a directory (recursively scanning site subdirs).
|
|
112
|
+
*
|
|
113
|
+
* @returns Array of ValidationError objects for files with issues
|
|
114
|
+
*/
|
|
115
|
+
export function validateAll(cliDir) {
|
|
116
|
+
const results = [];
|
|
117
|
+
if (!fs.existsSync(cliDir)) {
|
|
118
|
+
return results;
|
|
119
|
+
}
|
|
120
|
+
const siteDirs = fs.readdirSync(cliDir, { withFileTypes: true })
|
|
121
|
+
.filter(d => d.isDirectory() && !d.name.startsWith('.') && !d.name.startsWith('_'));
|
|
122
|
+
for (const siteDir of siteDirs) {
|
|
123
|
+
const sitePath = path.join(cliDir, siteDir.name);
|
|
124
|
+
const files = fs.readdirSync(sitePath);
|
|
125
|
+
for (const file of files) {
|
|
126
|
+
if (!file.endsWith('.yaml') && !file.endsWith('.yml'))
|
|
127
|
+
continue;
|
|
128
|
+
const filePath = path.join(sitePath, file);
|
|
129
|
+
const errors = validateYamlCli(filePath);
|
|
130
|
+
if (errors.length > 0) {
|
|
131
|
+
results.push({ file: filePath, errors });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return results;
|
|
136
|
+
}
|
package/dist/verify.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* biocli verify — Unified verification: validate + doctor + optional smoke.
|
|
3
|
+
*
|
|
4
|
+
* Runs all diagnostic checks in sequence and reports a summary.
|
|
5
|
+
*/
|
|
6
|
+
interface StepResult {
|
|
7
|
+
step: string;
|
|
8
|
+
ok: boolean;
|
|
9
|
+
detail: string;
|
|
10
|
+
}
|
|
11
|
+
export interface VerifyResult {
|
|
12
|
+
steps: StepResult[];
|
|
13
|
+
allPassed: boolean;
|
|
14
|
+
}
|
|
15
|
+
export declare function runVerify(opts?: {
|
|
16
|
+
smoke?: boolean;
|
|
17
|
+
}): Promise<VerifyResult>;
|
|
18
|
+
export declare function formatVerifyText(result: VerifyResult): string;
|
|
19
|
+
export declare function formatVerifyJson(result: VerifyResult): string;
|
|
20
|
+
export {};
|
package/dist/verify.js
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* biocli verify — Unified verification: validate + doctor + optional smoke.
|
|
3
|
+
*
|
|
4
|
+
* Runs all diagnostic checks in sequence and reports a summary.
|
|
5
|
+
*/
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import { validateAll } from './validate.js';
|
|
8
|
+
import { runDoctor } from './doctor.js';
|
|
9
|
+
import { BUILTIN_CLIS_DIR } from './discovery.js';
|
|
10
|
+
import { generateCompletion } from './completion.js';
|
|
11
|
+
import { getConfigPath } from './config.js';
|
|
12
|
+
import { getRegistry } from './registry.js';
|
|
13
|
+
import { biocliResultSchema, resultWithMetaSchema } from './schema.js';
|
|
14
|
+
import { getVersion } from './version.js';
|
|
15
|
+
// ── Steps ────────────────────────────────────────────────────────────────────
|
|
16
|
+
function runValidateStep() {
|
|
17
|
+
const errors = validateAll(BUILTIN_CLIS_DIR);
|
|
18
|
+
const totalErrors = errors.reduce((n, e) => n + e.errors.length, 0);
|
|
19
|
+
return {
|
|
20
|
+
step: 'validate',
|
|
21
|
+
ok: totalErrors === 0,
|
|
22
|
+
detail: totalErrors === 0
|
|
23
|
+
? 'All YAML adapters valid'
|
|
24
|
+
: `${totalErrors} validation error(s) in ${errors.length} file(s)`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function runDoctorStep() {
|
|
28
|
+
const { checks, allPassed } = await runDoctor();
|
|
29
|
+
const failed = checks.filter(c => !c.ok);
|
|
30
|
+
return {
|
|
31
|
+
step: 'doctor',
|
|
32
|
+
ok: allPassed,
|
|
33
|
+
detail: allPassed
|
|
34
|
+
? `All ${checks.length} checks passed`
|
|
35
|
+
: `${failed.length} check(s) failed: ${failed.map(c => c.name).join(', ')}`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function runSmokeStep() {
|
|
39
|
+
const checks = [
|
|
40
|
+
{
|
|
41
|
+
name: 'version',
|
|
42
|
+
check: () => {
|
|
43
|
+
if (!getVersion() || getVersion() === '0.0.0') {
|
|
44
|
+
throw new Error('invalid version');
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'list',
|
|
50
|
+
check: () => {
|
|
51
|
+
if (getRegistry().size === 0) {
|
|
52
|
+
throw new Error('empty registry');
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
name: 'config path',
|
|
58
|
+
check: () => {
|
|
59
|
+
if (!getConfigPath().endsWith('/config.yaml')) {
|
|
60
|
+
throw new Error('bad config path');
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: 'schema',
|
|
66
|
+
check: () => {
|
|
67
|
+
if (biocliResultSchema.title !== 'BiocliResult') {
|
|
68
|
+
throw new Error('bad schema');
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: 'schema meta',
|
|
74
|
+
check: () => {
|
|
75
|
+
if (resultWithMetaSchema.title !== 'ResultWithMeta') {
|
|
76
|
+
throw new Error('bad meta schema');
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'completion bash',
|
|
82
|
+
check: () => {
|
|
83
|
+
const script = generateCompletion('bash');
|
|
84
|
+
if (!script.includes('complete -F _biocli_completions biocli')) {
|
|
85
|
+
throw new Error('bad completion');
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
];
|
|
90
|
+
for (const smokeCheck of checks) {
|
|
91
|
+
try {
|
|
92
|
+
smokeCheck.check();
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
const detail = error instanceof Error ? error.message : 'unknown error';
|
|
96
|
+
return { step: 'smoke', ok: false, detail: `${smokeCheck.name}: ${detail}` };
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return { step: 'smoke', ok: true, detail: `Core smoke tests passed (${checks.length} checks)` };
|
|
100
|
+
}
|
|
101
|
+
// ── Main ─────────────────────────────────────────────────────────────────────
|
|
102
|
+
export async function runVerify(opts = {}) {
|
|
103
|
+
const steps = [];
|
|
104
|
+
steps.push(runValidateStep());
|
|
105
|
+
steps.push(await runDoctorStep());
|
|
106
|
+
if (opts.smoke) {
|
|
107
|
+
steps.push(runSmokeStep());
|
|
108
|
+
}
|
|
109
|
+
return { steps, allPassed: steps.every(s => s.ok) };
|
|
110
|
+
}
|
|
111
|
+
// ── Formatters ───────────────────────────────────────────────────────────────
|
|
112
|
+
export function formatVerifyText(result) {
|
|
113
|
+
const lines = ['', chalk.bold('biocli verify'), ''];
|
|
114
|
+
for (const step of result.steps) {
|
|
115
|
+
const status = step.ok ? chalk.green('PASS') : chalk.red('FAIL');
|
|
116
|
+
lines.push(` ${step.step.padEnd(12)} ${status} ${chalk.dim(step.detail)}`);
|
|
117
|
+
}
|
|
118
|
+
lines.push('');
|
|
119
|
+
if (result.allPassed) {
|
|
120
|
+
lines.push(chalk.green(` All ${result.steps.length} steps passed.`));
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
const passed = result.steps.filter(s => s.ok).length;
|
|
124
|
+
lines.push(chalk.yellow(` ${passed}/${result.steps.length} steps passed.`));
|
|
125
|
+
}
|
|
126
|
+
lines.push('');
|
|
127
|
+
return lines.join('\n');
|
|
128
|
+
}
|
|
129
|
+
export function formatVerifyJson(result) {
|
|
130
|
+
return JSON.stringify(result, null, 2);
|
|
131
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version string reader for biocli.
|
|
3
|
+
*
|
|
4
|
+
* Reads the version from package.json at runtime. Handles both
|
|
5
|
+
* development (src/) and production (dist/) directory layouts.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Return the current biocli version string from package.json.
|
|
9
|
+
*
|
|
10
|
+
* The result is cached after the first call. Falls back to '0.0.0'
|
|
11
|
+
* if package.json cannot be found or parsed.
|
|
12
|
+
*/
|
|
13
|
+
export declare function getVersion(): string;
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version string reader for biocli.
|
|
3
|
+
*
|
|
4
|
+
* Reads the version from package.json at runtime. Handles both
|
|
5
|
+
* development (src/) and production (dist/) directory layouts.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync } from 'node:fs';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
let _version;
|
|
11
|
+
/**
|
|
12
|
+
* Return the current biocli version string from package.json.
|
|
13
|
+
*
|
|
14
|
+
* The result is cached after the first call. Falls back to '0.0.0'
|
|
15
|
+
* if package.json cannot be found or parsed.
|
|
16
|
+
*/
|
|
17
|
+
export function getVersion() {
|
|
18
|
+
if (_version)
|
|
19
|
+
return _version;
|
|
20
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
21
|
+
// Try dist/../package.json first (production), then src/../package.json (dev)
|
|
22
|
+
for (const rel of ['..', '../..']) {
|
|
23
|
+
try {
|
|
24
|
+
const pkgPath = join(__dirname, rel, 'package.json');
|
|
25
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
26
|
+
const v = pkg.version ?? '0.0.0';
|
|
27
|
+
_version = v;
|
|
28
|
+
return v;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// Try next relative path
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
_version = '0.0.0';
|
|
35
|
+
return _version;
|
|
36
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* XML parsing wrapper for NCBI responses.
|
|
3
|
+
*
|
|
4
|
+
* Uses fast-xml-parser with NCBI-specific configuration:
|
|
5
|
+
* - Attributes are preserved with '@_' prefix
|
|
6
|
+
* - Text nodes use '#text' key
|
|
7
|
+
* - Known NCBI elements that can repeat are always parsed as arrays
|
|
8
|
+
* (even when only a single element is present in the response)
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Parse an XML string into a JavaScript object using NCBI-tuned settings.
|
|
12
|
+
*
|
|
13
|
+
* Pre-strips inline markup tags (<i>, <b>, <sup>, etc.) so their text
|
|
14
|
+
* content is preserved in the parent element's #text node.
|
|
15
|
+
*
|
|
16
|
+
* @param xml Raw XML response body from NCBI.
|
|
17
|
+
* @returns Parsed object tree.
|
|
18
|
+
*/
|
|
19
|
+
export declare function parseXml(xml: string): unknown;
|