@travetto/runtime 5.0.0-rc.2

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/src/env.ts ADDED
@@ -0,0 +1,109 @@
1
+ const IS_TRUE = /^(true|yes|on|1)$/i;
2
+ const IS_FALSE = /^(false|no|off|0)$/i;
3
+
4
+ export class EnvProp<T> {
5
+ constructor(public readonly key: string) { }
6
+
7
+ /** Set value according to prop type */
8
+ set(val: T | undefined | null): void {
9
+ if (val === undefined || val === null) {
10
+ delete process.env[this.key];
11
+ } else {
12
+ process.env[this.key] = Array.isArray(val) ? `${val.join(',')}` : `${val}`;
13
+ }
14
+ }
15
+
16
+ /** Remove value */
17
+ clear(): void {
18
+ this.set(null);
19
+ }
20
+
21
+ /** Export value */
22
+ export(val: T | undefined): Record<string, string> {
23
+ let out: string;
24
+ if (val === undefined || val === '' || val === null) {
25
+ out = '';
26
+ } else if (Array.isArray(val)) {
27
+ out = val.join(',');
28
+ } else if (typeof val === 'object') {
29
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
30
+ out = Object.entries(val as Record<string, string>).map(([k, v]) => `${k}=${v}`).join(',');
31
+ } else {
32
+ out = `${val}`;
33
+ }
34
+ return { [this.key]: out };
35
+ }
36
+
37
+ /** Read value as string */
38
+ get val(): string | undefined { return process.env[this.key] || undefined; }
39
+
40
+ /** Read value as list */
41
+ get list(): string[] | undefined {
42
+ const val = this.val;
43
+ return (val === undefined || val === '') ?
44
+ undefined : val.split(/[, ]+/g).map(x => x.trim()).filter(x => !!x);
45
+ }
46
+
47
+ /** Read value as object */
48
+ get object(): Record<string, string> | undefined {
49
+ const items = this.list;
50
+ return items ? Object.fromEntries(items.map(x => x.split(/[:=]/g))) : undefined;
51
+ }
52
+
53
+ /** Add values to list */
54
+ add(...items: string[]): void {
55
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
56
+ this.set([... new Set([...this.list ?? [], ...items])] as T);
57
+ }
58
+
59
+ /** Read value as int */
60
+ get int(): number | undefined {
61
+ const vi = parseInt(this.val ?? '', 10);
62
+ return Number.isNaN(vi) ? undefined : vi;
63
+ }
64
+
65
+ /** Read value as boolean */
66
+ get bool(): boolean | undefined {
67
+ const val = this.val;
68
+ return (val === undefined || val === '') ? undefined : IS_TRUE.test(val);
69
+ }
70
+
71
+ /** Determine if the underlying value is truthy */
72
+ get isTrue(): boolean {
73
+ return IS_TRUE.test(this.val ?? '');
74
+ }
75
+
76
+ /** Determine if the underlying value is falsy */
77
+ get isFalse(): boolean {
78
+ return IS_FALSE.test(this.val ?? '');
79
+ }
80
+
81
+ /** Determine if the underlying value is set */
82
+ get isSet(): boolean {
83
+ const val = this.val;
84
+ return val !== undefined && val !== '';
85
+ }
86
+ }
87
+
88
+ type AllType = {
89
+ [K in keyof TravettoEnv]: Pick<EnvProp<TravettoEnv[K]>, 'key' | 'export' | 'val' | 'set' | 'clear' | 'isSet' |
90
+ (TravettoEnv[K] extends unknown[] ? 'list' | 'add' : never) |
91
+ (Extract<TravettoEnv[K], object> extends never ? never : 'object') |
92
+ (Extract<TravettoEnv[K], number> extends never ? never : 'int') |
93
+ (Extract<TravettoEnv[K], boolean> extends never ? never : 'bool' | 'isTrue' | 'isFalse')
94
+ >
95
+ };
96
+
97
+ function delegate<T extends object>(base: T): AllType & T {
98
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
99
+ return new Proxy(base as AllType & T, {
100
+ get(target, prop): unknown {
101
+ return typeof prop !== 'string' ? undefined :
102
+ // @ts-expect-error
103
+ (prop in base ? base[prop] : target[prop] ??= new EnvProp(prop));
104
+ }
105
+ });
106
+ }
107
+
108
+ /** Basic utils for reading known environment variables */
109
+ export const Env = delegate({});
package/src/error.ts ADDED
@@ -0,0 +1,66 @@
1
+ export type ErrorCategory =
2
+ 'general' |
3
+ 'notfound' |
4
+ 'data' |
5
+ 'permissions' |
6
+ 'authentication' |
7
+ 'timeout' |
8
+ 'unavailable';
9
+
10
+ /**
11
+ * Framework error class, with the aim of being extensible
12
+ */
13
+ export class AppError<T = unknown> extends Error {
14
+
15
+ /** Convert from JSON object */
16
+ static fromJSON(e: unknown): AppError | undefined {
17
+ if (!!e && typeof e === 'object' &&
18
+ ('message' in e && typeof e.message === 'string') &&
19
+ ('category' in e && typeof e.category === 'string') &&
20
+ ('type' in e && typeof e.type === 'string') &&
21
+ ('at' in e && typeof e.at === 'number')
22
+ ) {
23
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
24
+ const err = new AppError(e.message, e.category as ErrorCategory, 'details' in e ? e.details : undefined);
25
+ err.at = new Date(e.at);
26
+ err.type = e.type;
27
+ return err;
28
+ }
29
+ }
30
+
31
+ type: string;
32
+ at = new Date();
33
+ details: T;
34
+
35
+ /**
36
+ * Build an app error
37
+ *
38
+ * @param message The error message
39
+ * @param category The error category, can be mapped to HTTP statuses
40
+ * @param details Optional error payload
41
+ */
42
+ constructor(
43
+ message: string,
44
+ public category: ErrorCategory = 'general',
45
+ details?: T
46
+
47
+ ) {
48
+ super(message);
49
+ this.type = this.constructor.name;
50
+ this.details = details!;
51
+ }
52
+
53
+ /**
54
+ * The format of the JSON output
55
+ */
56
+ toJSON(): { message: string, category: string, type: string, at: string, details?: Record<string, unknown> } {
57
+ return {
58
+ message: this.message,
59
+ category: this.category,
60
+ type: this.type,
61
+ at: this.at.toISOString(),
62
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
63
+ details: this.details as Record<string, unknown>,
64
+ };
65
+ }
66
+ }
package/src/exec.ts ADDED
@@ -0,0 +1,156 @@
1
+ import { ChildProcess } from 'node:child_process';
2
+ import { Readable } from 'node:stream';
3
+ import { createInterface } from 'node:readline/promises';
4
+
5
+ const MINUTE = (1000 * 60);
6
+
7
+ const RESULT = Symbol.for('@travetto/runtime:exec-result');
8
+
9
+ interface ExecutionBaseResult {
10
+ /**
11
+ * Exit code
12
+ */
13
+ code: number;
14
+ /**
15
+ * Execution result message, should be inline with code
16
+ */
17
+ message?: string;
18
+ /**
19
+ * Whether or not the execution completed successfully
20
+ */
21
+ valid: boolean;
22
+ }
23
+
24
+ /**
25
+ * Result of an execution
26
+ */
27
+ export interface ExecutionResult<T extends string | Buffer = string | Buffer> extends ExecutionBaseResult {
28
+ /**
29
+ * Stdout
30
+ */
31
+ stdout: T;
32
+ /**
33
+ * Stderr
34
+ */
35
+ stderr: T;
36
+ }
37
+
38
+ /**
39
+ * Standard utilities for managing executions
40
+ */
41
+ export class ExecUtil {
42
+
43
+ static RESTART_EXIT_CODE = 200;
44
+
45
+ /**
46
+ * Run with automatic restart support
47
+ * @param run The factory to produce the next running process
48
+ * @param maxRetriesPerMinute The number of times to allow a retry within a minute
49
+ */
50
+ static async withRestart(run: () => ChildProcess, maxRetriesPerMinute?: number): Promise<ExecutionResult> {
51
+ const maxRetries = maxRetriesPerMinute ?? 5;
52
+ const restarts: number[] = [];
53
+
54
+ for (; ;) {
55
+ const proc = run();
56
+
57
+ const toKill = (): void => { proc.kill('SIGKILL'); };
58
+ const toMessage = (v: unknown): void => { proc.send?.(v!); };
59
+
60
+ // Proxy kill requests
61
+ process.on('message', toMessage);
62
+ process.on('SIGINT', toKill);
63
+ proc.on('message', v => process.send?.(v));
64
+
65
+ const result = await this.getResult(proc, { catch: true });
66
+ if (result.code !== this.RESTART_EXIT_CODE) {
67
+ return result;
68
+ } else {
69
+ process.off('SIGINT', toKill);
70
+ process.off('message', toMessage);
71
+ restarts.unshift(Date.now());
72
+ if (restarts.length === maxRetries) {
73
+ if ((restarts[0] - restarts[maxRetries - 1]) <= MINUTE) {
74
+ console.error(`Bailing, due to ${maxRetries} restarts in under a minute`);
75
+ return result;
76
+ }
77
+ restarts.pop(); // Keep list short
78
+ }
79
+ console.error('Restarting...', { pid: process.pid });
80
+ }
81
+ }
82
+ }
83
+
84
+ /**
85
+ * Take a child process, and some additional options, and produce a promise that
86
+ * represents the entire execution. On successful completion the promise will resolve, and
87
+ * on failed completion the promise will reject.
88
+ *
89
+ * @param proc The process to enhance
90
+ * @param options The options to use to enhance the process
91
+ */
92
+ static getResult(proc: ChildProcess): Promise<ExecutionResult<string>>;
93
+ static getResult(proc: ChildProcess, options: { catch?: boolean, binary?: false }): Promise<ExecutionResult<string>>;
94
+ static getResult(proc: ChildProcess, options: { catch?: boolean, binary: true }): Promise<ExecutionResult<Buffer>>;
95
+ static getResult<T extends string | Buffer>(proc: ChildProcess, options: { catch?: boolean, binary?: boolean } = {}): Promise<ExecutionResult<T>> {
96
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
97
+ const res = (proc as unknown as { [RESULT]: Promise<ExecutionResult> })[RESULT] ??= new Promise<ExecutionResult>(resolve => {
98
+ const stdout: Buffer[] = [];
99
+ const stderr: Buffer[] = [];
100
+ let done = false;
101
+ const finish = (result: ExecutionBaseResult): void => {
102
+ if (done) {
103
+ return;
104
+ }
105
+ done = true;
106
+
107
+ const buffers = {
108
+ stdout: Buffer.concat(stdout),
109
+ stderr: Buffer.concat(stderr),
110
+ };
111
+
112
+ const final = {
113
+ stdout: options.binary ? buffers.stdout : buffers.stdout.toString('utf8'),
114
+ stderr: options.binary ? buffers.stderr : buffers.stderr.toString('utf8'),
115
+ ...result
116
+ };
117
+
118
+ resolve(!final.valid ?
119
+ { ...final, message: `${final.message || final.stderr || final.stdout || 'failed'}` } :
120
+ final
121
+ );
122
+ };
123
+
124
+ proc.stdout?.on('data', (d: string | Buffer) => stdout.push(Buffer.from(d)));
125
+ proc.stderr?.on('data', (d: string | Buffer) => stderr.push(Buffer.from(d)));
126
+
127
+ proc.on('error', (err: Error) =>
128
+ finish({ code: 1, message: err.message, valid: false }));
129
+
130
+ proc.on('close', (code: number) =>
131
+ finish({ code, valid: code === null || code === 0 }));
132
+
133
+ if (proc.exitCode !== null) { // We are already done
134
+ finish({ code: proc.exitCode, valid: proc.exitCode === 0 });
135
+ }
136
+ });
137
+
138
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
139
+ return (options.catch ? res : res.then(v => {
140
+ if (v.valid) {
141
+ return v;
142
+ } else {
143
+ throw new Error(v.message);
144
+ }
145
+ })) as Promise<ExecutionResult<T>>;
146
+ }
147
+
148
+ /**
149
+ * Consume lines
150
+ */
151
+ static async readLines(stream: Readable, handler: (input: string) => unknown | Promise<unknown>): Promise<void> {
152
+ for await (const item of createInterface(stream)) {
153
+ await handler(item);
154
+ }
155
+ }
156
+ }
@@ -0,0 +1,59 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { Readable } from 'node:stream';
3
+ import fs from 'node:fs/promises';
4
+ import path from 'node:path';
5
+
6
+ import { AppError } from './error';
7
+
8
+ /**
9
+ * File loader that will search for files across the provided search paths
10
+ */
11
+ export class FileLoader {
12
+
13
+ #searchPaths: readonly string[];
14
+
15
+ constructor(paths: string[]) {
16
+ this.#searchPaths = paths;
17
+ }
18
+
19
+ /**
20
+ * The paths that will be searched on resolve
21
+ */
22
+ get searchPaths(): readonly string[] {
23
+ return this.#searchPaths;
24
+ }
25
+
26
+ /**
27
+ * Return the absolute path for the given relative path
28
+ * @param relativePath The path to resolve
29
+ */
30
+ async resolve(relativePath: string): Promise<string> {
31
+ for (const sub of this.searchPaths) {
32
+ const resolved = path.join(sub, relativePath);
33
+ if (await fs.stat(resolved).catch(() => false)) {
34
+ return resolved;
35
+ }
36
+ }
37
+ throw new AppError(`Unable to find: ${relativePath}, searched=${this.searchPaths.join(',')}`, 'notfound');
38
+ }
39
+
40
+ /**
41
+ * Read a file, after resolving the path
42
+ * @param relativePath The path to read
43
+ */
44
+ async read(relativePath: string, binary?: false): Promise<string>;
45
+ async read(relativePath: string, binary: true): Promise<Buffer>;
46
+ async read(relativePath: string, binary = false): Promise<string | Buffer> {
47
+ const file = await this.resolve(relativePath);
48
+ return fs.readFile(file, binary ? undefined : 'utf8');
49
+ }
50
+
51
+ /**
52
+ * Read a file as a stream
53
+ * @param relativePath The path to read
54
+ */
55
+ async readStream(relativePath: string, binary = true): Promise<Readable> {
56
+ const file = await this.resolve(relativePath);
57
+ return createReadStream(file, { encoding: binary ? undefined : 'utf8' });
58
+ }
59
+ }
@@ -0,0 +1,42 @@
1
+ export type FunctionMetadataTag = { hash: number, lines: [number, number] };
2
+ export type FunctionMetadata = FunctionMetadataTag & {
3
+ id: string;
4
+ import: string;
5
+ methods?: Record<string, FunctionMetadataTag>;
6
+ synthetic?: boolean;
7
+ abstract?: boolean;
8
+ };
9
+
10
+ const METADATA = Symbol.for('@travetto/runtime:function-metadata');
11
+
12
+ /**
13
+ * Initialize the meta data for a function/class
14
+ * @param fn Class
15
+ * @param `file` Filename
16
+ * @param `hash` Hash of class contents
17
+ * @param `line` Line number in source
18
+ * @param `methods` Methods and their hashes
19
+ * @param `abstract` Is the class abstract
20
+ * @param `synthetic` Is this code generated at build time
21
+ * @private
22
+ */
23
+ export function register(
24
+ fn: Function, module: [string, string], tag: FunctionMetadataTag,
25
+ methods?: Record<string, FunctionMetadataTag>, abstract?: boolean, synthetic?: boolean
26
+ ): void {
27
+ let id = module.join(':');
28
+ if (fn.name) {
29
+ id = `${id}○${fn.name}`;
30
+ }
31
+ const value = { id, import: module.join('/'), ...tag, methods, abstract, synthetic };
32
+ Object.defineProperties(fn, { Ⲑid: { value: id }, [METADATA]: { value } });
33
+ }
34
+
35
+ /**
36
+ * Read metadata
37
+ */
38
+ export function describeFunction(fn: Function): FunctionMetadata;
39
+ export function describeFunction(fn?: Function): FunctionMetadata | undefined {
40
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
41
+ return (fn as unknown as { [METADATA]: FunctionMetadata })?.[METADATA];
42
+ }
@@ -0,0 +1,3 @@
1
+ declare interface Function {
2
+ Ⲑid: string;
3
+ }
@@ -0,0 +1,4 @@
1
+ import { ManifestIndex } from '@travetto/manifest';
2
+
3
+ /** Runtime manifest index */
4
+ export const RuntimeIndex = new ManifestIndex();
@@ -0,0 +1,26 @@
1
+ import { Runtime } from './context';
2
+ import { Env } from './env';
3
+ import { FileLoader } from './file-loader';
4
+
5
+ /**
6
+ * Environment aware file loader
7
+ */
8
+ class $RuntimeResources extends FileLoader {
9
+ #computed: string[];
10
+ #env: string;
11
+
12
+ constructor() {
13
+ super(Runtime.resourcePaths());
14
+ }
15
+
16
+ override get searchPaths(): readonly string[] {
17
+ if (this.#env !== Env.TRV_RESOURCES.val) {
18
+ this.#env = Env.TRV_RESOURCES.val!;
19
+ this.#computed = Runtime.resourcePaths();
20
+ }
21
+ return this.#computed ?? super.searchPaths;
22
+ }
23
+ }
24
+
25
+ /** Runtime resources */
26
+ export const RuntimeResources = new $RuntimeResources();
@@ -0,0 +1,65 @@
1
+ import { Env } from './env';
2
+ import { Util } from './util';
3
+ import { TimeUtil } from './time';
4
+
5
+ /**
6
+ * Shutdown manager, allowing for listening for graceful shutdowns
7
+ */
8
+ export class ShutdownManager {
9
+
10
+ static #registered = false;
11
+ static #handlers: { name?: string, handler: () => Promise<void> }[] = [];
12
+
13
+ /**
14
+ * On Shutdown requested
15
+ * @param name name to log for
16
+ * @param handler synchronous or asynchronous handler
17
+ */
18
+ static onGracefulShutdown(handler: () => Promise<void>, name?: string | { constructor: { Ⲑid: string } }): () => void {
19
+ if (!this.#registered) {
20
+ this.#registered = true;
21
+ const done = (): void => { this.gracefulShutdown(0); };
22
+ process.on('SIGUSR2', done).on('SIGTERM', done).on('SIGINT', done);
23
+ }
24
+ this.#handlers.push({ handler, name: typeof name === 'string' ? name : name?.constructor.Ⲑid });
25
+ return () => {
26
+ const idx = this.#handlers.findIndex(x => x.handler === handler);
27
+ if (idx >= 0) {
28
+ this.#handlers.splice(idx, 1);
29
+ }
30
+ };
31
+ }
32
+
33
+ /**
34
+ * Wait for graceful shutdown to run and complete
35
+ */
36
+ static async gracefulShutdown(code: number | string | undefined = process.exitCode): Promise<void> {
37
+ if (code !== undefined) {
38
+ process.exitCode = code;
39
+ }
40
+
41
+ if (this.#handlers.length) {
42
+ console.debug('Graceful shutdown: started');
43
+
44
+ const items = this.#handlers.splice(0, this.#handlers.length);
45
+ const handlers = Promise.all(items.map(({ name, handler }) => {
46
+ if (name) {
47
+ console.debug('Stopping', { name });
48
+ }
49
+ return handler().catch(err => {
50
+ console.error('Error shutting down', { name, err });
51
+ });
52
+ }));
53
+
54
+ await Promise.race([
55
+ Util.nonBlockingTimeout(TimeUtil.fromValue(Env.TRV_SHUTDOWN_WAIT.val) ?? 2000), // Wait 2s and then force finish
56
+ handlers,
57
+ ]);
58
+
59
+ console.debug('Graceful shutdown: completed');
60
+ }
61
+ if (code !== undefined) {
62
+ process.exit();
63
+ }
64
+ }
65
+ }
package/src/time.ts ADDED
@@ -0,0 +1,101 @@
1
+ const MIN = 1000 * 60;
2
+ const DAY = 24 * MIN * 60;
3
+ const TIME_UNITS = {
4
+ y: DAY * 365,
5
+ M: DAY * 30,
6
+ w: DAY * 7,
7
+ d: DAY,
8
+ h: MIN * 60,
9
+ m: MIN,
10
+ s: 1000,
11
+ ms: 1
12
+ };
13
+
14
+ export type TimeSpan = `${number}${keyof typeof TIME_UNITS}`;
15
+ export type TimeUnit = keyof typeof TIME_UNITS;
16
+
17
+ export class TimeUtil {
18
+
19
+ static #timePattern = new RegExp(`^(?<amount>-?[0-9.]+)(?<unit>${Object.keys(TIME_UNITS).join('|')})$`);
20
+
21
+ /**
22
+ * Test to see if a string is valid for relative time
23
+ * @param val
24
+ */
25
+ static isTimeSpan(val: string): val is TimeSpan {
26
+ return this.#timePattern.test(val);
27
+ }
28
+
29
+ /**
30
+ * Returns time units convert to ms
31
+ * @param amount Number of units to extend
32
+ * @param unit Time unit to extend ('ms', 's', 'm', 'h', 'd', 'w', 'y')
33
+ */
34
+ static asMillis(amount: Date | number | TimeSpan, unit?: TimeUnit): number {
35
+ if (amount instanceof Date) {
36
+ return amount.getTime();
37
+ } else if (typeof amount === 'string') {
38
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
39
+ const { groups } = (amount.match(this.#timePattern) as { groups: { amount?: string, unit?: TimeUnit } });
40
+ const amountStr = groups?.amount ?? `${amount}`;
41
+ unit = groups?.unit ?? unit ?? 'ms';
42
+ if (!TIME_UNITS[unit]) {
43
+ return NaN;
44
+ }
45
+ amount = amountStr.includes('.') ? parseFloat(amountStr) : parseInt(amountStr, 10);
46
+ }
47
+ return amount * TIME_UNITS[unit ?? 'ms'];
48
+ }
49
+
50
+ /**
51
+ * Returns the time converted to seconds
52
+ * @param date The date to convert
53
+ */
54
+ static asSeconds(date: Date | number | TimeSpan, unit?: TimeUnit): number {
55
+ return Math.trunc(this.asMillis(date, unit) / 1000);
56
+ }
57
+
58
+ /**
59
+ * Returns the time converted to a Date
60
+ * @param date The date to convert
61
+ */
62
+ static asDate(date: Date | number | TimeSpan, unit?: TimeUnit): Date {
63
+ return new Date(this.asMillis(date, unit));
64
+ }
65
+
66
+ /**
67
+ * Resolve time or span to possible time
68
+ */
69
+ static fromValue(value: Date | number | string | undefined): number | undefined {
70
+ if (value === undefined) {
71
+ return value;
72
+ }
73
+ const val = (typeof value === 'string' && /\d+[a-z]$/i.test(value)) ?
74
+ (this.isTimeSpan(value) ? this.asMillis(value) : undefined) :
75
+ (typeof value === 'string' ? parseInt(value, 10) :
76
+ (value instanceof Date ? value.getTime() : value));
77
+ return Number.isNaN(val) ? undefined : val;
78
+ }
79
+
80
+ /**
81
+ * Returns a new date with `amount` units into the future
82
+ * @param amount Number of units to extend
83
+ * @param unit Time unit to extend ('ms', 's', 'm', 'h', 'd', 'w', 'y')
84
+ */
85
+ static fromNow(amount: number | TimeSpan, unit: TimeUnit = 'ms'): Date {
86
+ return new Date(Date.now() + this.asMillis(amount, unit));
87
+ }
88
+
89
+ /**
90
+ * Returns a pretty timestamp
91
+ * @param time Time in milliseconds
92
+ */
93
+ static asClock(time: number): string {
94
+ const s = Math.trunc(time / 1000);
95
+ return [
96
+ s > 3600 ? `${Math.trunc(s / 3600).toString().padStart(2, '0')}h` : '',
97
+ s > 60 ? `${Math.trunc((s % 3600) / 60).toString().padStart(2, '0')}m` : '',
98
+ `${(s % 60).toString().padStart(2, '0')}s`
99
+ ].filter(x => !!x).slice(0, 2).join(' ');
100
+ }
101
+ }
package/src/trv.d.ts ADDED
@@ -0,0 +1,59 @@
1
+ import type { ManifestModuleRole } from '@travetto/manifest';
2
+
3
+ import type { TimeSpan } from './time';
4
+
5
+ type Role = Exclude<ManifestModuleRole, 'std' | 'compile'>;
6
+
7
+
8
+ declare global {
9
+ interface TravettoEnv {
10
+ /**
11
+ * The node environment we are running in
12
+ * @default development
13
+ */
14
+ NODE_ENV: 'development' | 'production';
15
+ /**
16
+ * Outputs all console.debug messages, defaults to `local` in dev, and `off` in prod.
17
+ */
18
+ DEBUG: boolean | string;
19
+ /**
20
+ * Environment to deploy, defaults to `NODE_ENV` if not `TRV_ENV` is not specified.
21
+ */
22
+ TRV_ENV: string;
23
+ /**
24
+ * Special role to run as, used to access additional files from the manifest during runtime.
25
+ */
26
+ TRV_ROLE: Role;
27
+ /**
28
+ * Whether or not to run the program in dynamic mode, allowing for real-time updates
29
+ */
30
+ TRV_DYNAMIC: boolean;
31
+ /**
32
+ * The folders to use for resource lookup
33
+ */
34
+ TRV_RESOURCES: string[];
35
+ /**
36
+ * Resource path overrides
37
+ * @private
38
+ */
39
+ TRV_RESOURCE_OVERRIDES: Record<string, string>;
40
+ /**
41
+ * The max time to wait for shutdown to finish after initial SIGINT,
42
+ * @default 2s
43
+ */
44
+ TRV_SHUTDOWN_WAIT: TimeSpan | number;
45
+ /**
46
+ * The desired runtime module
47
+ */
48
+ TRV_MODULE: string;
49
+ /**
50
+ * The location of the manifest file
51
+ * @default undefined
52
+ */
53
+ TRV_MANIFEST: string;
54
+ /**
55
+ * trvc log level
56
+ */
57
+ TRV_BUILD: 'none' | 'info' | 'debug' | 'error' | 'warn'
58
+ }
59
+ }