@basis-theory-labs/cli 4.0.0 → 4.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 CHANGED
@@ -19,7 +19,7 @@ $ npm install -g @basis-theory-labs/cli
19
19
  $ bt COMMAND
20
20
  running command...
21
21
  $ bt (--version)
22
- @basis-theory-labs/cli/4.0.0 linux-x64 node-v22.23.1
22
+ @basis-theory-labs/cli/4.1.0 linux-x64 node-v22.23.1
23
23
  $ bt --help [COMMAND]
24
24
  USAGE
25
25
  $ bt COMMAND
@@ -62,7 +62,7 @@ EXAMPLES
62
62
  $ bt applications
63
63
  ```
64
64
 
65
- _See code: [dist/commands/applications/index.ts](https://github.com/Basis-Theory-Labs/basistheory-cli/blob/v4.0.0/dist/commands/applications/index.ts)_
65
+ _See code: [dist/commands/applications/index.ts](https://github.com/Basis-Theory-Labs/basistheory-cli/blob/v4.1.0/dist/commands/applications/index.ts)_
66
66
 
67
67
  ## `bt applications create`
68
68
 
@@ -151,7 +151,7 @@ EXAMPLES
151
151
  $ bt proxies
152
152
  ```
153
153
 
154
- _See code: [dist/commands/proxies/index.ts](https://github.com/Basis-Theory-Labs/basistheory-cli/blob/v4.0.0/dist/commands/proxies/index.ts)_
154
+ _See code: [dist/commands/proxies/index.ts](https://github.com/Basis-Theory-Labs/basistheory-cli/blob/v4.1.0/dist/commands/proxies/index.ts)_
155
155
 
156
156
  ## `bt proxies create`
157
157
 
@@ -378,7 +378,7 @@ EXAMPLES
378
378
  $ bt reactors
379
379
  ```
380
380
 
381
- _See code: [dist/commands/reactors/index.ts](https://github.com/Basis-Theory-Labs/basistheory-cli/blob/v4.0.0/dist/commands/reactors/index.ts)_
381
+ _See code: [dist/commands/reactors/index.ts](https://github.com/Basis-Theory-Labs/basistheory-cli/blob/v4.1.0/dist/commands/reactors/index.ts)_
382
382
 
383
383
  ## `bt reactors create`
384
384
 
@@ -495,7 +495,7 @@ FLAGS
495
495
  -l, --logs Start logs server after update
496
496
  -n, --name=<value> name of the Reactor
497
497
  -r, --code=<value> path to JavaScript file containing the Reactor code
498
- -w, --watch Watch for changes in informed files
498
+ -w, --watch Watch for changes in supplied code, configuration, and runtime package files
499
499
  -x, --management-key=<value> (required) management key used for connecting with the reactor / proxy
500
500
  --[no-]async execute Reactor invocations asynchronously (node22 only)
501
501
  --image=<option> runtime image (node-bt|node22)
@@ -522,6 +522,11 @@ EXAMPLES
522
522
 
523
523
  $ bt reactors update <reactor-id> --code ./reactor.js --image node22
524
524
 
525
+ Watch a node22 reactor for code, configuration, and dependency changes
526
+
527
+ $ bt reactors update <reactor-id> --code ./reactor.js --configuration ./config.env --package-json ./package.json \
528
+ --image node22 --watch
529
+
525
530
  Update a reactor with node22 and all runtime options
526
531
 
527
532
  $ bt reactors update <reactor-id> --name "My Reactor" --code ./reactor.js --configuration ./config.env --image \
@@ -13,13 +13,15 @@ class Update extends base_1.BaseCommand {
13
13
  var _a, _b, _c, _d;
14
14
  const { bt, args: { id }, flags, } = await this.parse(Update);
15
15
  const { name, code, 'application-id': applicationId, configuration, image, 'package-json': packageJson, timeout, 'warm-concurrency': warmConcurrency, resources, permissions, 'no-wait': noWait, async: runtimeAsync, watch, logs, } = flags;
16
- const existingReactor = (0, runtime_1.hasReactorRuntimeFlags)(flags) ||
16
+ const existingReactor = watch ||
17
+ (0, runtime_1.hasReactorRuntimeFlags)(flags) ||
17
18
  (applicationId !== undefined && image === undefined)
18
19
  ? await (0, management_1.getReactor)(bt, id)
19
20
  : undefined;
20
21
  const effectiveImage = image !== null && image !== void 0 ? image : (_a = existingReactor === null || existingReactor === void 0 ? void 0 : existingReactor.runtime) === null || _a === void 0 ? void 0 : _a.image;
21
22
  const effectiveRuntimeAsync = (_c = runtimeAsync !== null && runtimeAsync !== void 0 ? runtimeAsync : (_b = existingReactor === null || existingReactor === void 0 ? void 0 : existingReactor.runtime) === null || _b === void 0 ? void 0 : _b.async) !== null && _c !== void 0 ? _c : false;
22
23
  const effectiveTimeout = timeout !== null && timeout !== void 0 ? timeout : (_d = existingReactor === null || existingReactor === void 0 ? void 0 : existingReactor.runtime) === null || _d === void 0 ? void 0 : _d.timeout;
24
+ const isConfigurableRuntime = !(0, runtime_2.isLegacyRuntimeImage)(effectiveImage);
23
25
  (0, runtime_1.validateReactorRuntimeFlags)(flags, effectiveImage);
24
26
  (0, runtime_1.validateReactorApplicationId)(applicationId, effectiveImage);
25
27
  if (timeout !== undefined || runtimeAsync !== undefined) {
@@ -42,36 +44,110 @@ class Update extends base_1.BaseCommand {
42
44
  runtime,
43
45
  });
44
46
  await (0, management_1.patchReactor)(bt, id, model);
45
- const reactor = await (0, management_1.getReactor)(bt, id);
46
- const isConfigurableRuntime = (0, runtime_2.needsPolling)(reactor.state);
47
- if (!noWait) {
48
- await (0, runtime_2.waitForResourceState)(bt, 'reactor', id, reactor.state);
49
- }
50
- this.log('Reactor updated successfully!');
51
- if (logs) {
52
- await (0, logs_1.showReactorLogs)(bt, id);
53
- }
54
- if (watch && isConfigurableRuntime) {
55
- this.warn(`--watch is not supported for configurable runtimes (${runtime_2.CONFIGURABLE_RUNTIME_IMAGES.join(' | ')}). Skipping watch.`);
56
- }
57
- else if (watch) {
58
- const entries = Object.entries({
59
- code,
60
- configuration,
61
- }).filter(([, value]) => Boolean(value));
62
- const files = entries.reduce((arr, [, file]) => [...arr, file], []);
47
+ const watchedEntries = Object.entries({
48
+ code,
49
+ configuration,
50
+ ...(isConfigurableRuntime ? { packageJson } : {}),
51
+ }).filter(([, value]) => Boolean(value));
52
+ const startWatching = (initialUpdateCompleted) => {
53
+ const files = watchedEntries.reduce((arr, [, file]) => [...arr, file], []);
63
54
  if (files.length) {
64
55
  this.log(`Watching files for changes: ${files.join(', ')} `);
65
56
  }
66
- entries.forEach(([prop, file]) => {
67
- (0, files_1.watchForChanges)(file, async () => {
68
- core_1.ux.action.start(`Detected change in ${file}. Pushing changes`);
69
- await (0, management_1.patchReactor)(bt, id, (0, utils_1.createModelFromFlags)({
70
- [prop]: file,
71
- }));
72
- core_1.ux.action.stop('✅\t');
57
+ if (isConfigurableRuntime) {
58
+ const waitUntilReactorIsAvailable = async () => {
59
+ await initialUpdateCompleted;
60
+ const currentReactor = await (0, management_1.getReactor)(bt, id);
61
+ if (!(0, runtime_2.needsPolling)(currentReactor.state)) {
62
+ return;
63
+ }
64
+ try {
65
+ await (0, runtime_2.waitForResourceState)(bt, 'reactor', id, currentReactor.state);
66
+ }
67
+ catch (error) {
68
+ const latestReactor = await (0, management_1.getReactor)(bt, id);
69
+ if (latestReactor.state !== 'failed' &&
70
+ latestReactor.state !== 'outdated') {
71
+ throw error;
72
+ }
73
+ }
74
+ };
75
+ const pushLatestChanges = (0, files_1.createCoalescingHandler)(async (markLatestStateCaptured) => {
76
+ await waitUntilReactorIsAvailable();
77
+ core_1.ux.action.start('Pushing latest watched changes');
78
+ try {
79
+ markLatestStateCaptured();
80
+ await (0, management_1.patchReactor)(bt, id, (0, utils_1.createModelFromFlags)({
81
+ code,
82
+ configuration,
83
+ runtime: (0, runtime_2.buildReactorRuntime)({ packageJson }),
84
+ }));
85
+ core_1.ux.action.stop('✅\t');
86
+ }
87
+ catch (error) {
88
+ core_1.ux.action.stop('failed ❌');
89
+ throw error;
90
+ }
91
+ const updatedReactor = await (0, management_1.getReactor)(bt, id);
92
+ await (0, runtime_2.waitForResourceState)(bt, 'reactor', id, updatedReactor.state);
93
+ }, (error) => {
94
+ const message = error instanceof Error ? error.message : String(error);
95
+ this.warn(`Failed to push watched Reactor changes: ${message}`);
73
96
  });
74
- });
97
+ watchedEntries.forEach(([, file]) => {
98
+ (0, files_1.watchForChanges)(file, () => {
99
+ this.log(`Detected change in ${file}. Queuing latest files`);
100
+ return pushLatestChanges();
101
+ });
102
+ });
103
+ }
104
+ else {
105
+ watchedEntries.forEach(([prop, file]) => {
106
+ (0, files_1.watchForChanges)(file, async () => {
107
+ core_1.ux.action.start(`Detected change in ${file}. Pushing changes`);
108
+ await (0, management_1.patchReactor)(bt, id, (0, utils_1.createModelFromFlags)({
109
+ [prop]: file,
110
+ }));
111
+ core_1.ux.action.stop('✅\t');
112
+ });
113
+ });
114
+ }
115
+ };
116
+ let initialUpdateError;
117
+ const completeInitialUpdate = async () => {
118
+ if (!noWait) {
119
+ try {
120
+ const initialState = isConfigurableRuntime
121
+ ? 'updating'
122
+ : (await (0, management_1.getReactor)(bt, id)).state;
123
+ await (0, runtime_2.waitForResourceState)(bt, 'reactor', id, initialState);
124
+ }
125
+ catch (error) {
126
+ if (!watch || !isConfigurableRuntime) {
127
+ throw error;
128
+ }
129
+ initialUpdateError = error;
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ this.warn(`Initial Reactor update failed: ${message}`);
132
+ }
133
+ }
134
+ if (initialUpdateError) {
135
+ this.log('Watch remains active. Save a watched file to retry the latest snapshot.');
136
+ }
137
+ else {
138
+ this.log('Reactor updated successfully!');
139
+ }
140
+ };
141
+ const initialUpdateCompleted = completeInitialUpdate();
142
+ if (watch && isConfigurableRuntime) {
143
+ startWatching(initialUpdateCompleted);
144
+ }
145
+ await initialUpdateCompleted;
146
+ if (logs && !initialUpdateError) {
147
+ await (0, logs_1.showReactorLogs)(bt, id);
148
+ }
149
+ if (watch && !isConfigurableRuntime) {
150
+ startWatching();
75
151
  }
76
152
  }
77
153
  }
@@ -86,6 +162,15 @@ Update.examples = [
86
162
  description: 'Update a reactor with node22 runtime',
87
163
  command: '<%= config.bin %> <%= command.id %> <reactor-id> --code ./reactor.js --image node22',
88
164
  },
165
+ {
166
+ description: 'Watch a node22 reactor for code, configuration, and dependency changes',
167
+ command: '<%= config.bin %> <%= command.id %> <reactor-id> ' +
168
+ '--code ./reactor.js ' +
169
+ '--configuration ./config.env ' +
170
+ '--package-json ./package.json ' +
171
+ '--image node22 ' +
172
+ '--watch',
173
+ },
89
174
  {
90
175
  description: 'Update a reactor with node22 and all runtime options',
91
176
  command: '<%= config.bin %> <%= command.id %> <reactor-id> ' +
@@ -106,7 +191,7 @@ Update.flags = {
106
191
  ...utils_1.REACTOR_FLAGS,
107
192
  watch: core_1.Flags.boolean({
108
193
  char: 'w',
109
- description: 'Watch for changes in informed files',
194
+ description: 'Watch for changes in supplied code, configuration, and runtime package files',
110
195
  default: false,
111
196
  required: false,
112
197
  }),
package/dist/files.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import * as fs from 'fs';
2
2
  declare const readFileContents: (filePath: string) => string;
3
+ declare const createCoalescingHandler: (handler: (markLatestStateCaptured: () => void) => Promise<void>, onError: (error: unknown) => void) => (() => Promise<void>);
3
4
  declare const watchForChanges: (filePath: string, handler: (contents: string) => unknown) => fs.FSWatcher;
4
- export { readFileContents, watchForChanges };
5
+ export { createCoalescingHandler, readFileContents, watchForChanges };
package/dist/files.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.watchForChanges = exports.readFileContents = void 0;
3
+ exports.watchForChanges = exports.readFileContents = exports.createCoalescingHandler = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const fs = tslib_1.__importStar(require("fs"));
6
6
  const lodash_debounce_1 = tslib_1.__importDefault(require("lodash.debounce"));
@@ -8,6 +8,36 @@ const path = tslib_1.__importStar(require("path"));
8
8
  const readFileContents = (filePath) => fs.readFileSync(path.resolve(process.cwd(), filePath)).toString();
9
9
  exports.readFileContents = readFileContents;
10
10
  const FILE_WATCH_DEBOUNCE_DELAY = 1000;
11
+ const createCoalescingHandler = (handler, onError) => {
12
+ let pending = false;
13
+ let running;
14
+ const markLatestStateCaptured = () => {
15
+ pending = false;
16
+ };
17
+ /* eslint-disable no-await-in-loop */
18
+ const drain = async () => {
19
+ while (pending) {
20
+ pending = false;
21
+ try {
22
+ await handler(markLatestStateCaptured);
23
+ }
24
+ catch (error) {
25
+ onError(error);
26
+ }
27
+ }
28
+ };
29
+ /* eslint-enable no-await-in-loop */
30
+ return () => {
31
+ pending = true;
32
+ if (!running) {
33
+ running = drain().finally(() => {
34
+ running = undefined;
35
+ });
36
+ }
37
+ return running;
38
+ };
39
+ };
40
+ exports.createCoalescingHandler = createCoalescingHandler;
11
41
  const watchForChanges = (filePath, handler) => {
12
42
  let lastContents = '';
13
43
  const debounced = (0, lodash_debounce_1.default)(() => {
@@ -64,9 +64,11 @@ const waitForResourceState = async (bt, resourceType, id, initialState) => {
64
64
  }
65
65
  const deadline = Date.now() + POLL_TIMEOUT;
66
66
  const startTime = Date.now();
67
- let currentState;
67
+ let currentState = initialState;
68
68
  const timer = createTimer(startTime, () => currentState);
69
- core_1.ux.action.start('Status');
69
+ core_1.ux.action.start('Status', currentState
70
+ ? `${currentState} (${formatElapsedTime(startTime)})`
71
+ : undefined);
70
72
  /* eslint-disable no-await-in-loop */
71
73
  try {
72
74
  while (Date.now() < deadline) {
@@ -83,11 +85,11 @@ const waitForResourceState = async (bt, resourceType, id, initialState) => {
83
85
  currentState = resource.state;
84
86
  core_1.ux.action.status = `${currentState} (${formatElapsedTime(startTime)})`;
85
87
  if (currentState === 'active') {
86
- core_1.ux.action.stop(`ready (${formatElapsedTime(startTime)})`);
88
+ core_1.ux.action.stop(`ready (${formatElapsedTime(startTime)}) ✅`);
87
89
  return;
88
90
  }
89
91
  if (currentState === 'failed' || currentState === 'outdated') {
90
- core_1.ux.action.stop(`failed (${formatElapsedTime(startTime)})`);
92
+ core_1.ux.action.stop(`failed (${formatElapsedTime(startTime)}) ❌`);
91
93
  throw new Error(formatErrorMessage(resource, resourceType));
92
94
  }
93
95
  await new Promise((resolve) => {
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "4.0.0",
2
+ "version": "4.1.0",
3
3
  "commands": {
4
4
  "applications:create": {
5
5
  "id": "applications:create",
@@ -992,6 +992,10 @@
992
992
  "description": "Update a reactor with node22 runtime",
993
993
  "command": "<%= config.bin %> <%= command.id %> <reactor-id> --code ./reactor.js --image node22"
994
994
  },
995
+ {
996
+ "description": "Watch a node22 reactor for code, configuration, and dependency changes",
997
+ "command": "<%= config.bin %> <%= command.id %> <reactor-id> --code ./reactor.js --configuration ./config.env --package-json ./package.json --image node22 --watch"
998
+ },
995
999
  {
996
1000
  "description": "Update a reactor with node22 and all runtime options",
997
1001
  "command": "<%= config.bin %> <%= command.id %> <reactor-id> --name \"My Reactor\" --code ./reactor.js --configuration ./config.env --image node22 --async --timeout 10 --warm-concurrency 0 --resources standard --package-json ./package.json --permissions token:read --permissions token:create"
@@ -1102,7 +1106,7 @@
1102
1106
  "name": "watch",
1103
1107
  "type": "boolean",
1104
1108
  "char": "w",
1105
- "description": "Watch for changes in informed files",
1109
+ "description": "Watch for changes in supplied code, configuration, and runtime package files",
1106
1110
  "required": false,
1107
1111
  "allowNo": false
1108
1112
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@basis-theory-labs/cli",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Basis Theory CLI tool",
5
5
  "author": "Davi Aquino @djejaquino",
6
6
  "bin": {