@tapi-dev/sdk 0.1.21 → 0.1.24

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
@@ -63,19 +63,18 @@ tapi sessions
63
63
  tapi publish
64
64
  ```
65
65
 
66
- Local authoring files live in the app repo:
67
-
68
- ```text
69
- .tapi/project.json
70
- .tapi/sitemaps/<site>.json
71
- .tapi/apis/<site>/<api>.json
72
- .tapi/generated/catalog.json
73
- src/tapi.generated.ts
74
- ```
75
-
76
- Drafts are local. `tapi publish` uploads local sitemaps and API contracts, marks
77
- ready requests as published, and activates one immutable server release. Runtime
78
- SDK calls read the active release catalog, not mutable Studio drafts.
66
+ Local project identity lives in the app repo:
67
+
68
+ ```text
69
+ .tapi/project.json
70
+ .tapi/generated/catalog.json
71
+ src/tapi.generated.ts
72
+ ```
73
+
74
+ Sitemaps and API contracts are saved in the bound Tapi server project. Run
75
+ `tapi publish` to mark ready server-side requests as published and activate one
76
+ immutable server release. Runtime SDK calls read the active release catalog, not
77
+ mutable Studio drafts.
79
78
 
80
79
  ```bash
81
80
  TAPI_API_KEY=tapi_project_key tapi publish
package/dist/cli.d.ts CHANGED
@@ -75,6 +75,8 @@ export interface ServiceStatus {
75
75
  installedVersion?: string;
76
76
  }
77
77
  export type FetchLike = typeof fetch;
78
+ type StudioPortAvailable = (port: number) => Promise<boolean>;
79
+ type StudioPortReaper = (preferred: number, count: number) => Promise<number>;
78
80
  export declare function runCli(argv?: string[]): Promise<number>;
79
81
  export declare function parseStudioOptions(args: string[]): StudioCliOptions;
