@dinoxx/dinox-cli 2.13.1 → 2.14.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.
Files changed (58) hide show
  1. package/dist/cli.js +5 -2
  2. package/dist/commands/auth/index.d.ts +2 -1
  3. package/dist/commands/auth/index.js +18 -1
  4. package/dist/commands/doctor.d.ts +4 -1
  5. package/dist/commands/doctor.js +20 -1
  6. package/dist/commands/graph/index.js +2 -1
  7. package/dist/commands/notes/contentPatch.js +2 -1
  8. package/dist/commands/notes/export.js +3 -2
  9. package/dist/commands/runtime.d.ts +8 -0
  10. package/dist/commands/runtime.js +49 -1
  11. package/dist/commands/schema/globalOptions.d.ts +1 -0
  12. package/dist/commands/schema/globalOptions.js +1 -0
  13. package/dist/commands/schema/renderSkills.js +1 -0
  14. package/dist/commands/schema/spec.js +9 -1
  15. package/dist/commands/storage/repo/operations.js +3 -1
  16. package/dist/commands/sync.d.ts +2 -1
  17. package/dist/commands/sync.js +24 -5
  18. package/dist/daemon/client.js +32 -2
  19. package/dist/daemon/connectivity.d.ts +20 -0
  20. package/dist/daemon/connectivity.js +42 -0
  21. package/dist/daemon/ensure.js +48 -12
  22. package/dist/daemon/forward.d.ts +34 -0
  23. package/dist/daemon/forward.js +146 -0
  24. package/dist/daemon/fresh.d.ts +4 -0
  25. package/dist/daemon/fresh.js +10 -0
  26. package/dist/daemon/index.d.ts +11 -6
  27. package/dist/daemon/index.js +90 -25
  28. package/dist/daemon/launch.d.ts +13 -1
  29. package/dist/daemon/launch.js +31 -2
  30. package/dist/daemon/lifecycle.d.ts +3 -1
  31. package/dist/daemon/lifecycle.js +27 -3
  32. package/dist/daemon/pid.d.ts +8 -0
  33. package/dist/daemon/pid.js +20 -0
  34. package/dist/daemon/route.js +2 -1
  35. package/dist/daemon/server.d.ts +44 -0
  36. package/dist/daemon/server.js +175 -3
  37. package/dist/daemon/supervise.d.ts +6 -6
  38. package/dist/daemon/supervise.js +12 -10
  39. package/dist/daemon/supervisor.d.ts +21 -5
  40. package/dist/daemon/supervisor.js +125 -6
  41. package/dist/daemon/types.d.ts +16 -0
  42. package/dist/dinox.js +41 -64
  43. package/dist/gateway/mcpCommandTools.d.ts +9 -0
  44. package/dist/gateway/mcpCommandTools.js +40 -10
  45. package/dist/powersync/runtime.d.ts +2 -0
  46. package/dist/powersync/runtime.js +3 -1
  47. package/dist/requests/store.d.ts +10 -0
  48. package/dist/requests/store.js +190 -0
  49. package/dist/runCli.d.ts +2 -0
  50. package/dist/runCli.js +64 -0
  51. package/dist/utils/argValue.js +2 -1
  52. package/dist/utils/errorCodes.d.ts +20 -0
  53. package/dist/utils/errorCodes.js +5 -0
  54. package/dist/utils/output.d.ts +2 -0
  55. package/dist/utils/output.js +31 -7
  56. package/dist/utils/requestContext.d.ts +24 -0
  57. package/dist/utils/requestContext.js +13 -0
  58. package/package.json +2 -1
