@revoengine/cli 1.0.0 → 1.0.1

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
@@ -27,7 +27,7 @@ Log in and save your default credentials:
27
27
  revo auth login
28
28
  ```
29
29
 
30
- `revo auth login` opens an interactive terminal prompt for your API key and instance ID. If you are already logged in, the CLI warns and asks you to `revo auth logout` first.
30
+ `revo auth login` opens an interactive terminal prompt for your API key. If you are already logged in, the CLI warns and asks you to `revo auth logout` first.
31
31
 
32
32
  Check the active session:
33
33
 
@@ -52,10 +52,9 @@ Initialize a project for ambient low-code editor globals:
52
52
  ```bash
53
53
  revo project
54
54
  revo project init ./app
55
- revo project switch a931e927-738d-42a4-b486-5fc19bfb3dfb
56
55
  ```
57
56
 
58
- This writes `.revoengine/types/revo.editor.d.ts`, `.revoengine/revo.json`, patches `tsconfig.json` or `jsconfig.json`, and updates `.gitignore` so the generated type bundle stays local by default. `revo project switch <instanceId>` rebinds the current project to another stored instance and refreshes the ambient editor types for that instance.
57
+ This writes `.revoengine/types/revo.editor.d.ts`, `.revoengine/revo.json`, patches `tsconfig.json` or `jsconfig.json`, and updates `.gitignore` so the generated type bundle stays local by default.
59
58
 
60
59
  ## Common commands
61
60
 
@@ -140,7 +139,7 @@ Bulk sync behavior:
140
139
  - Pull skips with `changed` when local files differ from the remote contract.
141
140
  - Pull skips with `stale version` when the local version is older than the remote version, unless `--stale` or `--force` is passed.
142
141
  - Push treats backend `Not modified` responses as skipped instead of failing the whole run.
143
- - Debug posts the local `component.json` plus `elements/{order}_{key}.{js|ts}` source files to the instance sandbox `debug` endpoint.
142
+ - Debug posts the local `component.json` plus `elements/{order}_{key}.{js|ts}` source files to the authenticated sandbox `debug` endpoint.
144
143
  - Sync logs show direction explicitly: `RevoEngine -> path` for pull and `RevoEngine <- path` for push.
145
144
  - Bulk runs print a summary such as `Deployed 54/67, Skipped 13/67 in 13s`.
146
145
 
@@ -148,8 +147,8 @@ Bulk sync behavior:
148
147
 
149
148
  The CLI reads configuration in this order:
150
149
 
151
- 1. CLI flags such as `--url`, `--instance`, and `--token`
152
- 2. Environment variables such as `REVO_URL`, `REVO_INSTANCE`, and `REVO_TOKEN`
150
+ 1. CLI flags such as `--url` and `--token`
151
+ 2. Environment variables such as `REVO_URL` and `REVO_TOKEN`
153
152
  3. Stored config in the OS config directory
154
153
 
155
154
  This means environment variables override the locally stored config, which is the intended setup for CI and non-interactive automation.
@@ -158,7 +157,7 @@ Use `--url` for one-off local or dedicated API targets:
158
157
 
159
158
  ```bash
160
159
  revo --url https://api.test.revong.com -i
161
- revo --url https://api.test.revong.com auth login --instance a931e927-738d-42a4-b486-5fc19bfb3dfb
160
+ revo --url https://api.test.revong.com auth login
162
161
  revo --url https://api.revoengine.com endpoints
163
162
  ```
164
163
 
@@ -167,13 +166,11 @@ Use environment variables for CI/CD or shell profiles:
167
166
  ```bash
168
167
  # TEST
169
168
  export REVO_URL="https://api.test.revong.com"
170
- export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
171
169
  export REVO_TOKEN="your-test-token"
172
170
  revo endpoints
173
171
 
174
172
  # PROD
175
173
  export REVO_URL="https://api.revoengine.com"
176
- export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
177
174
  export REVO_TOKEN="your-prod-token"
178
175
  revo endpoints
179
176
  ```
@@ -181,17 +178,15 @@ revo endpoints
181
178
  Supported environment variables:
182
179
 
183
180
  - `REVO_URL` or `REVO_BASE_URL`
184
- - `REVO_INSTANCE`
185
181
  - `REVO_TOKEN` or `REVO_API_KEY`
182
+ - `REVO_NO_UPDATE_NOTIFIER`
186
183
  - `REVOENGINE_URL` or `REVOENGINE_BASE_URL`
187
- - `REVOENGINE_INSTANCE`
188
184
  - `REVOENGINE_TOKEN` or `REVOENGINE_API_KEY`
189
185
 
190
186
  Example for CI:
191
187
 
192
188
  ```bash
193
189
  export REVO_URL="https://api.revoengine.com"
194
- export REVO_INSTANCE="a931e927-738d-42a4-b486-5fc19bfb3dfb"
195
190
  export REVO_TOKEN="your-ci-token"
196
191
  revo endpoints
197
192
  ```
@@ -201,6 +196,22 @@ Stored config lives in:
201
196
  - macOS and Linux: `~/.config/revoengine`
202
197
  - Windows: `%APPDATA%/revoengine`
203
198
 
199
+ ## Update notifications
200
+
201
+ When running in an interactive terminal, the CLI checks the npm registry at most once every 24 hours and prints a colored notice on stderr when a newer `@revoengine/cli` version is available.
202
+
203
+ Disable the check for one run:
204
+
205
+ ```bash
206
+ revo --no-update-notifier endpoints
207
+ ```
208
+
209
+ Disable it through the environment:
210
+
211
+ ```bash
212
+ export REVO_NO_UPDATE_NOTIFIER=1
213
+ ```
214
+
204
215
  ## Local development
205
216
 
206
217
  ```bash
package/dist/src/cli.js CHANGED
@@ -5,6 +5,7 @@ import { resolveRuntimeConfig } from "./config.js";
5
5
  import { handleAuthCommand, handleComponentCommand, handleEndpointsCommand, handleInfoCommand, handleProjectCommand, handleRequestCommand, handleSearchCommand, } from "./commands/index.js";
6
6
  import { buildRuntimeViewModel } from "./runtime-view.js";
7
7
  import { renderHelp, renderOverview } from "./ui.js";
8
+ import { maybeNotifyUpdateAvailable } from "./update-notifier.js";
8
9
  import { readPackageVersion } from "./utils.js";