80
82
  export declare function trySelectDevSessionInOpenStudio(session: TapiDevSession, options: StudioCliOptions, fetchImpl?: FetchLike): Promise<{
@@ -86,6 +88,7 @@ export declare function installedServiceVersionFromPathName(pathName: string): s
86
88
  export declare function serviceNeedsInstall(status: ServiceStatus, manifest: ServiceReleaseManifest): boolean;
87
89
  export declare function getDefaultStudioCacheDir(): string;
88
90
  export declare function getDefaultServiceCacheDir(): string;
91
+ export declare function getDefaultMachineServiceReleasesDir(): string | undefined;
89
92
  interface ServiceInstallLogPaths {
90
93
  logPath: string;
91
94
  resultPath: string;
@@ -113,11 +116,19 @@ export declare function compareVersions(left: string, right: string): number;
113
116
  export declare function memoizeStudioInstallToken(options: StudioCliOptions, installToken?: string): string;
114
117
  export declare function findPortablePlaywrightBrowsersDir(exePath: string): string | undefined;
115
118
  export declare function waitForHttpOk(url: string, timeoutMs?: number, intervalMs?: number, fetchImpl?: FetchLike): Promise<void>;
119
+ export declare function chooseStudioPort(preferred?: number, options?: {
120
+ count?: number;
121
+ portAvailable?: StudioPortAvailable;
122
+ reapBlockedPorts?: StudioPortReaper;
123
+ }): Promise<number>;
116
124
  export declare function studioInstanceRecordPathForOptions(options: StudioCliOptions): string;
117
125
  export declare function findPortableStudioServerExe(releaseDir: string, manifest: StudioReleaseManifest): string | undefined;
126
+ export declare function estimateRequiredExtractionBytes(zipBytes: number): number;
127
+ export declare function formatInsufficientExtractionSpace(zipPath: string, destination: string, availableBytes: number, requiredBytes: number): string;
118
128
  export declare function buildExpandArchiveCommand(zipPath: string, destination: string): string;
119
129
  export declare function formatZipExtractionFailure(zipPath: string, destination: string, error: unknown): string;
120
130
  export declare function pruneManagedFiles(directory: string, keepPaths: string[], isManagedFileName: (name: string) => boolean, maxNoncurrentEntries?: number, label?: string): Promise<number>;
131
+ export declare function pruneTapiReleaseDirs(directory: string, keepPaths: string[], isManagedDirName: (name: string) => boolean, maxNoncurrentEntries?: number, label?: string): Promise<number>;
121
132
  export declare function isTapiStudioDownloadArtifact(fileName: string): boolean;
122
133
  export declare function isTapiServiceDownloadArtifact(fileName: string): boolean;
123
134
  export {};
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { createServer } from "node:http";
4
4
  import { createServer as createNetServer } from "node:net";
5
5
  import { createHash, randomUUID } from "node:crypto";
6
6
  import { createReadStream, createWriteStream, existsSync, readFileSync, readdirSync } from "node:fs";
7
- import { mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "node:fs/promises";
7
+ import { mkdir, readFile, readdir, rename, rm, stat, statfs, unlink, writeFile } from "node:fs/promises";
8
8
  import { homedir } from "node:os";
9
9
  import { basename, dirname, join, resolve } from "node:path";
10
10
  import { performance } from "node:perf_hooks";
@@ -27,6 +27,8 @@ const MAX_WIDE_EVENT_PROCESS_ROOTS = 5;
27
27
  const MAX_NONCURRENT_MANAGED_DOWNLOADS = 0;
28
28
  const MAX_NONCURRENT_MANAGED_RELEASES = 0;
29
29
  const MANAGED_RELEASE_MARKER = ".tapi-managed-release";
30
+ const MIN_EXTRACTION_REQUIRED_BYTES = 1024 * 1024 * 1024;
31
+ const EXTRACTION_REQUIRED_MULTIPLIER = 3;
30
32
  class StudioInstallApprovalError extends Error {
31
33
  code;
32
34
  status;
@@ -138,13 +140,6 @@ export async function runCli(argv = process.argv.slice(2)) {
138
140
  }
139
141
  return runServiceCommand(subcommand, rest);
140
142
  }
141
- if (command === "publish") {
142
- if (hasHelpFlag([subcommand, ...rest])) {
143
- printPublishHelp();
144
- return 0;
145
- }
146
- return publishLocalApis([subcommand, ...rest].filter(Boolean));
147
- }
148
143
  if (command === "apis") {
149
144
  if (!subcommand || subcommand === "help" || subcommand === "--help" || subcommand === "-h" || hasHelpFlag(rest)) {
150
145
  printApisHelp();
@@ -438,7 +433,7 @@ async function describeApiOperation(args) {
438
433
  apiKey: options.apiKey,
439
434
  projectId: options.projectId,
440
435
  });
441
- const description = await withCliSpinner(`Fetching Tapi API description for ${operation}`, () => client.websiteApis.describe(operation));
436
+ const description = await withCliSpinner(`Fetching Tapi API description for ${operation}`, () => client.services.describe(operation));
442
437
  console.log(JSON.stringify(description, null, 2));
443
438
  return 0;
444
439
  }
@@ -1215,7 +1210,7 @@ function renderGeneratedApiClient(catalog) {
1215
1210
  }
1216
1211
  }
1217
1212
  const namespaceBlocks = [...namespaces.entries()].map(([namespace, operations]) => {
1218
- const lines = operations.map(({ key, operationName }) => ` ${operationName}: (inputs: Record<string, unknown> = {}, options: GeneratedRunOptions = {}) => client.websiteApis.run(${JSON.stringify(key)}, { ...options, inputs }),`);
1213
+ const lines = operations.map(({ key, operationName }) => ` ${operationName}: (inputs: Record<string, unknown> = {}, options: GeneratedRunOptions = {}) => client.services.run(${JSON.stringify(key)}, { ...options, inputs }),`);
1219
1214
  return ` ${namespace}: {\n${lines.join("\n")}\n },`;
1220
1215
  });
1221
1216
  return `/* Generated by Tapi. Do not edit by hand. */
@@ -1521,12 +1516,26 @@ async function installService(options) {
1521
1516
  manifest: serviceManifestEventSummary(manifest),
1522
1517
  });
1523
1518
  ensureCompatibleServiceManifest(manifest);
1519
+ const serviceStatus = await getServiceStatus().catch(() => ({
1520
+ installed: false,
1521
+ name: "tapi-service",
1522
+ status: "unknown",
1523
+ }));
1524
1524
  const serviceCacheDir = getDefaultServiceCacheDir();
1525
1525
  const downloadsDir = join(serviceCacheDir, "downloads");
1526
1526
  const releasesDir = join(serviceCacheDir, "releases");
1527
1527
  await mkdir(downloadsDir, { recursive: true });
1528
1528
  await mkdir(releasesDir, { recursive: true });
1529
1529
  const zipPath = join(downloadsDir, cachedServiceArtifactName(manifest));
1530
+ const releaseDir = join(releasesDir, safePathSegment(manifest.version));
1531
+ await pruneServiceInstallStorageBeforeDownload({
1532
+ downloadsDir,
1533
+ zipPath,
1534
+ releasesDir,
1535
+ releaseDir,
1536
+ manifestVersion: manifest.version,
1537
+ installedVersion: serviceStatus.installedVersion,
1538
+ });
1530
1539
  const verified = await hasVerifiedCachedInstaller(zipPath, manifest.sha256);
1531
1540
  if (verified) {
1532
1541
  console.log(`Using cached Tapi Service artifact: ${zipPath}`);
@@ -1552,7 +1561,8 @@ async function installService(options) {
1552
1561
  });
1553
1562
  return 0;
1554
1563
  }
1555
- const releaseDir = join(releasesDir, safePathSegment(manifest.version));
1564
+ await pruneManagedFiles(downloadsDir, [zipPath], isTapiServiceDownloadArtifact, MAX_NONCURRENT_MANAGED_DOWNLOADS, "old Tapi Service download");
1565
+ await pruneTapiReleaseDirs(releasesDir, [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Service release");
1556
1566
  await withCliSpinner(`Extracting Tapi Service ${manifest.version}`, () => extractZip(zipPath, releaseDir));
1557
1567
  await markManagedReleaseDir(releaseDir, manifest.version);
1558
1568
  const installer = join(releaseDir, manifest.installer || "install_tapi_service.ps1");
@@ -1597,7 +1607,7 @@ async function installService(options) {
1597
1607
  version: manifest.version,
1598
1608
  });
1599
1609
  await pruneManagedFiles(downloadsDir, [zipPath], isTapiServiceDownloadArtifact, MAX_NONCURRENT_MANAGED_DOWNLOADS, "old Tapi Service download");
1600
- await pruneManagedDirs(releasesDir, [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Service release");
1610
+ await pruneTapiReleaseDirs(releasesDir, [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Service release");
1601
1611
  console.log("Tapi Service is installed and running.");
1602
1612
  return 0;
1603
1613
  }
@@ -1608,147 +1618,6 @@ async function installService(options) {
1608
1618
  throw error;
1609
1619
  }
1610
1620
  }
1611
- async function publishLocalApis(args) {
1612
- try {
1613
- const options = parseApiWorkspaceOptions(args);
1614
- const workspace = loadWorkspace();
1615
- const root = workspace?.root || process.cwd();
1616
- const sitemaps = await readLocalSitemaps(join(root, ".tapi", "sitemaps"));
1617
- const apis = await readLocalGeneratedApis(join(root, ".tapi", "apis"));
1618
- if (apis.length === 0) {
1619
- throw new Error("No local generated APIs found under .tapi/apis.");
1620
- }
1621
- const { sitemapCount, contracts, requests, releaseVersion } = await withCliSpinner("Publishing local Tapi APIs", async () => {
1622
- let sitemapCount = 0;
1623
- for (const sitemap of sitemaps) {
1624
- const site = String(sitemap.site || "").trim();
1625
- if (!site || !isRecord(sitemap.siteMap)) {
1626
- continue;
1627
- }
1628
- await putJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/sitemaps/${encodeURIComponent(site)}?project=${encodeURIComponent(options.projectId)}`, { site_map: sitemap.siteMap }, options.apiKey, options.projectId);
1629
- sitemapCount += 1;
1630
- }
1631
- let contracts = 0;
1632
- let requests = 0;
1633
- for (const api of apis) {
1634
- const apiId = String(api.id || api.name || "").trim();
1635
- if (!apiId) {
1636
- continue;
1637
- }
1638
- const site = String(api.site || "").trim();
1639
- await putJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/generated-apis/contracts/${encodeURIComponent(apiId)}?project=${encodeURIComponent(options.projectId)}`, { generated_api: api }, options.apiKey, options.projectId);
1640
- contracts += 1;
1641
- const apiRequests = api.requests && typeof api.requests === "object" ? Object.values(api.requests) : [];
1642
- for (const request of apiRequests) {
1643
- if (!isRecord(request)) {
1644
- continue;
1645
- }
1646
- const requestId = String(request.id || request.key || "").trim();
1647
- const status = String(request.status || "").trim();
1648
- if (!requestId || !["ready", "published"].includes(status)) {
1649
- continue;
1650
- }
1651
- await postJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/generated-apis/contracts/${encodeURIComponent(apiId)}/requests/${encodeURIComponent(requestId)}/publish?project=${encodeURIComponent(options.projectId)}`, { site }, options.apiKey, options.projectId);
1652
- requests += 1;
1653
- }
1654
- }
1655
- const releaseResponse = await postJson(`${options.apiBaseUrl.replace(/\/+$/, "")}/api/v1/generated-apis/releases?project=${encodeURIComponent(options.projectId)}`, { environment: "production" }, options.apiKey, options.projectId);
1656
- const release = isRecord(releaseResponse) && isRecord(releaseResponse.release) ? releaseResponse.release : {};
1657
- const releaseVersion = typeof release.version === "string" ? release.version : "unknown";
1658
- return { sitemapCount, contracts, requests, releaseVersion };
1659
- });
1660
- console.log(`Published ${requests} API request(s) from ${contracts} contract(s), synced ${sitemapCount} sitemap(s), activated release ${releaseVersion}.`);
1661
- return 0;
1662
- }
1663
- catch (error) {
1664
- console.error(formatError(error));
1665
- return 1;
1666
- }
1667
- }
1668
- async function readLocalSitemaps(root) {
1669
- const sitemaps = [];
1670
- if (!existsSync(root)) {
1671
- return sitemaps;
1672
- }
1673
- const entries = await readdir(root, { withFileTypes: true });
1674
- for (const entry of entries) {
1675
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
1676
- continue;
1677
- }
1678
- const path = join(root, entry.name);
1679
- try {
1680
- const payload = JSON.parse(readFileSync(path, "utf8"));
1681
- if (!isRecord(payload) || !isRecord(payload.siteMap)) {
1682
- continue;
1683
- }
1684
- const site = String(payload.site || payload.siteMap.site || "").trim();
1685
- if (site) {
1686
- sitemaps.push({ site, siteMap: payload.siteMap });
1687
- }
1688
- }
1689
- catch {
1690
- continue;
1691
- }
1692
- }
1693
- return sitemaps;
1694
- }
1695
- async function readLocalGeneratedApis(root) {
1696
- const apis = [];
1697
- if (!existsSync(root)) {
1698
- return apis;
1699
- }
1700
- const entries = await readdir(root, { withFileTypes: true });
1701
- for (const entry of entries) {
1702
- const path = join(root, entry.name);
1703
- if (entry.isDirectory()) {
1704
- apis.push(...await readLocalGeneratedApis(path));
1705
- continue;
1706
- }
1707
- if (!entry.isFile() || !entry.name.endsWith(".json")) {
1708
- continue;
1709
- }
1710
- try {
1711
- const payload = JSON.parse(readFileSync(path, "utf8"));
1712
- if (isRecord(payload) && isRecord(payload.api)) {
1713
- apis.push(payload.api);
1714
- }
1715
- }
1716
- catch {
1717
- continue;
1718
- }
1719
- }
1720
- return apis;
1721
- }
1722
- async function putJson(url, body, apiKey, projectId) {
1723
- return requestJson("PUT", url, body, apiKey, projectId);
1724
- }
1725
- async function postJson(url, body, apiKey, projectId) {
1726
- return requestJson("POST", url, body, apiKey, projectId);
1727
- }
1728
- async function requestJson(method, url, body, apiKey, projectId) {
1729
- const response = await fetch(url, {
1730
- method,
1731
- headers: {
1732
- Authorization: `Bearer ${apiKey}`,
1733
- "Content-Type": "application/json",
1734
- "X-Tapi-Project": projectId,
1735
- },
1736
- body: JSON.stringify(body),
1737
- });
1738
- const text = await response.text();
1739
- if (!response.ok) {
1740
- throw new Error(`Tapi publish request failed: HTTP ${response.status} ${text}`);
1741
- }
1742
- if (!text.trim()) {
1743
- return {};
1744
- }
1745
- try {
1746
- return JSON.parse(text);
1747
- }
1748
- catch {
1749
- return {};
1750
- }
1751
- }
1752
1621
  export function getDefaultStudioCacheDir() {
1753
1622
  const homeOverride = envString("TAPI_STUDIO_HOME");
1754
1623
  if (homeOverride) {
@@ -1769,6 +1638,27 @@ export function getDefaultServiceCacheDir() {
1769
1638
  }
1770
1639
  return join(process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache"), "tapi", "service");
1771
1640
  }
1641
+ export function getDefaultMachineServiceReleasesDir() {
1642
+ if (process.platform !== "win32") {
1643
+ return undefined;
1644
+ }
1645
+ const programData = process.env.PROGRAMDATA?.trim() || "C:\\ProgramData";
1646
+ return join(programData, "Tapi", "Service", "releases");
1647
+ }
1648
+ async function pruneServiceInstallStorageBeforeDownload(options) {
1649
+ await pruneManagedFiles(options.downloadsDir, [options.zipPath], isTapiServiceDownloadArtifact, MAX_NONCURRENT_MANAGED_DOWNLOADS, "old Tapi Service download");
1650
+ await rm(options.releaseDir, { recursive: true, force: true }).catch(() => undefined);
1651
+ await pruneTapiReleaseDirs(options.releasesDir, [options.releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Service release");
1652
+ const machineReleasesDir = getDefaultMachineServiceReleasesDir();
1653
+ if (!machineReleasesDir) {
1654
+ return;
1655
+ }
1656
+ const keepPaths = [join(machineReleasesDir, safePathSegment(options.manifestVersion))];
1657
+ if (options.installedVersion) {
1658
+ keepPaths.push(join(machineReleasesDir, safePathSegment(options.installedVersion)));
1659
+ }
1660
+ await pruneTapiReleaseDirs(machineReleasesDir, keepPaths, isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old machine-scope Tapi Service release");
1661
+ }
1772
1662
  export function getDefaultServiceInstallLogDir() {
1773
1663
  return join(getDefaultServiceCacheDir(), "logs");
1774
1664
  }
@@ -2340,9 +2230,11 @@ async function installPortableStudioServer(options, manifest, event) {
2340
2230
  return artifactPath;
2341
2231
  }
2342
2232
  const releaseDir = portableStudioServerReleaseDir(manifest, options.installDir);
2233
+ await pruneManagedFiles(options.cacheDir, [artifactPath], isTapiStudioDownloadArtifact, MAX_NONCURRENT_MANAGED_DOWNLOADS, "old Tapi Studio download");
2234
+ await pruneTapiReleaseDirs(dirname(releaseDir), [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Studio server release");
2343
2235
  const exePath = await withCliSpinner(`Extracting Tapi Studio server ${manifest.version}`, () => extractPortableStudioServerZip(artifactPath, releaseDir, manifest));
2344
2236
  await pruneManagedFiles(options.cacheDir, [artifactPath], isTapiStudioDownloadArtifact, MAX_NONCURRENT_MANAGED_DOWNLOADS, "old Tapi Studio download");
2345
- await pruneManagedDirs(dirname(releaseDir), [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Studio server release");
2237
+ await pruneTapiReleaseDirs(dirname(releaseDir), [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Studio server release");
2346
2238
  console.log(`Installed Tapi Studio server ${manifest.version}: ${releaseDir}`);
2347
2239
  return exePath;
2348
2240
  }
@@ -2354,6 +2246,7 @@ async function launchPortableStudioServer(exePath, options, manifest) {
2354
2246
  env.TAPI_RUNTIME_NAMESPACE = "installed";
2355
2247
  env.TAPI_RUNTIME_CONTROL_PORT = env.TAPI_RUNTIME_CONTROL_PORT || "8765";
2356
2248
  env.TAPI_PROCESS_ROLE = "studio_server";
2249
+ env.TAPI_STUDIO_EXIT_ON_IDLE = "1";
2357
2250
  env.TAPI_STUDIO_OPEN_BROWSER = "0";
2358
2251
  env.TAPI_STUDIO_HOST = host;
2359
2252
  env.TAPI_STUDIO_PORT = String(port);
@@ -2458,12 +2351,28 @@ function delay(ms) {
2458
2351
  setTimeout(resolvePromise, Math.max(0, ms));
2459
2352
  });
2460
2353
  }
2461
- async function chooseStudioPort(preferred = 18766) {
2462
- for (let port = preferred; port < preferred + 25; port += 1) {
2463
- if (await isPortAvailable(port)) {
2354
+ export async function chooseStudioPort(preferred = 18766, options = {}) {
2355
+ const count = options.count ?? 25;
2356
+ const portAvailable = options.portAvailable ?? isPortAvailable;
2357
+ const reapBlockedPorts = options.reapBlockedPorts ?? reapPortableStudioServersBlockingPorts;
2358
+ const initiallyReaped = await reapBlockedPorts(preferred, count);
2359
+ if (initiallyReaped > 0) {
2360
+ await delay(250);
2361
+ }
2362
+ for (let port = preferred; port < preferred + count; port += 1) {
2363
+ if (await portAvailable(port)) {
2464
2364
  return port;
2465
2365
  }
2466
2366
  }
2367
+ const reaped = await reapBlockedPorts(preferred, count);
2368
+ if (reaped > 0) {
2369
+ await delay(250);
2370
+ for (let port = preferred; port < preferred + count; port += 1) {
2371
+ if (await portAvailable(port)) {
2372
+ return port;
2373
+ }
2374
+ }
2375
+ }
2467
2376
  throw new Error(`No available local Studio port found starting at ${preferred}.`);
2468
2377
  }
2469
2378
  async function isPortAvailable(port) {
@@ -2475,6 +2384,56 @@ async function isPortAvailable(port) {
2475
2384
  });
2476
2385
  });
2477
2386
  }
2387
+ async function reapPortableStudioServersBlockingPorts(preferred, count) {
2388
+ if (process.platform !== "win32") {
2389
+ return 0;
2390
+ }
2391
+ const start = Math.max(1, Math.min(65535, Math.trunc(preferred)));
2392
+ const end = Math.max(start, Math.min(65535, start + Math.max(1, Math.trunc(count)) - 1));
2393
+ const script = `
2394
+ $ErrorActionPreference = 'SilentlyContinue'
2395
+ $ports = ${start}..${end}
2396
+ $connections = @(Get-NetTCPConnection -State Listen | Where-Object { $ports -contains $_.LocalPort })
2397
+ $owners = @{}
2398
+ foreach ($conn in $connections) {
2399
+ $pidValue = [int]$conn.OwningProcess
2400
+ if ($pidValue -le 0 -or $owners.ContainsKey($pidValue)) { continue }
2401
+ $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$pidValue"
2402
+ if ($null -eq $proc) { continue }
2403
+ $path = [string]$proc.ExecutablePath
2404
+ $cmd = [string]$proc.CommandLine
2405
+ $isPortableStudio = (
2406
+ $path -match '\\\\Tapi\\\\Studio\\\\server\\\\releases\\\\' -or
2407
+ $cmd -match 'tapi-studio-server'
2408
+ )
2409
+ if ($isPortableStudio) {
2410
+ $owners[$pidValue] = $true
2411
+ }
2412
+ }
2413
+ $stopped = 0
2414
+ foreach ($pidValue in $owners.Keys) {
2415
+ try {
2416
+ Stop-Process -Id $pidValue -Force -ErrorAction Stop
2417
+ $stopped += 1
2418
+ } catch {}
2419
+ }
2420
+ [pscustomobject]@{ stopped = $stopped } | ConvertTo-Json -Compress
2421
+ `;
2422
+ try {
2423
+ const output = await runProcessCapture("powershell.exe", [
2424
+ "-NoProfile",
2425
+ "-ExecutionPolicy",
2426
+ "Bypass",
2427
+ "-Command",
2428
+ script,
2429
+ ]);
2430
+ const payload = JSON.parse(output.trim() || "{}");
2431
+ return typeof payload.stopped === "number" ? payload.stopped : 0;
2432
+ }
2433
+ catch {
2434
+ return 0;
2435
+ }
2436
+ }
2478
2437
  async function runDoctor(options) {
2479
2438
  console.log(`Tapi SDK: ${sdkVersion}`);
2480
2439
  console.log(`Node: ${process.version}`);
@@ -3026,6 +2985,7 @@ function cachedServiceArtifactName(manifest) {
3026
2985
  async function extractZip(zipPath, destination) {
3027
2986
  await rm(destination, { recursive: true, force: true });
3028
2987
  await mkdir(destination, { recursive: true });
2988
+ await ensureEnoughDiskSpaceForExtraction(zipPath, destination);
3029
2989
  try {
3030
2990
  await runProcessCapture("powershell.exe", [
3031
2991
  "-NoProfile",
@@ -3039,6 +2999,60 @@ async function extractZip(zipPath, destination) {
3039
2999
  throw new Error(formatZipExtractionFailure(zipPath, destination, error), { cause: error });
3040
3000
  }
3041
3001
  }
3002
+ async function ensureEnoughDiskSpaceForExtraction(zipPath, destination) {
3003
+ const zipInfo = await stat(zipPath).catch(() => undefined);
3004
+ if (!zipInfo?.isFile()) {
3005
+ return;
3006
+ }
3007
+ const requiredBytes = estimateRequiredExtractionBytes(zipInfo.size);
3008
+ const availableBytes = await availableBytesForPath(destination);
3009
+ if (availableBytes === undefined || availableBytes >= requiredBytes) {
3010
+ return;
3011
+ }
3012
+ throw new Error(formatInsufficientExtractionSpace(zipPath, destination, availableBytes, requiredBytes));
3013
+ }
3014
+ export function estimateRequiredExtractionBytes(zipBytes) {
3015
+ const scaled = Math.ceil(Math.max(0, zipBytes) * EXTRACTION_REQUIRED_MULTIPLIER);
3016
+ return Math.max(MIN_EXTRACTION_REQUIRED_BYTES, scaled);
3017
+ }
3018
+ async function availableBytesForPath(targetPath) {
3019
+ let probe = resolve(targetPath);
3020
+ for (;;) {
3021
+ if (existsSync(probe)) {
3022
+ try {
3023
+ const info = await statfs(probe);
3024
+ return Number(info.bavail) * Number(info.bsize);
3025
+ }
3026
+ catch {
3027
+ return undefined;
3028
+ }
3029
+ }
3030
+ const parent = dirname(probe);
3031
+ if (!parent || parent === probe) {
3032
+ return undefined;
3033
+ }
3034
+ probe = parent;
3035
+ }
3036
+ }
3037
+ export function formatInsufficientExtractionSpace(zipPath, destination, availableBytes, requiredBytes) {
3038
+ return [
3039
+ `Not enough free disk space to extract ${zipPath}.`,
3040
+ `Destination: ${destination}.`,
3041
+ `Available: ${formatBytes(availableBytes)}; estimated required before extraction: ${formatBytes(requiredBytes)}.`,
3042
+ "Tapi prunes old managed downloads and releases before extraction, but this drive is still too full.",
3043
+ "Free space under %LOCALAPPDATA%\\Tapi or move Studio downloads/releases with TAPI_STUDIO_HOME / --cache-dir / --install-dir.",
3044
+ ].join(" ");
3045
+ }
3046
+ function formatBytes(bytes) {
3047
+ if (!Number.isFinite(bytes) || bytes < 0) {
3048
+ return "unknown";
3049
+ }
3050
+ const gib = bytes / (1024 * 1024 * 1024);
3051
+ if (gib >= 1) {
3052
+ return `${gib.toFixed(2)} GB`;
3053
+ }
3054
+ return `${(bytes / (1024 * 1024)).toFixed(0)} MB`;
3055
+ }
3042
3056
  export function buildExpandArchiveCommand(zipPath, destination) {
3043
3057
  return `$ErrorActionPreference = 'Stop'; try { Expand-Archive -LiteralPath ${powerShellSingleQuoted(zipPath)} -DestinationPath ${powerShellSingleQuoted(destination)} -Force -ErrorAction Stop } catch { Write-Error $_; exit 1 }`;
3044
3058
  }
@@ -3090,7 +3104,7 @@ export async function pruneManagedFiles(directory, keepPaths, isManagedFileName,
3090
3104
  }
3091
3105
  return removed;
3092
3106
  }
3093
- async function pruneManagedDirs(directory, keepPaths, isManagedDirName, maxNoncurrentEntries = 0, label = "old Tapi release") {
3107
+ export async function pruneTapiReleaseDirs(directory, keepPaths, isManagedDirName, maxNoncurrentEntries = 0, label = "old Tapi release") {
3094
3108
  const keep = normalizedKeepPathSet(keepPaths);
3095
3109
  let entries;
3096
3110
  try {
@@ -3108,7 +3122,7 @@ async function pruneManagedDirs(directory, keepPaths, isManagedDirName, maxNoncu
3108
3122
  if (keep.has(normalizeFilesystemPath(candidate))) {
3109
3123
  continue;
3110
3124
  }
3111
- if (!existsSync(join(candidate, MANAGED_RELEASE_MARKER))) {
3125
+ if (!(await isTapiManagedReleaseDir(candidate))) {
3112
3126
  continue;
3113
3127
  }
3114
3128
  const info = await stat(candidate).catch(() => undefined);
@@ -3134,6 +3148,34 @@ async function pruneManagedDirs(directory, keepPaths, isManagedDirName, maxNoncu
3134
3148
  }
3135
3149
  return removed;
3136
3150
  }
3151
+ async function isTapiManagedReleaseDir(directory) {
3152
+ if (existsSync(join(directory, MANAGED_RELEASE_MARKER))) {
3153
+ return true;
3154
+ }
3155
+ if (looksLikeTapiServiceReleaseDir(directory) || looksLikeTapiStudioReleaseDir(directory)) {
3156
+ return true;
3157
+ }
3158
+ const entries = await readdir(directory).catch(() => undefined);
3159
+ return Array.isArray(entries) && entries.length === 0;
3160
+ }
3161
+ function looksLikeTapiServiceReleaseDir(directory) {
3162
+ return (existsSync(join(directory, "tapi-service-host.exe"))
3163
+ && existsSync(join(directory, "install_tapi_service.ps1"))
3164
+ && (existsSync(join(directory, "tapi-service-worker.exe"))
3165
+ || existsSync(join(directory, "tapi-service-worker", "tapi-service-worker.exe"))));
3166
+ }
3167
+ function looksLikeTapiStudioReleaseDir(directory) {
3168
+ return Boolean(findPortableStudioServerExe(directory, {
3169
+ version: "",
3170
+ channel: "",
3171
+ platform: SUPPORTED_STUDIO_PLATFORM,
3172
+ artifactName: "tapi-studio-server.zip",
3173
+ url: "",
3174
+ sha256: "",
3175
+ installerKind: "portable-server",
3176
+ serverExecutable: "tapi-studio-server.exe",
3177
+ }));
3178
+ }
3137
3179
  export function isTapiStudioDownloadArtifact(fileName) {
3138
3180
  const normalized = fileName.toLowerCase();
3139
3181
  return (normalized.endsWith(".zip")
@@ -3310,7 +3352,6 @@ Usage:
3310
3352
  tapi apis generate
3311
3353
  tapi triggers sync
3312
3354
  tapi sessions
3313
- tapi publish
3314
3355
  tapi service status
3315
3356
  tapi doctor
3316
3357
 
@@ -3326,7 +3367,6 @@ Commands:
3326
3367
  apis generate Generate a TypeScript runtime wrapper from the catalog
3327
3368
  triggers sync Upsert API-call triggers from tapi.config
3328
3369
  sessions List dev-mode API sessions and open takeover sessions
3329
- publish Upload local .tapi API drafts and publish ready requests
3330
3370
  service Inspect or control the local Tapi Windows service
3331
3371
  doctor Alias for studio doctor
3332
3372
  `);
@@ -3451,15 +3491,6 @@ Commands:
3451
3491
  repair Restart tapi-service using the current installed service
3452
3492
  `);
3453
3493
  }
3454
- function printPublishHelp() {
3455
- console.log(`Tapi publish
3456
-
3457
- Usage:
3458
- tapi publish [--api-base-url URL] [--api-key KEY] [--project PROJECT]
3459
-
3460
- Publishes ready generated API requests from local .tapi/apis files.
3461
- `);
3462
- }
3463
3494
  function formatError(error) {
3464
3495
  return error instanceof Error ? error.message : String(error);
3465
3496
  }
package/dist/index.d.ts CHANGED
@@ -4,9 +4,9 @@ import { RunnersResource } from "./runners.js";
4
4
  import { RunsResource } from "./runs.js";
5
5
  import { RuntimeResource } from "./runtime.js";
6
6
  import { SessionsResource } from "./sessions.js";
7
+ import { ServicesResource } from "./services.js";
7
8
  import { TriggersResource } from "./triggers.js";
8
9
  import type { TapiClientOptions } from "./types.js";
9
- import { WebsiteApisResource } from "./website-apis.js";
10
10
  export declare class TapiClient {
11
11
  readonly catalog: CatalogResource;
12
12
  readonly cloudRuns: CloudRunsResource;
@@ -14,8 +14,8 @@ export declare class TapiClient {
14
14
  readonly runs: RunsResource;
15
15
  readonly runtime: RuntimeResource;
16
16
  readonly sessions: SessionsResource;
17
+ readonly services: ServicesResource;
17
18
  readonly triggers: TriggersResource;
18
- readonly websiteApis: WebsiteApisResource;
19
19
  constructor(options: TapiClientOptions);
20
20
  }
21
21
  export * from "./errors.js";
package/dist/index.js CHANGED
@@ -5,8 +5,8 @@ import { RunnersResource } from "./runners.js";
5
5
  import { RunsResource } from "./runs.js";
6
6
  import { RuntimeResource } from "./runtime.js";
7
7
  import { SessionsResource } from "./sessions.js";
8
+ import { ServicesResource } from "./services.js";
8
9
  import { TriggersResource } from "./triggers.js";
9
- import { WebsiteApisResource } from "./website-apis.js";
10
10
  export class TapiClient {
11
11
  catalog;
12
12
  cloudRuns;
@@ -14,8 +14,8 @@ export class TapiClient {
14
14
  runs;
15
15
  runtime;
16
16
  sessions;
17
+ services;
17
18
  triggers;
18
- websiteApis;
19
19
  constructor(options) {
20
20
  const http = new HttpClient(options);
21
21
  this.catalog = new CatalogResource(http);
@@ -24,8 +24,8 @@ export class TapiClient {
24
24
  this.runs = new RunsResource(http);
25
25
  this.runtime = new RuntimeResource(http, options);
26
26
  this.sessions = new SessionsResource(http);
27
+ this.services = new ServicesResource(http, options);
27
28
  this.triggers = new TriggersResource(http);
28
- this.websiteApis = new WebsiteApisResource(http, options);
29
29
  }
30
30
  }
31
31
  export * from "./errors.js";
@@ -0,0 +1,17 @@
1
+ import type { HttpClient } from "./client.js";
2
+ import type { CloudBatchRun, CloudRunQuote, ServiceCloudBatchRequest, ServiceCloudQuoteRequest, ServiceOperation, ServiceRunRequest, TapiRun } from "./types.js";
3
+ import type { TapiClientOptions } from "./types.js";
4
+ export declare class ServicesResource {
5
+ private readonly http;
6
+ private readonly dev;
7
+ constructor(http: HttpClient, options: TapiClientOptions);
8
+ run(serviceRequest: string, request?: ServiceRunRequest): Promise<TapiRun>;
9
+ quoteCloud(serviceRequest: string, request?: ServiceCloudQuoteRequest): Promise<CloudRunQuote>;
10
+ runCloud(serviceRequest: string, request?: ServiceRunRequest & {
11
+ cloud?: ServiceCloudBatchRequest["cloud"];
12
+ user?: ServiceCloudBatchRequest["user"];
13
+ payment?: ServiceCloudBatchRequest["payment"];
14
+ }): Promise<CloudBatchRun>;
15
+ runCloudBatch(serviceRequest: string, request: ServiceCloudBatchRequest): Promise<CloudBatchRun>;
16
+ describe(serviceRequest: string): Promise<ServiceOperation>;
17
+ }
@@ -1,32 +1,32 @@
1
1
  import { TapiError } from "./errors.js";
2
2
  import { attachLateInputs, serializeInputs } from "./late-input.js";
3
- export class WebsiteApisResource {
3
+ export class ServicesResource {
4
4
  http;
5
5
  dev;
6
6
  constructor(http, options) {
7
7
  this.http = http;
8
8
  this.dev = options.dev === true;
9
9
  }
10
- async run(apiRequest, request = {}) {
11
- const { apiName, requestKey } = splitApiRequest(apiRequest);
10
+ async run(serviceRequest, request = {}) {
11
+ const { serviceName, requestKey } = splitServiceRequest(serviceRequest);
12
12
  const { bodyInputs, lateInputs } = serializeInputs(request.inputs);
13
13
  const body = {
14
14
  ...request,
15
15
  ...(bodyInputs === undefined ? {} : { inputs: bodyInputs }),
16
16
  ...(this.dev ? { dev: true } : {}),
17
17
  };
18
- const run = await this.http.post(`/api/sdk/v1/website-apis/${encodeURIComponent(apiName)}/requests/${encodeURIComponent(requestKey)}/runs`, body);
18
+ const run = await this.http.post(`/api/sdk/v1/services/${encodeURIComponent(serviceName)}/requests/${encodeURIComponent(requestKey)}/runs`, body);
19
19
  if (run.id && lateInputs.length > 0) {
20
20
  attachLateInputs(lateInputs, run.id, (runId, inputKey, value) => this.http.post(`/api/sdk/v1/runs/${encodeURIComponent(runId)}/inputs/${encodeURIComponent(inputKey)}`, { value }));
21
21
  }
22
22
  return run;
23
23
  }
24
- quoteCloud(apiRequest, request = {}) {
25
- const { apiName, requestKey } = splitApiRequest(apiRequest);
26
- return this.http.post(`/api/sdk/v1/website-apis/${encodeURIComponent(apiName)}/requests/${encodeURIComponent(requestKey)}/cloud/quote`, request);
24
+ quoteCloud(serviceRequest, request = {}) {
25
+ const { serviceName, requestKey } = splitServiceRequest(serviceRequest);
26
+ return this.http.post(`/api/sdk/v1/services/${encodeURIComponent(serviceName)}/requests/${encodeURIComponent(requestKey)}/cloud/quote`, request);
27
27
  }
28
- runCloud(apiRequest, request = {}) {
29
- return this.runCloudBatch(apiRequest, {
28
+ runCloud(serviceRequest, request = {}) {
29
+ return this.runCloudBatch(serviceRequest, {
30
30
  inputs: [request.inputs ?? {}],
31
31
  cloud: request.cloud,
32
32
  user: request.user,
@@ -35,10 +35,10 @@ export class WebsiteApisResource {
35
35
  idempotencyKey: request.idempotencyKey,
36
36
  });
37
37
  }
38
- async runCloudBatch(apiRequest, request) {
39
- const { apiName, requestKey } = splitApiRequest(apiRequest);
38
+ async runCloudBatch(serviceRequest, request) {
39
+ const { serviceName, requestKey } = splitServiceRequest(serviceRequest);
40
40
  try {
41
- return await this.http.post(`/api/sdk/v1/website-apis/${encodeURIComponent(apiName)}/requests/${encodeURIComponent(requestKey)}/cloud/batch`, request);
41
+ return await this.http.post(`/api/sdk/v1/services/${encodeURIComponent(serviceName)}/requests/${encodeURIComponent(requestKey)}/cloud/batch`, request);
42
42
  }
43
43
  catch (error) {
44
44
  if (error instanceof TapiError && error.status === 402 && isRecord(error.details)) {
@@ -47,19 +47,19 @@ export class WebsiteApisResource {
47
47
  throw error;
48
48
  }
49
49
  }
50
- describe(apiRequest) {
51
- const { apiName, requestKey } = splitApiRequest(apiRequest);
52
- return this.http.get(`/api/sdk/v1/website-apis/${encodeURIComponent(apiName)}/requests/${encodeURIComponent(requestKey)}`);
50
+ describe(serviceRequest) {
51
+ const { serviceName, requestKey } = splitServiceRequest(serviceRequest);
52
+ return this.http.get(`/api/sdk/v1/services/${encodeURIComponent(serviceName)}/requests/${encodeURIComponent(requestKey)}`);
53
53
  }
54
54
  }
55
- function splitApiRequest(value) {
55
+ function splitServiceRequest(value) {
56
56
  const text = value.trim();
57
57
  const index = text.lastIndexOf(".");
58
58
  if (index <= 0 || index === text.length - 1) {
59
- throw new Error("api request must be formatted as '<apiName>.<requestKey>'");
59
+ throw new Error("service request must be formatted as '<serviceName>.<requestKey>'");
60
60
  }
61
61
  return {
62
- apiName: text.slice(0, index),
62
+ serviceName: text.slice(0, index),
63
63
  requestKey: text.slice(index + 1),
64
64
  };
65
65
  }
package/dist/triggers.js CHANGED
@@ -4,16 +4,16 @@ export class TriggersResource {
4
4
  this.http = http;
5
5
  }
6
6
  list() {
7
- return this.http.get("/api/sdk/v1/website-api-triggers");
7
+ return this.http.get("/api/sdk/v1/service-triggers");
8
8
  }
9
9
  create(request) {
10
- return this.http.post("/api/sdk/v1/website-api-triggers", request);
10
+ return this.http.post("/api/sdk/v1/service-triggers", request);
11
11
  }
12
12
  get(triggerId) {
13
- return this.http.get(`/api/sdk/v1/website-api-triggers/${encodeURIComponent(triggerId)}`);
13
+ return this.http.get(`/api/sdk/v1/service-triggers/${encodeURIComponent(triggerId)}`);
14
14
  }
15
15
  update(triggerId, request) {
16
- return this.http.patch(`/api/sdk/v1/website-api-triggers/${encodeURIComponent(triggerId)}`, request);
16
+ return this.http.patch(`/api/sdk/v1/service-triggers/${encodeURIComponent(triggerId)}`, request);
17
17
  }
18
18
  enable(triggerId) {
19
19
  return this.update(triggerId, { enabled: true });
@@ -22,9 +22,9 @@ export class TriggersResource {
22
22
  return this.update(triggerId, { enabled: false });
23
23
  }
24
24
  delete(triggerId) {
25
- return this.http.delete(`/api/sdk/v1/website-api-triggers/${encodeURIComponent(triggerId)}`);
25
+ return this.http.delete(`/api/sdk/v1/service-triggers/${encodeURIComponent(triggerId)}`);
26
26
  }
27
27
  fire(triggerId) {
28
- return this.http.post(`/api/sdk/v1/website-api-triggers/${encodeURIComponent(triggerId)}/run`);
28
+ return this.http.post(`/api/sdk/v1/service-triggers/${encodeURIComponent(triggerId)}/run`);
29
29
  }
30
30
  }
package/dist/types.d.ts CHANGED
@@ -16,6 +16,7 @@ export interface WebsiteApiRunRequest {
16
16
  runnerId?: string;
17
17
  idempotencyKey?: string;
18
18
  site?: string;
19
+ sitemapId?: string;
19
20
  }
20
21
  export interface CloudRunOptions {
21
22
  windows?: boolean;
@@ -46,6 +47,9 @@ export interface WebsiteApiCloudBatchRequest {
46
47
  site?: string;
47
48
  idempotencyKey?: string;
48
49
  }
50
+ export type ServiceRunRequest = WebsiteApiRunRequest;
51
+ export type ServiceCloudQuoteRequest = WebsiteApiCloudQuoteRequest;
52
+ export type ServiceCloudBatchRequest = WebsiteApiCloudBatchRequest;
49
53
  export interface CloudRunQuote {
50
54
  inputCount: number;
51
55
  windows: boolean;
@@ -155,10 +159,11 @@ export interface WebsiteApiOperation {
155
159
  namespace: string;
156
160
  operation: string;
157
161
  site?: string;
162
+ sitemapId?: string;
158
163
  version?: string;
159
164
  status?: string;
160
165
  publishedAt?: string | null;
161
- variant?: "desktop" | "mobile" | string;
166
+ deviceMode?: "desktop" | "mobile" | string;
162
167
  runtime?: RuntimeRunOptions | Record<string, unknown>;
163
168
  inputs: WebsiteApiInputContract[];
164
169
  outputs?: WebsiteApiOutputContract[];
@@ -181,6 +186,7 @@ export interface WebsiteApiTriggerCreateRequest {
181
186
  runnerId?: string;
182
187
  priority?: number;
183
188
  site?: string;
189
+ sitemapId?: string;
184
190
  }
185
191
  export interface WebsiteApiTriggerUpdateRequest {
186
192
  enabled: boolean;
@@ -228,6 +234,9 @@ export interface TapiRun {
228
234
  updatedAt?: string;
229
235
  [key: string]: unknown;
230
236
  }
237
+ export type ServiceInputContract = WebsiteApiInputContract;
238
+ export type ServiceOutputContract = WebsiteApiOutputContract;
239
+ export type ServiceOperation = WebsiteApiOperation;
231
240
  export type TapiDevSessionStatus = "queued" | "running" | "waiting_for_input" | "awaiting_takeover" | "needs_developer_attention" | "completed" | "failed" | "cancelled" | string;
232
241
  export interface TapiDevSession {
233
242
  id: string;
@@ -367,7 +376,8 @@ export interface SdkCatalogRequest {
367
376
  status: string;
368
377
  operation?: string;
369
378
  sdkName?: string;
370
- variant?: "desktop" | "mobile" | string;
379
+ sitemapId?: string;
380
+ deviceMode?: "desktop" | "mobile" | string;
371
381
  runtime?: RuntimeRunOptions | Record<string, unknown>;
372
382
  inputs?: WebsiteApiInputContract[];
373
383
  inputSchema?: Record<string, unknown>;
@@ -379,6 +389,7 @@ export interface SdkCatalogApi {
379
389
  name: string;
380
390
  version: string;
381
391
  site?: string;
392
+ sitemapId?: string;
382
393
  requests: SdkCatalogRequest[];
383
394
  [key: string]: unknown;
384
395
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tapi-dev/sdk",
3
- "version": "0.1.21",
3
+ "version": "0.1.24",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -1,17 +0,0 @@
1
- import type { HttpClient } from "./client.js";
2
- import type { CloudBatchRun, CloudRunQuote, TapiRun, WebsiteApiCloudBatchRequest, WebsiteApiCloudQuoteRequest, WebsiteApiOperation, WebsiteApiRunRequest } from "./types.js";
3
- import type { TapiClientOptions } from "./types.js";
4
- export declare class WebsiteApisResource {
5
- private readonly http;
6
- private readonly dev;
7
- constructor(http: HttpClient, options: TapiClientOptions);
8
- run(apiRequest: string, request?: WebsiteApiRunRequest): Promise<TapiRun>;
9
- quoteCloud(apiRequest: string, request?: WebsiteApiCloudQuoteRequest): Promise<CloudRunQuote>;
10
- runCloud(apiRequest: string, request?: WebsiteApiRunRequest & {
11
- cloud?: WebsiteApiCloudBatchRequest["cloud"];
12
- user?: WebsiteApiCloudBatchRequest["user"];
13
- payment?: WebsiteApiCloudBatchRequest["payment"];
14
- }): Promise<CloudBatchRun>;
15
- runCloudBatch(apiRequest: string, request: WebsiteApiCloudBatchRequest): Promise<CloudBatchRun>;
16
- describe(apiRequest: string): Promise<WebsiteApiOperation>;
17
- }