@@ -0,0 +1,190 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { setTimeout as delay } from 'node:timers/promises';
5
+ import { getDataDir } from '../config/paths.js';
6
+ import { DinoxError } from '../utils/errors.js';
7
+ const RETENTION_MS = 24 * 60 * 60 * 1000;
8
+ /** Ids the CLI generates only cover its own single retry; their output need not linger. */
9
+ const AUTO_RETENTION_MS = 10 * 60 * 1000;
10
+ export const AUTO_REQUEST_ID_PREFIX = 'auto:';
11
+ const instanceId = randomUUID();
12
+ const cleanedDirs = new Set();
13
+ export function validateRequestId(key) {
14
+ if (typeof key !== 'string' || key.length < 1 || key.length > 128 || /[^A-Za-z0-9._:-]/.test(key)) {
15
+ throw new DinoxError('Request id must contain 1–128 characters from A-Z, a-z, 0-9, ., _, :, -.', { code: 'INVALID_ARGUMENT' });
16
+ }
17
+ return key;
18
+ }
19
+ /** Reads only options before the argument terminator. */
20
+ export function requestIdFromArgv(argv) {
21
+ let key;
22
+ for (let i = 0; i < argv.length; i++) {
23
+ if (argv[i] === '--')
24
+ break;
25
+ if (argv[i] === '--request-id')
26
+ key = validateRequestId(argv[++i] ?? '');
27
+ else if (argv[i].startsWith('--request-id='))
28
+ key = validateRequestId(argv[i].slice(13));
29
+ }
30
+ return key;
31
+ }
32
+ function hash(value) {
33
+ return createHash('sha256').update(value).digest('hex');
34
+ }
35
+ function argvHash(argv) {
36
+ const filtered = [];
37
+ for (let i = 0; i < argv.length; i++) {
38
+ if (argv[i] === '--') {
39
+ filtered.push(...argv.slice(i));
40
+ break;
41
+ }
42
+ if (argv[i] === '--request-id') {
43
+ i++;
44
+ continue;
45
+ }
46
+ if (argv[i].startsWith('--request-id='))
47
+ continue;
48
+ filtered.push(argv[i]);
49
+ }
50
+ return hash(JSON.stringify(filtered));
51
+ }
52
+ function ownerAlive(pid) {
53
+ try {
54
+ process.kill(pid, 0);
55
+ return true;
56
+ }
57
+ catch (error) {
58
+ return error.code !== 'ESRCH';
59
+ }
60
+ }
61
+ async function cleanup(dir) {
62
+ if (cleanedDirs.has(dir))
63
+ return;
64
+ cleanedDirs.add(dir);
65
+ const lockPath = path.join(dir, '.cleanup.lock');
66
+ let lock;
67
+ try {
68
+ lock = await fs.open(lockPath, 'wx', 0o600);
69
+ }
70
+ catch (error) {
71
+ if (error.code === 'EEXIST')
72
+ return;
73
+ throw error;
74
+ }
75
+ try {
76
+ for (const name of await fs.readdir(dir)) {
77
+ if (!/^[a-f0-9]{64}\.json$/.test(name))
78
+ continue;
79
+ const file = path.join(dir, name);
80
+ try {
81
+ const age = Date.now() - (await fs.stat(file)).mtimeMs;
82
+ if (age <= AUTO_RETENTION_MS)
83
+ continue;
84
+ const record = JSON.parse(await fs.readFile(file, 'utf8'));
85
+ if (!record.auto && age <= RETENTION_MS)
86
+ continue;
87
+ // Never expire a command that is still executing, even after 24 hours.
88
+ if (record.state === 'running' && ownerAlive(record.pid))
89
+ continue;
90
+ await fs.unlink(file);
91
+ }
92
+ catch {
93
+ // Cleanup is best effort; another process may already have removed it.
94
+ }
95
+ }
96
+ }
97
+ finally {
98
+ await lock.close();
99
+ await fs.rm(lockPath, { force: true });
100
+ }
101
+ }
102
+ /** Claims once across processes and preserves the complete output for replay. */
103
+ export async function runWithRequestId(key, argv, run, opts = {}) {
104
+ validateRequestId(key);
105
+ const dir = path.join(getDataDir(), 'requests');
106
+ await fs.mkdir(dir, { recursive: true, mode: 0o700 });
107
+ await fs.chmod(dir, 0o700);
108
+ await cleanup(dir);
109
+ const file = path.join(dir, `${hash(key)}.json`);
110
+ const record = {
111
+ version: 1, argvHash: argvHash(argv), state: 'running', auto: key.startsWith(AUTO_REQUEST_ID_PREFIX), pid: process.pid,
112
+ instanceId, startedAt: Date.now(),
113
+ };
114
+ const deadline = Date.now() + (opts.waitTimeoutMs ?? 300_000);
115
+ try {
116
+ const handle = await fs.open(file, 'wx', 0o600);
117
+ try {
118
+ await handle.writeFile(JSON.stringify(record));
119
+ await handle.sync();
120
+ }
121
+ finally {
122
+ await handle.close();
123
+ }
124
+ }
125
+ catch (error) {
126
+ if (error.code !== 'EEXIST')
127
+ throw error;
128
+ while (true) {
129
+ let previous;
130
+ try {
131
+ previous = JSON.parse(await fs.readFile(file, 'utf8'));
132
+ }
133
+ catch (error) {
134
+ // A claimant may still be writing, or may have rejected and removed its claim.
135
+ if (error.code === 'ENOENT') {
136
+ return runWithRequestId(key, argv, run, { ...opts, waitTimeoutMs: Math.max(0, deadline - Date.now()) });
137
+ }
138
+ if (!(error instanceof SyntaxError))
139
+ throw error;
140
+ if (Date.now() >= deadline) {
141
+ throw new DinoxError('Request claim is not readable yet.', { code: 'REQUEST_IN_PROGRESS', recoverable: true });
142
+ }
143
+ await delay(opts.pollIntervalMs ?? 100);
144
+ continue;
145
+ }
146
+ if (previous.argvHash !== record.argvHash) {
147
+ throw new DinoxError('Request id was used with different arguments.', { code: 'REQUEST_ID_CONFLICT' });
148
+ }
149
+ if (previous.state === 'done' && previous.result)
150
+ return previous.result;
151
+ if (!ownerAlive(previous.pid)) {
152
+ throw new DinoxError('The earlier request stopped; its outcome is unknown.', {
153
+ code: 'REQUEST_INTERRUPTED',
154
+ suggestion: 'Verify the result, then retry with a new request id.',
155
+ });
156
+ }
157
+ if (Date.now() >= deadline) {
158
+ throw new DinoxError('The earlier request is still running.', {
159
+ code: 'REQUEST_IN_PROGRESS', recoverable: true,
160
+ suggestion: 'Rerun the exact same command with the same request id.',
161
+ });
162
+ }
163
+ await delay(opts.pollIntervalMs ?? 100);
164
+ }
165
+ }
166
+ let result;
167
+ try {
168
+ result = await run();
169
+ }
170
+ catch (error) {
171
+ await fs.unlink(file);
172
+ throw error;
173
+ }
174
+ const temporary = `${file}.${randomUUID()}.tmp`;
175
+ try {
176
+ const handle = await fs.open(temporary, 'wx', 0o600);
177
+ try {
178
+ await handle.writeFile(JSON.stringify({ ...record, state: 'done', finishedAt: Date.now(), result }));
179
+ await handle.sync();
180
+ }
181
+ finally {
182
+ await handle.close();
183
+ }
184
+ await fs.rename(temporary, file);
185
+ }
186
+ finally {
187
+ await fs.rm(temporary, { force: true });
188
+ }
189
+ return result;
190
+ }
@@ -0,0 +1,2 @@
1
+ export declare function runCli(input: string[], onFatalError?: (error: unknown) => void): Promise<number>;
2
+ export declare function printCliError(error: unknown, argv: string[]): number;
package/dist/runCli.js ADDED
@@ -0,0 +1,64 @@
1
+ import { createCli } from './cli.js';
2
+ import { buildStructuredErrorPayload, isDinoxError } from './utils/errors.js';
3
+ import { clearRequestedStructuredOutputFormat, parseStructuredOutputFormatFromArgv, printError, printHumanReadableError, setRequestedStructuredOutputFormat, } from './utils/output.js';
4
+ function normalizeArgv(argv) {
5
+ // pnpm/npm script forwarding can prepend a standalone "--".
6
+ if (argv[0] === '--') {
7
+ return argv.slice(1);
8
+ }
9
+ return argv;
10
+ }
11
+ function isVerbose(argv) {
12
+ return argv.includes('--verbose');
13
+ }
14
+ function isCommanderError(error) {
15
+ return Boolean(error)
16
+ && typeof error === 'object'
17
+ && typeof error.code === 'string'
18
+ && error.code.startsWith('commander.')
19
+ && typeof error.exitCode === 'number';
20
+ }
21
+ export async function runCli(input, onFatalError) {
22
+ const argv = normalizeArgv(input);
23
+ try {
24
+ await createCli().parseAsync(['node', process.argv[1] ?? 'dino', ...argv]);
25
+ return 0;
26
+ }
27
+ catch (error) {
28
+ onFatalError?.(error);
29
+ return printCliError(error, argv);
30
+ }
31
+ }
32
+ export function printCliError(error, argv) {
33
+ if (isCommanderError(error)) {
34
+ return error.exitCode === 0 ? 0 : 2;
35
+ }
36
+ let format = null;
37
+ try {
38
+ format = parseStructuredOutputFormatFromArgv(argv);
39
+ }
40
+ catch {
41
+ format = null;
42
+ }
43
+ const verbose = isVerbose(argv);
44
+ const payload = buildStructuredErrorPayload(error, verbose);
45
+ const exitCode = isDinoxError(error) ? error.exitCode : 1;
46
+ if (format) {
47
+ setRequestedStructuredOutputFormat(format);
48
+ printError(payload, format);
49
+ clearRequestedStructuredOutputFormat();
50
+ return exitCode;
51
+ }
52
+ printHumanReadableError(payload.message);
53
+ if (payload.suggested_action) {
54
+ printHumanReadableError(`suggested action: ${payload.suggested_action.command}`);
55
+ printHumanReadableError(`reason: ${payload.suggested_action.reason}`);
56
+ }
57
+ else if (payload.suggestion) {
58
+ printHumanReadableError(`suggestion: ${payload.suggestion}`);
59
+ }
60
+ if (verbose && payload.stack) {
61
+ printHumanReadableError(payload.stack);
62
+ }
63
+ return exitCode;
64
+ }
@@ -1,5 +1,6 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import { DinoxError } from './errors.js';
3
+ import { resolveUserPath } from './requestContext.js';
3
4
  export async function readStringOrFile(value) {
4
5
  if (!value.startsWith('@')) {
5
6
  return value;
@@ -9,7 +10,7 @@ export async function readStringOrFile(value) {
9
10
  throw new DinoxError('Invalid @file syntax: expected @<path>');
10
11
  }
11
12
  try {
12
- return await fs.readFile(filePath, 'utf8');
13
+ return await fs.readFile(resolveUserPath(filePath), 'utf8');
13
14
  }
14
15
  catch (error) {
15
16
  throw new DinoxError(`Failed to read file: ${filePath}`, { cause: error });
@@ -47,6 +47,10 @@ export declare const ERROR_CODES: {
47
47
  readonly exitCode: 1;
48
48
  readonly description: "Another process is writing the CLI config.";
49
49
  };
50
+ readonly DAEMON_COMMAND_NOT_FORWARDABLE: {
51
+ readonly exitCode: 2;
52
+ readonly description: "The command must run in the CLI process.";
53
+ };
50
54
  readonly DAEMON_ERROR: {
51
55
  readonly exitCode: 5;
52
56
  readonly description: "The background daemon reported an error.";
@@ -79,6 +83,10 @@ export declare const ERROR_CODES: {
79
83
  readonly exitCode: 1;
80
84
  readonly description: "The service manager (launchd / systemd) rejected a daemon service operation.";
81
85
  };
86
+ readonly DAEMON_SERVICE_FOREIGN: {
87
+ readonly exitCode: 1;
88
+ readonly description: "The daemon service runs another dino install; it was left unchanged.";
89
+ };
82
90
  readonly DAEMON_SERVICE_UNSUPPORTED: {
83
91
  readonly exitCode: 5;
84
92
  readonly description: "The daemon cannot run as a login service on this platform or with this auth setup.";
@@ -343,6 +351,18 @@ export declare const ERROR_CODES: {
343
351
  readonly exitCode: 1;
344
352
  readonly description: "A property value does not match its definition.";
345
353
  };
354
+ readonly REQUEST_ID_CONFLICT: {
355
+ readonly exitCode: 2;
356
+ readonly description: "The request id was already used for different arguments.";
357
+ };
358
+ readonly REQUEST_INTERRUPTED: {
359
+ readonly exitCode: 1;
360
+ readonly description: "The earlier request owner exited; its outcome is unknown.";
361
+ };
362
+ readonly REQUEST_IN_PROGRESS: {
363
+ readonly exitCode: 1;
364
+ readonly description: "The earlier request is still running; retry with the same id.";
365
+ };
346
366
  readonly SAVED_VIEW_FILTER_CORRUPT: {
347
367
  readonly exitCode: 1;
348
368
  readonly description: "A stored saved view filter is corrupt.";
@@ -17,6 +17,7 @@ export const ERROR_CODES = {
17
17
  COMMAND_FAILED: { exitCode: 1, description: 'Generic command failure without a more specific code.' },
18
18
  CONFIG_INVALID_JSON: { exitCode: 1, description: 'The CLI config file is not valid JSON.' },
19
19
  CONFIG_WRITE_BUSY: { exitCode: 1, description: 'Another process is writing the CLI config.' },
20
+ DAEMON_COMMAND_NOT_FORWARDABLE: { exitCode: 2, description: 'The command must run in the CLI process.' },
20
21
  DAEMON_ERROR: { exitCode: 5, description: 'The background daemon reported an error.' },
21
22
  DAEMON_INSTANCE_CHANGED: { exitCode: 1, description: 'The daemon instance changed between requests.' },
22
23
  DAEMON_INSTANCE_MISMATCH: { exitCode: 1, description: 'The running daemon belongs to a different CLI version or account.' },
@@ -25,6 +26,7 @@ export const ERROR_CODES = {
25
26
  DAEMON_RELEASE_FAILED: { exitCode: 1, description: 'The daemon could not release database ownership.' },
26
27
  DAEMON_RELEASE_TIMEOUT: { exitCode: 1, description: 'The daemon did not release database ownership in time.' },
27
28
  DAEMON_SERVICE_FAILED: { exitCode: 1, description: 'The service manager (launchd / systemd) rejected a daemon service operation.' },
29
+ DAEMON_SERVICE_FOREIGN: { exitCode: 1, description: 'The daemon service runs another dino install; it was left unchanged.' },
28
30
  DAEMON_SERVICE_UNSUPPORTED: { exitCode: 5, description: 'The daemon cannot run as a login service on this platform or with this auth setup.' },
29
31
  DAEMON_STALE: { exitCode: 5, description: 'The daemon state is stale and needs a restart.' },
30
32
  DAEMON_START_FAILED: { exitCode: 5, description: 'The daemon process failed to start.' },
@@ -91,6 +93,9 @@ export const ERROR_CODES = {
91
93
  PROPERTY_UNIQUE_ID_TYPE_MISMATCH: { exitCode: 1, description: 'A unique-id setting was supplied for a different property type.' },
92
94
  PROPERTY_URL_INVALID: { exitCode: 1, description: 'A url property value is invalid.' },
93
95
  PROPERTY_VALUE_INVALID: { exitCode: 1, description: 'A property value does not match its definition.' },
96
+ REQUEST_ID_CONFLICT: { exitCode: 2, description: 'The request id was already used for different arguments.' },
97
+ REQUEST_INTERRUPTED: { exitCode: 1, description: 'The earlier request owner exited; its outcome is unknown.' },
98
+ REQUEST_IN_PROGRESS: { exitCode: 1, description: 'The earlier request is still running; retry with the same id.' },
94
99
  SAVED_VIEW_FILTER_CORRUPT: { exitCode: 1, description: 'A stored saved view filter is corrupt.' },
95
100
  SAVED_VIEW_FILTER_TOO_COMPLEX: { exitCode: 1, description: 'The saved view filter exceeds supported complexity.' },
96
101
  SAVED_VIEW_FILTER_VALUE_REQUIRED: { exitCode: 5, description: 'A saved view filter condition is missing its value.' },
@@ -2,6 +2,8 @@ export type StructuredOutputFormat = 'yaml' | 'json';
2
2
  export type StructuredNoticeMap = Record<string, unknown>;
3
3
  export declare function toYaml(value: unknown): string;
4
4
  export declare function toJson(value: unknown): string;
5
+ export declare function writeToStdout(text: string): void;
6
+ export declare function writeToStderr(text: string): void;
5
7
  export declare function printHumanReadableLine(text: string): void;
6
8
  export declare function printHumanReadableBlock(text: string): void;
7
9
  export declare function printHumanReadableWarning(text: string): void;
@@ -1,6 +1,7 @@
1
1
  import { stringify as stringifyYaml } from 'yaml';
2
2
  import { DinoxError } from './errors.js';
3
3
  import { sanitizeForTerminal } from './terminalSanitization.js';
4
+ import { requestContext } from './requestContext.js';
4
5
  let requestedStructuredOutputFormat = null;
5
6
  let pendingStructuredNoticeProvider = null;
6
7
  export function toYaml(value) {
@@ -12,10 +13,20 @@ export function toYaml(value) {
12
13
  export function toJson(value) {
13
14
  return `${JSON.stringify(value, null, 2)}\n`;
14
15
  }
15
- function writeToStdout(text) {
16
+ export function writeToStdout(text) {
17
+ const context = requestContext.getStore();
18
+ if (context) {
19
+ context.stdout.push(text);
20
+ return;
21
+ }
16
22
  process.stdout.write(text);
17
23
  }
18
- function writeToStderr(text) {
24
+ export function writeToStderr(text) {
25
+ const context = requestContext.getStore();
26
+ if (context) {
27
+ context.stderr.push(text);
28
+ return;
29
+ }
19
30
  process.stderr.write(text);
20
31
  }
21
32
  export function printHumanReadableLine(text) {
@@ -92,25 +103,38 @@ export function parseStructuredOutputFormatFromArgv(argv) {
92
103
  return explicitFormat ?? (hasLegacyJson ? 'yaml' : null);
93
104
  }
94
105
  export function setRequestedStructuredOutputFormat(format) {
106
+ const context = requestContext.getStore();
107
+ if (context) {
108
+ context.format = format;
109
+ return;
110
+ }
95
111
  requestedStructuredOutputFormat = format;
96
112
  }
97
113
  export function clearRequestedStructuredOutputFormat() {
98
- requestedStructuredOutputFormat = null;
114
+ setRequestedStructuredOutputFormat(null);
99
115
  }
100
116
  export function getRequestedStructuredOutputFormat() {
101
- return requestedStructuredOutputFormat;
117
+ const context = requestContext.getStore();
118
+ return context ? context.format : requestedStructuredOutputFormat;
102
119
  }
103
120
  export function setPendingStructuredNoticeProvider(provider) {
121
+ const context = requestContext.getStore();
122
+ if (context) {
123
+ context.noticeProvider = provider;
124
+ return;
125
+ }
104
126
  pendingStructuredNoticeProvider = provider;
105
127
  }
106
128
  export function clearPendingStructuredNoticeProvider() {
107
- pendingStructuredNoticeProvider = null;
129
+ setPendingStructuredNoticeProvider(null);
108
130
  }
109
131
  function getPendingStructuredNotice() {
110
- if (!pendingStructuredNoticeProvider) {
132
+ const context = requestContext.getStore();
133
+ const provider = context ? context.noticeProvider : pendingStructuredNoticeProvider;
134
+ if (!provider) {
111
135
  return null;
112
136
  }
113
- return pendingStructuredNoticeProvider();
137
+ return provider();
114
138
  }
115
139
  export function printStructuredData(value, format = getRequestedStructuredOutputFormat()) {
116
140
  if (format === 'json') {
@@ -0,0 +1,24 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import type { AbstractPowerSyncDatabase } from '@powersync/common';
3
+ import type { ResolvedDinoxConfig } from '../config/types.js';
4
+ import type { StructuredNoticeMap, StructuredOutputFormat } from './output.js';
5
+ export type RequestContext = {
6
+ stdout: string[];
7
+ stderr: string[];
8
+ format: StructuredOutputFormat | null;
9
+ noticeProvider: (() => StructuredNoticeMap | null) | null;
10
+ cwd: string;
11
+ daemon?: {
12
+ db: AbstractPowerSyncDatabase;
13
+ config: ResolvedDinoxConfig;
14
+ /**
15
+ * Set after the daemon reconnected (wake from sleep, network change): the
16
+ * stream is only known to be current once a checkpoint completes after it.
17
+ */
18
+ syncUnconfirmedSince?: () => string | null;
19
+ };
20
+ };
21
+ export declare const requestContext: AsyncLocalStorage<RequestContext>;
22
+ export declare function resolveUserPath(p: string): string;
23
+ export declare function assertStdinAvailable(): void;
24
+ export declare const localCommandExecution: AsyncLocalStorage<boolean>;
@@ -0,0 +1,13 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import path from 'node:path';
3
+ import { DinoxError } from './errors.js';
4
+ export const requestContext = new AsyncLocalStorage();
5
+ export function resolveUserPath(p) {
6
+ return path.resolve(requestContext.getStore()?.cwd ?? process.cwd(), p);
7
+ }
8
+ export function assertStdinAvailable() {
9
+ if (requestContext.getStore()?.daemon) {
10
+ throw new DinoxError('Reading stdin inside the daemon is not supported.', { code: 'USAGE_ERROR' });
11
+ }
12
+ }
13
+ export const localCommandExecution = new AsyncLocalStorage();
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "@dinoxx/dinox-cli",
3
- "version": "2.13.1",
3
+ "version": "2.14.0",
4
4
  "description": "Dinox CLI",
5
5
  "main": "dist/dinox.js",
6
6
  "scripts": {
7
+ "bench:startup": "node scripts/bench-startup.mjs",
7
8
  "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc -p tsconfig.json && node scripts/add-shebang.mjs",
8
9
  "dev": "tsx src/dinox.ts",
9
10
  "prepublishOnly": "pnpm run skills:check && pnpm run build",