9
10
  const packageVersion = readPackageVersion();
10
11
  const ANSI = {
@@ -137,7 +138,6 @@ function printError(error) {
137
138
  function resolveClient(args) {
138
139
  const runtime = resolveRuntimeConfig({
139
140
  baseUrl: typeof args.url === 'string' ? args.url : typeof args.baseUrl === 'string' ? args.baseUrl : undefined,
140
- instance: typeof args.instance === 'string' ? args.instance : typeof args.i === 'string' ? args.i : undefined,
141
141
  token: typeof args.token === 'string' ? args.token : typeof args.t === 'string' ? args.t : undefined,
142
142
  });
143
143
  return new RevoClient(runtime);
@@ -159,6 +159,10 @@ function printHelp() {
159
159
  }
160
160
  export async function main(argv = process.argv.slice(2)) {
161
161
  const args = parseArgs(argv);
162
+ await maybeNotifyUpdateAvailable({
163
+ args,
164
+ currentVersion: packageVersion,
165
+ });
162
166
  const command = args._[0];
163
167
  const noPositionals = args._.length === 0;
164
168
  if (command === 'version' || ((args.version !== undefined || args.V !== undefined) && noPositionals)) {
@@ -11,7 +11,6 @@ export type ComponentListRequest = {
11
11
  };
12
12
  export type ClientOptions = {
13
13
  baseUrl?: string;
14
- instance?: string;
15
14
  token?: string;
16
15
  fetch?: typeof fetch;
17
16
  };
@@ -37,7 +36,6 @@ declare function buildUrl(baseUrl: string, requestPath: string, query?: Record<s
37
36
  declare function normalizeToken(token?: string): string;
38
37
  export declare class RevoClient {
39
38
  baseUrl: string;
40
- instance: string;
41
39
  token: string;
42
40
  fetchImpl: typeof fetch | undefined;
43
41
  constructor(options?: ClientOptions);
@@ -1,4 +1,4 @@
1
- import { AUTH_VALIDATION_TTL_MS, buildAuthValidationKey, DEFAULT_BASE_URL, isUuid, readAuthValidationState, resolveRuntimeConfig, saveAuthValidationState, } from "./config.js";
1
+ import { AUTH_VALIDATION_TTL_MS, buildAuthValidationKey, DEFAULT_BASE_URL, readAuthValidationState, resolveRuntimeConfig, saveAuthValidationState, } from "./config.js";
2
2
  import { createSpinner } from "./spinner.js";
3
3
  export class ApiError extends Error {
4
4
  status;
@@ -127,13 +127,11 @@ async function readResponseData(response) {
127
127
  }
128
128
  export class RevoClient {
129
129
  baseUrl;
130
- instance;
131
130
  token;
132
131
  fetchImpl;
133
132
  constructor(options = {}) {
134
133
  const config = resolveRuntimeConfig(options);
135
134
  this.baseUrl = config.baseUrl;
136
- this.instance = config.instance;
137
135
  this.token = config.token;
138
136
  this.fetchImpl = options.fetch || globalThis.fetch;
139
137
  }
@@ -143,7 +141,6 @@ export class RevoClient {
143
141
  get authValidationKey() {
144
142
  return buildAuthValidationKey({
145
143
  baseUrl: this.baseUrl,
146
- instance: this.instance,
147
144
  token: this.token,
148
145
  });
149
146
  }
@@ -154,12 +151,6 @@ export class RevoClient {
154
151
  if (!this.token) {
155
152
  throw new Error('Missing API token. Run `revo auth login` or set REVO_TOKEN / REVO_API_KEY.');
156
153
  }
157
- if (!this.instance) {
158
- throw new Error('Missing instance UUID. Run `revo auth login` or set REVO_INSTANCE.');
159
- }
160
- if (!isUuid(this.instance)) {
161
- throw new Error(`Invalid instance UUID: ${this.instance}`);
162
- }
163
154
  }
164
155
  async validateSession(options = {}) {
165
156
  this.assertReady();
@@ -215,8 +206,6 @@ export class RevoClient {
215
206
  const headers = new Headers(options.headers || {});
216
207
  headers.set('Authorization', this.authHeader);
217
208
  headers.set('x-api-key', this.token);
218
- headers.set('instance', this.instance);
219
- headers.set('x-api-instance', this.instance);
220
209
  let body = options.body;
221
210
  if (body !== undefined && body !== null && method.toUpperCase() !== 'GET') {
222
211
  headers.set('Content-Type', 'application/json');
@@ -1,5 +1,5 @@
1
1
  import path from 'node:path';
2
- import { clearAuthValidationState, clearStoredConfig, getConfigDir, isUuid, loadStoredConfig, resolveRuntimeConfig, saveStoredConfig, } from "../config.js";
2
+ import { clearAuthValidationState, clearStoredConfig, getConfigDir, loadStoredConfig, resolveRuntimeConfig, saveStoredConfig, } from "../config.js";
3
3
  import { readLegacyImportFromFile } from "../legacy.js";
4
4
  import { promptText } from "../prompt.js";
5
5
  import { buildRuntimeViewModel } from "../runtime-view.js";
@@ -8,28 +8,19 @@ import { readBoolFlag, readFlag } from "../utils.js";
8
8
  function getConfigInput(args) {
9
9
  return resolveRuntimeConfig({
10
10
  baseUrl: readFlag(args, ['url', 'baseUrl']) || '',
11
- instance: readFlag(args, ['instance', 'i']) || '',
12
11
  token: readFlag(args, ['token', 't']) || '',
13
12
  });
14
13
  }
15
14
  async function promptMissingConfig(runtime) {
16
15
  let token = runtime.token;
17
- let instance = runtime.instance;
18
16
  if (!token) {
19
17
  token = (await promptText('Paste API key (input is visible)')).trim();
20
18
  }
21
- if (!instance) {
22
- instance = (await promptText('Paste instance ID')).trim();
23
- }
24
19
  if (!token) {
25
20
  throw new Error('API key is required.');
26
21
  }
27
- if (!instance || !isUuid(instance)) {
28
- throw new Error('Instance ID must be a UUID.');
29
- }
30
22
  return {
31
23
  token,
32
- instance,
33
24
  baseUrl: runtime.baseUrl,
34
25
  };
35
26
  }
@@ -44,7 +35,6 @@ export async function handleAuthCommand(context) {
44
35
  if (subcommand === 'get') {
45
36
  const stored = loadStoredConfig();
46
37
  print({
47
- instance: stored.instance || null,
48
38
  baseUrl: stored.baseUrl,
49
39
  tokenConfigured: Boolean(stored.token),
50
40
  configDir: getConfigDir(),
@@ -52,21 +42,7 @@ export async function handleAuthCommand(context) {
52
42
  return;
53
43
  }
54
44
  if (subcommand === 'set') {
55
- const instance = readFlag(args, ['instance', 'i']);
56
- if (!instance) {
57
- throw new Error('Missing instance UUID.');
58
- }
59
- if (!isUuid(instance)) {
60
- throw new Error('Instance must be a UUID.');
61
- }
62
- const stored = loadStoredConfig();
63
- saveStoredConfig({
64
- baseUrl: stored.baseUrl,
65
- token: stored.token,
66
- instance,
67
- });
68
- println(`Saved default instance ${instance}.`);
69
- return;
45
+ throw new Error('`revo auth set` was removed because API-key credentials are no longer instance-scoped.');
70
46
  }
71
47
  if (subcommand === 'status') {
72
48
  const view = await buildRuntimeViewModel(context, { forceValidation: true });
@@ -86,9 +62,8 @@ export async function handleAuthCommand(context) {
86
62
  println(`Imported legacy config from ${filePath}.`);
87
63
  if (!offline) {
88
64
  client.token = imported.token;
89
- client.instance = imported.instance;
90
65
  client.baseUrl = imported.baseUrl;
91
- if (imported.token && imported.instance) {
66
+ if (imported.token) {
92
67
  try {
93
68
  await client.validateSession({ force: true });
94
69
  }
@@ -105,14 +80,13 @@ export async function handleAuthCommand(context) {
105
80
  throw new Error('Unknown auth command.');
106
81
  }
107
82
  const stored = loadStoredConfig();
108
- if (stored.token && stored.instance) {
109
- println(`Warning: already logged in for instance ${stored.instance}. Run \`revo auth logout\` before logging in again.`);
83
+ if (stored.token) {
84
+ println('Warning: already logged in. Run `revo auth logout` before logging in again.');
110
85
  return;
111
86
  }
112
87
  const runtime = getConfigInput(args);
113
- const next = runtime.token && runtime.instance ? runtime : await promptMissingConfig(runtime);
88
+ const next = runtime.token ? runtime : await promptMissingConfig(runtime);
114
89
  client.token = next.token;
115
- client.instance = next.instance;
116
90
  client.baseUrl = next.baseUrl;
117
91
  try {
118
92
  await client.validateSession({ force: true });
@@ -837,7 +837,7 @@ async function debugSingleComponent(context, componentId) {
837
837
  const profile = await context.client.me();
838
838
  const sandboxEndpoint = extractSandboxEndpoint(profile);
839
839
  if (!sandboxEndpoint) {
840
- throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run for this instance.');
840
+ throw new Error('Authenticated profile did not include `endpoints.sandbox`, so component debug cannot run.');
841
841
  }
842
842
  const response = await context.client.debugComponent(buildSandboxDebugUrl(sandboxEndpoint), {
843
843
  elements: buildDebugElements(component),
@@ -1,6 +1,5 @@
1
1
  import path from 'node:path';
2
- import { isUuid, loadStoredConfigIndex } from "../config.js";
3
- import { buildProjectSyncState, buildEditorTypesUrl, extractSandboxEndpoint, extractEditorTypesBundle, findProjectMetadataFile, resolveProjectInvocation, resolveProjectRoot, resolveProjectTarget, syncProjectFiles, } from "../project.js";
2
+ import { buildProjectSyncState, buildEditorTypesUrl, extractSandboxEndpoint, extractEditorTypesBundle, resolveProjectInvocation, resolveProjectTarget, syncProjectFiles, } from "../project.js";
4
3
  function printSyncSummary(println, targetDir, result, prefix) {
5
4
  println(`${prefix} ${targetDir}`);
6
5
  println(`Wrote ${path.relative(targetDir, result.typesFile)}`);
@@ -20,12 +19,11 @@ async function resolveProjectSyncInput(context) {
20
19
  const profile = await client.me();
21
20
  const sandboxEndpoint = extractSandboxEndpoint(profile);
22
21
  if (!sandboxEndpoint) {
23
- throw new Error('Authenticated profile did not include `endpoints.sandbox`, so editor types cannot be initialized for this instance.');
22
+ throw new Error('Authenticated profile did not include `endpoints.sandbox`, so editor types cannot be initialized.');
24
23
  }
25
24
  const bundle = extractEditorTypesBundle(await client.getEditorTypes(buildEditorTypesUrl(sandboxEndpoint)));
26
25
  return buildProjectSyncState({
27
26
  fallbackEndpoint: sandboxEndpoint,
28
- fallbackInstanceId: client.instance,
29
27
  bundle,
30
28
  });
31
29
  }
@@ -35,41 +33,6 @@ export async function handleProjectCommand(context) {
35
33
  if (invocation.action === 'update') {
36
34
  throw new Error('`revo project update` is not implemented yet.');
37
35
  }
38
- if (invocation.action === 'switch') {
39
- if (invocation.extraArgs.length > 0) {
40
- throw new Error('Project switch accepts exactly one instance ID.');
41
- }
42
- const instanceId = invocation.targetArg || '';
43
- if (!instanceId) {
44
- throw new Error('Missing instance UUID. Usage: `revo project switch <instanceId>`.');
45
- }
46
- if (!isUuid(instanceId)) {
47
- throw new Error('Instance ID must be a UUID.');
48
- }
49
- const metadataFile = findProjectMetadataFile(cwd);
50
- if (!metadataFile) {
51
- throw new Error('Current directory is not inside an initialized Revo project.');
52
- }
53
- const projectRoot = resolveProjectRoot(cwd);
54
- if (!projectRoot) {
55
- throw new Error('Unable to resolve the current Revo project root.');
56
- }
57
- const storedConfigs = loadStoredConfigIndex();
58
- const selected = storedConfigs.instances[instanceId];
59
- if (!selected) {
60
- throw new Error(`No stored credentials found for instance ${instanceId}. Run \`revo auth login --instance ${instanceId}\` first.`);
61
- }
62
- if (!selected.token) {
63
- throw new Error(`Stored credentials for instance ${instanceId} are incomplete. Run \`revo auth login --instance ${instanceId}\` again.`);
64
- }
65
- client.baseUrl = selected.baseUrl;
66
- client.instance = instanceId;
67
- client.token = selected.token;
68
- const syncInput = await resolveProjectSyncInput(context);
69
- const result = syncProjectFiles(projectRoot, syncInput);
70
- printSyncSummary(println, projectRoot, result, `Switched Revo project to ${instanceId} in`);
71
- return;
72
- }
73
36
  if (invocation.extraArgs.length > 0) {
74
37
  throw new Error('Project init accepts at most one path argument.');
75
38
  }
@@ -2,22 +2,14 @@ export declare const APP_NAME = "revoengine";
2
2
  export declare const DEFAULT_BASE_URL = "https://api.revoengine.com";
3
3
  export type RuntimeConfig = {
4
4
  baseUrl: string;
5
- instance: string;
6
5
  token: string;
7
6
  };
8
7
  export type RuntimeConfigOptions = {
9
8
  baseUrl?: string;
10
- instance?: string;
11
9
  token?: string;
12
10
  };
13
- type StoredConfig = RuntimeConfig;
14
- type StoredInstanceConfig = {
15
- baseUrl: string;
16
- token: string;
17
- };
18
- type StoredConfigFile = {
19
- defaultInstance: string;
20
- instances: Record<string, StoredInstanceConfig>;
11
+ type LegacyRuntimeConfig = RuntimeConfig & {
12
+ instance?: string;
21
13
  };
22
14
  export type AuthValidationState = {
23
15
  key: string;
@@ -35,11 +27,20 @@ export declare function getConfigPaths(): {
35
27
  };
36
28
  export declare function readProjectInstanceId(startDir?: string): string;
37
29
  export declare function isUuid(value: unknown): value is string;
38
- export declare function saveStoredConfig(nextConfig: Partial<StoredConfig>): void;
30
+ export declare function saveStoredConfig(nextConfig: Partial<LegacyRuntimeConfig>): void;
39
31
  export declare function clearStoredConfig(): void;
40
- export declare function loadStoredConfigIndex(): StoredConfigFile;
32
+ export declare function loadStoredConfigIndex(): {
33
+ defaultInstance: string;
34
+ instances: {
35
+ default: {
36
+ baseUrl: string;
37
+ token: string;
38
+ };
39
+ } | {
40
+ default?: undefined;
41
+ };
42
+ };
41
43
  export declare function loadStoredConfig(options?: {
42
- instance?: string;
43
44
  allowLegacy?: boolean;
44
45
  }): RuntimeConfig;
45
46
  export declare function buildAuthValidationKey(runtime: RuntimeConfigOptions): string;
@@ -48,7 +49,6 @@ export declare function saveAuthValidationState(state: AuthValidationState): voi
48
49
  export declare function clearAuthValidationState(key?: string): void;
49
50
  export declare function resolveRuntimeConfig(options?: RuntimeConfigOptions): {
50
51
  baseUrl: string;
51
- instance: string;
52
52
  token: string;
53
53
  };
54
54
  export {};
@@ -73,20 +73,10 @@ export function saveStoredConfig(nextConfig) {
73
73
  const { dir, configFile, credentialsFile } = getConfigPaths();
74
74
  ensureDirectory(dir);
75
75
  const current = readStoredConfigFile();
76
- const fallback = current.defaultInstance ? current.instances[current.defaultInstance] : undefined;
77
- const instance = nextConfig.instance || current.defaultInstance || '';
78
- if (!instance) {
79
- writeJsonFile(configFile, current);
80
- removeFileIfExists(credentialsFile);
81
- return;
82
- }
83
- const currentEntry = current.instances[instance];
84
- current.instances[instance] = {
85
- baseUrl: nextConfig.baseUrl || currentEntry?.baseUrl || fallback?.baseUrl || DEFAULT_BASE_URL,
86
- token: nextConfig.token || currentEntry?.token || fallback?.token || '',
87
- };
88
- current.defaultInstance = instance;
89
- writeJsonFile(configFile, current);
76
+ writeJsonFile(configFile, {
77
+ baseUrl: nextConfig.baseUrl || current.baseUrl || DEFAULT_BASE_URL,
78
+ token: nextConfig.token || current.token || '',
79
+ });
90
80
  removeFileIfExists(credentialsFile);
91
81
  }
92
82
  export function clearStoredConfig() {
@@ -131,8 +121,8 @@ function migrateStoredConfigBestEffort(configFile, credentialsFile, nextConfig)
131
121
  }
132
122
  function emptyStoredConfigFile() {
133
123
  return {
134
- defaultInstance: '',
135
- instances: {},
124
+ baseUrl: DEFAULT_BASE_URL,
125
+ token: '',
136
126
  };
137
127
  }
138
128
  function isRecord(value) {
@@ -151,6 +141,24 @@ function normalizeInstanceConfig(value) {
151
141
  token: typeof value.token === 'string' ? value.token : '',
152
142
  };
153
143
  }
144
+ function parseFlatConfig(raw, credentials) {
145
+ if (!isRecord(raw) || isRecord(raw.instances)) {
146
+ return null;
147
+ }
148
+ const token = typeof raw.token === 'string'
149
+ ? raw.token
150
+ : isRecord(credentials) && typeof credentials.token === 'string'
151
+ ? credentials.token
152
+ : '';
153
+ return {
154
+ baseUrl: typeof raw.baseUrl === 'string'
155
+ ? raw.baseUrl
156
+ : typeof raw.url === 'string'
157
+ ? raw.url
158
+ : DEFAULT_BASE_URL,
159
+ token,
160
+ };
161
+ }
154
162
  function parseMappedConfig(raw) {
155
163
  if (!isRecord(raw) || !isRecord(raw.instances)) {
156
164
  return null;
@@ -169,10 +177,13 @@ function parseMappedConfig(raw) {
169
177
  const defaultInstance = isUuid(configuredDefault) && instances[configuredDefault]
170
178
  ? configuredDefault
171
179
  : Object.keys(instances)[0] || '';
172
- return {
173
- defaultInstance,
174
- instances,
175
- };
180
+ const selected = defaultInstance ? instances[defaultInstance] : undefined;
181
+ return selected
182
+ ? {
183
+ baseUrl: selected.baseUrl,
184
+ token: selected.token,
185
+ }
186
+ : emptyStoredConfigFile();
176
187
  }
177
188
  function parseLegacyConfig(config, credentials) {
178
189
  const baseUrl = isRecord(config) && typeof config.baseUrl === 'string'
@@ -180,58 +191,56 @@ function parseLegacyConfig(config, credentials) {
180
191
  : isRecord(config) && typeof config.url === 'string'
181
192
  ? config.url
182
193
  : DEFAULT_BASE_URL;
183
- const instance = isRecord(config) && typeof config.instance === 'string' ? config.instance : '';
184
194
  const token = isRecord(credentials) && typeof credentials.token === 'string' ? credentials.token : '';
185
- if (!instance) {
186
- return emptyStoredConfigFile();
187
- }
188
195
  return {
189
- defaultInstance: instance,
190
- instances: {
191
- [instance]: {
192
- baseUrl,
193
- token,
194
- },
195
- },
196
+ baseUrl,
197
+ token,
196
198
  };
197
199
  }
198
200
  function readStoredConfigFile() {
199
201
  const { configFile, credentialsFile } = getConfigPaths();
200
202
  const config = fs.existsSync(configFile) ? readJsonFile(configFile) : {};
201
203
  const credentials = fs.existsSync(credentialsFile) ? readJsonFile(credentialsFile) : {};
202
- const mapped = parseMappedConfig(config);
203
- if (mapped) {
204
+ const flat = parseFlatConfig(config, credentials);
205
+ if (flat) {
204
206
  if (fs.existsSync(credentialsFile)) {
205
- migrateStoredConfigBestEffort(configFile, credentialsFile, mapped);
207
+ migrateStoredConfigBestEffort(configFile, credentialsFile, flat);
206
208
  }
209
+ return flat;
210
+ }
211
+ const mapped = parseMappedConfig(config);
212
+ if (mapped) {
213
+ migrateStoredConfigBestEffort(configFile, credentialsFile, mapped);
207
214
  return mapped;
208
215
  }
209
216
  const legacy = parseLegacyConfig(config, credentials);
210
- if (legacy.defaultInstance) {
217
+ if (legacy.token || legacy.baseUrl !== DEFAULT_BASE_URL) {
211
218
  migrateStoredConfigBestEffort(configFile, credentialsFile, legacy);
212
219
  }
213
220
  return legacy;
214
221
  }
215
222
  export function loadStoredConfigIndex() {
216
- return readStoredConfigFile();
217
- }
218
- function readStoredConfig(options = {}) {
219
223
  const stored = readStoredConfigFile();
220
- const selectedInstance = options.instance && stored.instances[options.instance]
221
- ? options.instance
222
- : stored.defaultInstance;
223
- const selected = selectedInstance ? stored.instances[selectedInstance] : undefined;
224
224
  return {
225
- baseUrl: selected?.baseUrl || DEFAULT_BASE_URL,
226
- instance: selectedInstance || '',
227
- token: selected?.token || '',
225
+ defaultInstance: '',
226
+ instances: stored.token
227
+ ? {
228
+ default: {
229
+ baseUrl: stored.baseUrl,
230
+ token: stored.token,
231
+ },
232
+ }
233
+ : {},
228
234
  };
229
235
  }
236
+ function readStoredConfig() {
237
+ return readStoredConfigFile();
238
+ }
230
239
  function mergeWithLegacy(stored, options = {}) {
231
240
  if (options.allowLegacy === false) {
232
241
  return stored;
233
242
  }
234
- if ((stored.token && stored.instance) || !process.cwd()) {
243
+ if (stored.token || !process.cwd()) {
235
244
  return stored;
236
245
  }
237
246
  const legacy = readLegacyImportFromProject(process.cwd());
@@ -240,22 +249,20 @@ function mergeWithLegacy(stored, options = {}) {
240
249
  }
241
250
  const merged = {
242
251
  baseUrl: stored.baseUrl || legacy.baseUrl || DEFAULT_BASE_URL,
243
- instance: stored.instance || options.instance || legacy.instance || '',
244
252
  token: stored.token || legacy.token || '',
245
253
  };
246
- if (merged.token || merged.instance || merged.baseUrl !== DEFAULT_BASE_URL) {
254
+ if (merged.token || merged.baseUrl !== DEFAULT_BASE_URL) {
247
255
  saveStoredConfig(merged);
248
256
  }
249
257
  return merged;
250
258
  }
251
259
  export function loadStoredConfig(options = {}) {
252
- return mergeWithLegacy(readStoredConfig(options), options);
260
+ return mergeWithLegacy(readStoredConfig(), options);
253
261
  }
254
262
  export function buildAuthValidationKey(runtime) {
255
263
  return createHash('sha256')
256
264
  .update(JSON.stringify({
257
265
  baseUrl: runtime.baseUrl || DEFAULT_BASE_URL,
258
- instance: runtime.instance || '',
259
266
  token: runtime.token || '',
260
267
  }))
261
268
  .digest('hex');
@@ -339,18 +346,11 @@ export function clearAuthValidationState(key) {
339
346
  export function resolveRuntimeConfig(options = {}) {
340
347
  const env = {
341
348
  baseUrl: resolveEnvValue(['REVO_URL', 'REVO_BASE_URL', 'REVOENGINE_URL', 'REVOENGINE_BASE_URL']),
342
- instance: resolveEnvValue(['REVO_INSTANCE', 'REVOENGINE_INSTANCE']),
343
349
  token: resolveEnvValue(['REVO_TOKEN', 'REVO_API_KEY', 'REVOENGINE_TOKEN', 'REVOENGINE_API_KEY']),
344
350
  };
345
- const projectInstance = !options.instance && !env.instance ? readProjectInstanceId() : '';
346
- const requestedInstance = options.instance || env.instance || projectInstance || undefined;
347
- const stored = loadStoredConfig({
348
- instance: requestedInstance,
349
- allowLegacy: !requestedInstance,
350
- });
351
+ const stored = loadStoredConfig();
351
352
  return {
352
353
  baseUrl: options.baseUrl || env.baseUrl || stored.baseUrl || DEFAULT_BASE_URL,
353
- instance: options.instance || env.instance || stored.instance || '',
354
354
  token: options.token || env.token || stored.token || '',
355
355
  };
356
356
  }
@@ -11,14 +11,13 @@ export declare const REVO_DEBUG_CONFIG_FILE: string;
11
11
  export declare const REVO_PLAYGROUND_JS_FILE: string;
12
12
  export declare const REVO_PLAYGROUND_TS_FILE: string;
13
13
  export type ProjectInvocation = {
14
- action: 'init' | 'switch' | 'update';
14
+ action: 'init' | 'update';
15
15
  targetArg?: string;
16
16
  extraArgs: string[];
17
17
  };
18
18
  export type EditorTypesBundle = {
19
19
  code: string;
20
20
  endpoint?: string;
21
- instanceId?: string;
22
21
  apiVersion?: string;
23
22
  libVersion?: string;
24
23
  hash?: string;
@@ -27,7 +26,6 @@ export type EditorTypesBundle = {
27
26
  export type RevoProjectMetadata = {
28
27
  schemaVersion: 1;
29
28
  endpoint: string;
30
- instanceId: string;
31
29
  authProfile: string;
32
30
  apiVersion: string;
33
31
  libVersion: string;
@@ -36,7 +34,6 @@ export type RevoProjectMetadata = {
36
34
  };
37
35
  export type ProjectSyncInput = {
38
36
  endpoint: string;
39
- instanceId: string;
40
37
  code: string;
41
38
  apiVersion: string;
42
39
  libVersion: string;
@@ -60,13 +57,11 @@ export declare function buildSandboxDebugUrl(endpoint: string): string;
60
57
  export declare function extractSandboxEndpoint(profile: unknown): string | null;
61
58
  export declare function buildProjectMetadata(input: {
62
59
  baseUrl: string;
63
- instance: string;
64
60
  bundle: EditorTypesBundle;
65
61
  now?: Date;
66
62
  }): {
67
63
  schemaVersion: 1;
68
64
  endpoint: string;
69
- instanceId: string;
70
65
  authProfile: string;
71
66
  apiVersion: string;
72
67
  libVersion: string;
@@ -78,12 +73,10 @@ export declare function readProjectMetadata(startDir?: string): RevoProjectMetad
78
73
  export declare function resolveProjectRoot(startDir?: string): string;
79
74
  export declare function buildProjectSyncState(input: {
80
75
  fallbackEndpoint: string;
81
- fallbackInstanceId: string;
82
76
  bundle: EditorTypesBundle;
83
77
  now?: Date;
84
78
  }): {
85
79
  endpoint: string;
86
- instanceId: string;
87
80
  code: string;
88
81
  apiVersion: string;
89
82
  libVersion: string;
@@ -192,13 +192,16 @@ export function resolveProjectInvocation(args) {
192
192
  extraArgs: [],
193
193
  };
194
194
  }
195
- if (first === 'init' || first === 'switch' || first === 'update') {
195
+ if (first === 'init' || first === 'update') {
196
196
  return {
197
197
  action: first,
198
198
  targetArg: second,
199
199
  extraArgs: rest,
200
200
  };
201
201
  }
202
+ if (first === 'switch') {
203
+ throw new Error('`revo project switch` was removed because credentials are no longer instance-scoped.');
204
+ }
202
205
  return {
203
206
  action: 'init',
204
207
  targetArg: first,
@@ -221,7 +224,6 @@ export function extractEditorTypesBundle(payload) {
221
224
  return {
222
225
  code,
223
226
  endpoint: readString(root.endpoint) || readString(meta?.endpoint),
224
- instanceId: readString(root.instanceId) || readString(root.instance) || readString(meta?.instanceId) || readString(meta?.instance),
225
227
  apiVersion: readString(root.apiVersion) || readString(meta?.apiVersion),
226
228
  libVersion: readString(root.libVersion) || readString(meta?.libVersion),
227
229
  hash: readString(root.hash) || readString(meta?.hash),
@@ -265,27 +267,27 @@ export function extractSandboxEndpoint(profile) {
265
267
  roots.push(nestedProfile);
266
268
  }
267
269
  for (const root of roots) {
268
- const instance = root.instance;
269
- if (!instance || typeof instance !== 'object' || Array.isArray(instance)) {
270
- continue;
271
- }
272
- const endpoints = instance.endpoints;
273
- if (!endpoints || typeof endpoints !== 'object' || Array.isArray(endpoints)) {
274
- continue;
275
- }
276
- const sandbox = endpoints.sandbox;
277
- if (typeof sandbox === 'string' && sandbox) {
278
- return normalizeEndpoint(sandbox);
270
+ const endpointContainers = [
271
+ root.endpoints,
272
+ isRecord(root.instance) ? root.instance.endpoints : null,
273
+ ];
274
+ for (const endpoints of endpointContainers) {
275
+ if (!isRecord(endpoints)) {
276
+ continue;
277
+ }
278
+ const sandbox = endpoints.sandbox;
279
+ if (typeof sandbox === 'string' && sandbox) {
280
+ return normalizeEndpoint(sandbox);
281
+ }
279
282
  }
280
283
  }
281
284
  return null;
282
285
  }
283
286
  export function buildProjectMetadata(input) {
284
- const { baseUrl, bundle, instance, now = new Date() } = input;
287
+ const { baseUrl, bundle, now = new Date() } = input;
285
288
  return {
286
289
  schemaVersion: 1,
287
290
  endpoint: bundle.endpoint || buildEditorEndpoint(baseUrl),
288
- instanceId: bundle.instanceId || instance,
289
291
  authProfile: 'default',
290
292
  apiVersion: getRequiredMetadataValue(bundle, 'apiVersion'),
291
293
  libVersion: getRequiredMetadataValue(bundle, 'libVersion'),
@@ -319,8 +321,7 @@ export function readProjectMetadata(startDir = process.cwd()) {
319
321
  if (!isRecord(raw)) {
320
322
  return null;
321
323
  }
322
- if (typeof raw.instanceId !== 'string'
323
- || typeof raw.endpoint !== 'string'
324
+ if (typeof raw.endpoint !== 'string'
324
325
  || typeof raw.authProfile !== 'string'
325
326
  || typeof raw.apiVersion !== 'string'
326
327
  || typeof raw.libVersion !== 'string'
@@ -337,13 +338,11 @@ export function resolveProjectRoot(startDir = process.cwd()) {
337
338
  export function buildProjectSyncState(input) {
338
339
  const metadata = buildProjectMetadata({
339
340
  baseUrl: input.fallbackEndpoint,
340
- instance: input.fallbackInstanceId,
341
341
  bundle: input.bundle,
342
342
  now: input.now,
343
343
  });
344
344
  return {
345
345
  endpoint: metadata.endpoint,
346
- instanceId: metadata.instanceId,
347
346
  code: input.bundle.code,
348
347
  apiVersion: metadata.apiVersion,
349
348
  libVersion: metadata.libVersion,
@@ -362,7 +361,6 @@ export function syncProjectFiles(targetDir, input) {
362
361
  writeJsonFile(metadataFile, {
363
362
  schemaVersion: 1,
364
363
  endpoint: input.endpoint,
365
- instanceId: input.instanceId,
366
364
  authProfile: 'default',
367
365
  apiVersion: input.apiVersion,
368
366
  libVersion: input.libVersion,
@@ -7,11 +7,6 @@ function resolveArgsRuntime(context) {
7
7
  : typeof context.args.baseUrl === 'string'
8
8
  ? context.args.baseUrl
9
9
  : undefined,
10
- instance: typeof context.args.instance === 'string'
11
- ? context.args.instance
12
- : typeof context.args.i === 'string'
13
- ? context.args.i
14
- : undefined,
15
10
  token: typeof context.args.token === 'string'
16
11
  ? context.args.token
17
12
  : typeof context.args.t === 'string'
@@ -49,9 +44,8 @@ export async function buildRuntimeViewModel(context, options = {}) {
49
44
  validation: null,
50
45
  email: null,
51
46
  };
52
- if (runtime.token && runtime.instance) {
47
+ if (runtime.token) {
53
48
  context.client.baseUrl = runtime.baseUrl;
54
- context.client.instance = runtime.instance;
55
49
  context.client.token = runtime.token;
56
50
  try {
57
51
  const profile = await context.client.me({ force: options.forceValidation });
package/dist/src/ui.js CHANGED
@@ -95,16 +95,15 @@ function renderCommandCatalog() {
95
95
  '',
96
96
  paintHeader('Global Options'),
97
97
  commandRow('--url <url>', 'Override API base URL for TEST, prod, local, or dedicated tenants'),
98
- commandRow('--instance <uuid>', 'Override instance ID'),
99
98
  commandRow('--token <apiKey>', 'Override API key for non-interactive runs'),
99
+ commandRow('--no-update-notifier', 'Disable npm version update checks for this run'),
100
100
  '',
101
101
  paintHeader('Core'),
102
102
  commandRow('revo -i', 'Show runtime info, config path, and auth state'),
103
- commandRow('revo auth login', 'Paste API key and instance ID in the terminal'),
103
+ commandRow('revo auth login', 'Paste API key in the terminal'),
104
104
  commandRow('revo auth status', 'Show the current authentication status'),
105
105
  commandRow('revo auth logout', 'Remove stored credentials'),
106
106
  commandRow('revo project [path]', 'Initialize ambient editor types for JS and TS'),
107
- commandRow('revo project switch <instanceId>', 'Switch the current project to another stored instance'),
108
107
  commandRow('revo endpoints', 'List available API endpoints'),
109
108
  '',
110
109
  paintHeader('Components'),
@@ -153,7 +152,6 @@ export function renderRuntimeInfo(input) {
153
152
  row('Config', configDir),
154
153
  row('Base URL', runtime.baseUrl),
155
154
  row('Status', formatStatusValue(auth)),
156
- row('Instance', runtime.instance || 'missing'),
157
155
  ...(shouldRenderValidationRow(auth) ? [row('Validation', auth.validation || 'skipped')] : []),
158
156
  '',
159
157
  paintHeader('Tips'),
@@ -168,7 +166,6 @@ export function renderAuthStatus(input) {
168
166
  row('Status', formatStatusValue(auth)),
169
167
  row('Config', configDir),
170
168
  row('Base URL', runtime.baseUrl),
171
- row('Instance', runtime.instance || 'missing'),
172
169
  row('API key', runtime.token ? 'configured' : 'missing'),
173
170
  ...(shouldRenderValidationRow(auth) ? [row('Validation', auth.validation || 'skipped')] : []),
174
171
  ].join('\n');
@@ -0,0 +1,16 @@
1
+ import type { ParsedArgs } from './types.ts';
2
+ type UpdateNotifierOptions = {
3
+ args?: ParsedArgs;
4
+ currentVersion: string;
5
+ fetch?: typeof fetch;
6
+ now?: number;
7
+ stream?: NodeJS.WriteStream;
8
+ };
9
+ export declare function compareSemver(left: string, right: string): 0 | 1 | -1;
10
+ export declare function renderUpdateNotice(input: {
11
+ currentVersion: string;
12
+ latestVersion: string;
13
+ stream?: NodeJS.WriteStream;
14
+ }): string;
15
+ export declare function maybeNotifyUpdateAvailable(options: UpdateNotifierOptions): Promise<void>;
16
+ export {};
@@ -0,0 +1,142 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { getConfigDir } from "./config.js";
4
+ import { CLI_NAME, CLI_PACKAGE } from "./ui.js";
5
+ import { readJsonFile, writeJsonFile } from "./utils.js";
6
+ const UPDATE_CHECK_TTL_MS = 24 * 60 * 60 * 1000;
7
+ const UPDATE_CHECK_TIMEOUT_MS = 1_000;
8
+ const UPDATE_STATE_FILE = 'update-state.json';
9
+ const NPM_PACKAGE_URL = `https://registry.npmjs.org/${encodeURIComponent(CLI_PACKAGE)}/latest`;
10
+ function isTruthyEnv(value) {
11
+ return value === '1' || value === 'true' || value === 'yes';
12
+ }
13
+ function updateNotifierDisabled(args) {
14
+ return Boolean(args?.['no-update-notifier'])
15
+ || isTruthyEnv(process.env.REVO_NO_UPDATE_NOTIFIER)
16
+ || isTruthyEnv(process.env.NO_UPDATE_NOTIFIER)
17
+ || isTruthyEnv(process.env.CI);
18
+ }
19
+ function getUpdateStatePath() {
20
+ return path.join(getConfigDir(), UPDATE_STATE_FILE);
21
+ }
22
+ function readUpdateState() {
23
+ const filePath = getUpdateStatePath();
24
+ if (!fs.existsSync(filePath)) {
25
+ return null;
26
+ }
27
+ try {
28
+ const raw = readJsonFile(filePath);
29
+ if (raw
30
+ && typeof raw === 'object'
31
+ && !Array.isArray(raw)
32
+ && typeof raw.checkedAt === 'number'
33
+ && typeof raw.latestVersion === 'string') {
34
+ return raw;
35
+ }
36
+ }
37
+ catch {
38
+ return null;
39
+ }
40
+ return null;
41
+ }
42
+ function saveUpdateState(state) {
43
+ try {
44
+ writeJsonFile(getUpdateStatePath(), state);
45
+ }
46
+ catch {
47
+ // Update checks are advisory and must never make a CLI command fail.
48
+ }
49
+ }
50
+ function parseVersion(version) {
51
+ const match = version.trim().match(/^v?(\d+)\.(\d+)\.(\d+)(?:[-+].*)?$/);
52
+ if (!match) {
53
+ return null;
54
+ }
55
+ return [Number(match[1]), Number(match[2]), Number(match[3])];
56
+ }
57
+ export function compareSemver(left, right) {
58
+ const parsedLeft = parseVersion(left);
59
+ const parsedRight = parseVersion(right);
60
+ if (!parsedLeft || !parsedRight) {
61
+ return 0;
62
+ }
63
+ for (let index = 0; index < parsedLeft.length; index += 1) {
64
+ if (parsedLeft[index] > parsedRight[index]) {
65
+ return 1;
66
+ }
67
+ if (parsedLeft[index] < parsedRight[index]) {
68
+ return -1;
69
+ }
70
+ }
71
+ return 0;
72
+ }
73
+ async function fetchLatestVersion(fetchImpl) {
74
+ const response = await fetchImpl(NPM_PACKAGE_URL, {
75
+ headers: {
76
+ accept: 'application/json',
77
+ },
78
+ signal: AbortSignal.timeout(UPDATE_CHECK_TIMEOUT_MS),
79
+ });
80
+ if (!response.ok) {
81
+ return '';
82
+ }
83
+ const data = await response.json();
84
+ return data && typeof data === 'object' && typeof data.version === 'string'
85
+ ? data.version
86
+ : '';
87
+ }
88
+ function supportsColor(stream) {
89
+ return Boolean(stream.isTTY || process.env.FORCE_COLOR);
90
+ }
91
+ function color(stream, text, code) {
92
+ return supportsColor(stream) ? `${code}${text}\u001b[0m` : text;
93
+ }
94
+ export function renderUpdateNotice(input) {
95
+ const stream = input.stream || process.stderr;
96
+ const yellow = '\u001b[33m';
97
+ const cyan = '\u001b[36m';
98
+ const bold = '\u001b[1m';
99
+ return [
100
+ color(stream, `Update available ${input.currentVersion} -> ${input.latestVersion}`, bold + yellow),
101
+ `Run ${color(stream, `npm install -g ${CLI_PACKAGE}`, cyan)} to update ${CLI_NAME}.`,
102
+ ].join('\n');
103
+ }
104
+ export async function maybeNotifyUpdateAvailable(options) {
105
+ const stream = options.stream || process.stderr;
106
+ const fetchImpl = options.fetch || globalThis.fetch;
107
+ const now = options.now || Date.now();
108
+ if (updateNotifierDisabled(options.args) || !stream.isTTY || !fetchImpl) {
109
+ return;
110
+ }
111
+ const cached = readUpdateState();
112
+ if (cached && now - cached.checkedAt < UPDATE_CHECK_TTL_MS) {
113
+ if (compareSemver(cached.latestVersion, options.currentVersion) > 0) {
114
+ stream.write(`${renderUpdateNotice({
115
+ currentVersion: options.currentVersion,
116
+ latestVersion: cached.latestVersion,
117
+ stream,
118
+ })}\n`);
119
+ }
120
+ return;
121
+ }
122
+ try {
123
+ const latestVersion = await fetchLatestVersion(fetchImpl);
124
+ if (!latestVersion) {
125
+ return;
126
+ }
127
+ saveUpdateState({
128
+ checkedAt: now,
129
+ latestVersion,
130
+ });
131
+ if (compareSemver(latestVersion, options.currentVersion) > 0) {
132
+ stream.write(`${renderUpdateNotice({
133
+ currentVersion: options.currentVersion,
134
+ latestVersion,
135
+ stream,
136
+ })}\n`);
137
+ }
138
+ }
139
+ catch {
140
+ // Network, registry, and cache failures must not affect the requested command.
141
+ }
142
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revoengine/cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "CLI package for the RevoEngine Platform API",
5
5
  "type": "module",
6
6
  "main": "dist/src/index.js",
@@ -10,7 +10,7 @@
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc -p tsconfig.build.json",
13
- "check": "node --input-type=module -e \"await import('./src/config.ts'); await import('./src/legacy.ts'); await import('./src/client.ts'); await import('./src/ui.ts'); await import('./src/commands/auth.ts'); await import('./src/commands/component.ts'); await import('./src/commands/endpoints.ts'); await import('./src/commands/info.ts'); await import('./src/commands/request.ts'); await import('./src/commands/search.ts'); await import('./src/cli.ts'); await import('./src/index.ts'); await import('./bin/revo.ts'); await import('./scripts/dev.ts')\"",
13
+ "check": "node --input-type=module -e \"await import('./src/config.ts'); await import('./src/legacy.ts'); await import('./src/client.ts'); await import('./src/ui.ts'); await import('./src/update-notifier.ts'); await import('./src/commands/auth.ts'); await import('./src/commands/component.ts'); await import('./src/commands/endpoints.ts'); await import('./src/commands/info.ts'); await import('./src/commands/request.ts'); await import('./src/commands/search.ts'); await import('./src/cli.ts'); await import('./src/index.ts'); await import('./bin/revo.ts'); await import('./scripts/dev.ts')\"",
14
14
  "dev": "node ./scripts/dev.ts",
15
15
  "prepack": "npm run build",
16
16
  "test": "node --test ./test/*.test.ts"