@tapi-dev/sdk 0.1.21 → 0.1.25

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;
@@ -111,13 +114,26 @@ export declare function validateStudioManifest(input: unknown): StudioReleaseMan
111
114
  export declare function validateServiceReleaseManifest(input: unknown): ServiceReleaseManifest;
112
115
  export declare function compareVersions(left: string, right: string): number;
113
116
  export declare function memoizeStudioInstallToken(options: StudioCliOptions, installToken?: string): string;
117
+ export declare function tryOpenExistingPortableStudioServer(options: StudioCliOptions, manifest: StudioReleaseManifest, fetchImpl?: FetchLike, openBrowserImpl?: (url: string) => void): Promise<{
118
+ opened: boolean;
119
+ baseUrl?: string;
120
+ reason?: string;
121
+ }>;
114
122
  export declare function findPortablePlaywrightBrowsersDir(exePath: string): string | undefined;
115
123
  export declare function waitForHttpOk(url: string, timeoutMs?: number, intervalMs?: number, fetchImpl?: FetchLike): Promise<void>;
124
+ export declare function chooseStudioPort(preferred?: number, options?: {
125
+ count?: number;
126
+ portAvailable?: StudioPortAvailable;
127
+ reapBlockedPorts?: StudioPortReaper;
128
+ }): Promise<number>;
116
129
  export declare function studioInstanceRecordPathForOptions(options: StudioCliOptions): string;
117
130
  export declare function findPortableStudioServerExe(releaseDir: string, manifest: StudioReleaseManifest): string | undefined;
131
+ export declare function estimateRequiredExtractionBytes(zipBytes: number): number;
132
+ export declare function formatInsufficientExtractionSpace(zipPath: string, destination: string, availableBytes: number, requiredBytes: number): string;
118
133
  export declare function buildExpandArchiveCommand(zipPath: string, destination: string): string;
119
134
  export declare function formatZipExtractionFailure(zipPath: string, destination: string, error: unknown): string;
120
135
  export declare function pruneManagedFiles(directory: string, keepPaths: string[], isManagedFileName: (name: string) => boolean, maxNoncurrentEntries?: number, label?: string): Promise<number>;
136
+ export declare function pruneTapiReleaseDirs(directory: string, keepPaths: string[], isManagedDirName: (name: string) => boolean, maxNoncurrentEntries?: number, label?: string): Promise<number>;
121
137
  export declare function isTapiStudioDownloadArtifact(fileName: string): boolean;
122
138
  export declare function isTapiServiceDownloadArtifact(fileName: string): boolean;
123
139
  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
  }
@@ -2236,6 +2126,10 @@ async function openStudio(options) {
2236
2126
  if (!options.exePath) {
2237
2127
  const portable = await ensurePortableStudioServer(options);
2238
2128
  if (portable) {
2129
+ const existing = await tryOpenExistingPortableStudioServer(options, portable.manifest);
2130
+ if (existing.opened) {
2131
+ return 0;
2132
+ }
2239
2133
  await launchPortableStudioServer(portable.exePath, options, portable.manifest);
2240
2134
  return 0;
2241
2135
  }
@@ -2270,6 +2164,34 @@ function findStudioExecutable(options) {
2270
2164
  }
2271
2165
  return getStudioExecutableCandidates().find((candidate) => existsSync(candidate));
2272
2166
  }
2167
+ export async function tryOpenExistingPortableStudioServer(options, manifest, fetchImpl = fetch, openBrowserImpl = openBrowser) {
2168
+ const record = await readStudioInstanceRecord(options);
2169
+ if (!record) {
2170
+ return { opened: false, reason: "instance_record_missing" };
2171
+ }
2172
+ if ((record.manifestVersion || "") !== manifest.version) {
2173
+ return { opened: false, reason: "manifest_version_mismatch" };
2174
+ }
2175
+ try {
2176
+ const identityResponse = await fetchWithTimeout(`${record.baseUrl.replace(/\/+$/, "")}/api/studio/control/identity`, { headers: { Accept: "application/json" } }, 1_000, fetchImpl);
2177
+ if (!identityResponse.ok) {
2178
+ await unlinkIfExists(studioInstanceRecordPathForOptions(options));
2179
+ return { opened: false, reason: `identity_http_${identityResponse.status}` };
2180
+ }
2181
+ const identity = await readJsonBody(identityResponse);
2182
+ if (!studioInstanceIdentityMatches(identity, options)) {
2183
+ return { opened: false, reason: "identity_mismatch" };
2184
+ }
2185
+ const launchUrl = studioLaunchUrl(record.baseUrl, options);
2186
+ openBrowserImpl(launchUrl);
2187
+ console.log(`Opened existing Tapi Studio ${record.manifestVersion || manifest.version}: ${launchUrl}`);
2188
+ return { opened: true, baseUrl: record.baseUrl };
2189
+ }
2190
+ catch (error) {
2191
+ await unlinkIfExists(studioInstanceRecordPathForOptions(options));
2192
+ return { opened: false, reason: formatError(error) };
2193
+ }
2194
+ }
2273
2195
  async function ensurePortableStudioServer(options) {
2274
2196
  const event = await createCliWideEvent("tapi_cli.studio_server_ensure", {
2275
2197
  sdk_version: sdkVersion,
@@ -2340,9 +2262,11 @@ async function installPortableStudioServer(options, manifest, event) {
2340
2262
  return artifactPath;
2341
2263
  }
2342
2264
  const releaseDir = portableStudioServerReleaseDir(manifest, options.installDir);
2265
+ await pruneManagedFiles(options.cacheDir, [artifactPath], isTapiStudioDownloadArtifact, MAX_NONCURRENT_MANAGED_DOWNLOADS, "old Tapi Studio download");
2266
+ await pruneTapiReleaseDirs(dirname(releaseDir), [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Studio server release");
2343
2267
  const exePath = await withCliSpinner(`Extracting Tapi Studio server ${manifest.version}`, () => extractPortableStudioServerZip(artifactPath, releaseDir, manifest));
2344
2268
  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");
2269
+ await pruneTapiReleaseDirs(dirname(releaseDir), [releaseDir], isSafeManagedReleaseDirName, MAX_NONCURRENT_MANAGED_RELEASES, "old Tapi Studio server release");
2346
2270
  console.log(`Installed Tapi Studio server ${manifest.version}: ${releaseDir}`);
2347
2271
  return exePath;
2348
2272
  }
@@ -2354,6 +2278,7 @@ async function launchPortableStudioServer(exePath, options, manifest) {
2354
2278
  env.TAPI_RUNTIME_NAMESPACE = "installed";
2355
2279
  env.TAPI_RUNTIME_CONTROL_PORT = env.TAPI_RUNTIME_CONTROL_PORT || "8765";
2356
2280
  env.TAPI_PROCESS_ROLE = "studio_server";
2281
+ env.TAPI_STUDIO_EXIT_ON_IDLE = "1";
2357
2282
  env.TAPI_STUDIO_OPEN_BROWSER = "0";
2358
2283
  env.TAPI_STUDIO_HOST = host;
2359
2284
  env.TAPI_STUDIO_PORT = String(port);
@@ -2366,8 +2291,7 @@ async function launchPortableStudioServer(exePath, options, manifest) {
2366
2291
  env.PLAYWRIGHT_BROWSERS_PATH = playwrightBrowsersPath;
2367
2292
  }
2368
2293
  const url = `http://${host}:${port}`;
2369
- const launchQuery = options.launchUrlQuery ? options.launchUrlQuery.replace(/^\?+/, "") : "";
2370
- const launchUrl = launchQuery ? `${url}/?${launchQuery}` : url;
2294
+ const launchUrl = studioLaunchUrl(url, options);
2371
2295
  let childPid = 0;
2372
2296
  await withCliSpinner("Starting Tapi Studio", async () => {
2373
2297
  const child = spawn(exePath, [], {
@@ -2396,6 +2320,11 @@ async function launchPortableStudioServer(exePath, options, manifest) {
2396
2320
  openBrowser(launchUrl);
2397
2321
  console.log(`Opened Tapi Studio ${manifest.version}: ${launchUrl}`);
2398
2322
  }
2323
+ function studioLaunchUrl(baseUrl, options) {
2324
+ const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
2325
+ const launchQuery = options.launchUrlQuery ? options.launchUrlQuery.replace(/^\?+/, "") : "";
2326
+ return launchQuery ? `${normalizedBaseUrl}/?${launchQuery}` : normalizedBaseUrl;
2327
+ }
2399
2328
  export function findPortablePlaywrightBrowsersDir(exePath) {
2400
2329
  const exeDir = dirname(exePath);
2401
2330
  const releaseDir = basename(exeDir).toLowerCase() === "tapi-studio-server"
@@ -2458,12 +2387,28 @@ function delay(ms) {
2458
2387
  setTimeout(resolvePromise, Math.max(0, ms));
2459
2388
  });
2460
2389
  }
2461
- async function chooseStudioPort(preferred = 18766) {
2462
- for (let port = preferred; port < preferred + 25; port += 1) {
2463
- if (await isPortAvailable(port)) {
2390
+ export async function chooseStudioPort(preferred = 18766, options = {}) {
2391
+ const count = options.count ?? 25;
2392
+ const portAvailable = options.portAvailable ?? isPortAvailable;
2393
+ const reapBlockedPorts = options.reapBlockedPorts ?? reapPortableStudioServersBlockingPorts;
2394
+ const initiallyReaped = await reapBlockedPorts(preferred, count);
2395
+ if (initiallyReaped > 0) {
2396
+ await delay(250);
2397
+ }
2398
+ for (let port = preferred; port < preferred + count; port += 1) {
2399
+ if (await portAvailable(port)) {
2464
2400
  return port;
2465
2401
  }
2466
2402
  }
2403
+ const reaped = await reapBlockedPorts(preferred, count);
2404
+ if (reaped > 0) {
2405
+ await delay(250);
2406
+ for (let port = preferred; port < preferred + count; port += 1) {
2407
+ if (await portAvailable(port)) {
2408
+ return port;
2409
+ }
2410
+ }
2411
+ }
2467
2412
  throw new Error(`No available local Studio port found starting at ${preferred}.`);
2468
2413
  }
2469
2414
  async function isPortAvailable(port) {
@@ -2475,6 +2420,56 @@ async function isPortAvailable(port) {
2475
2420
  });
2476
2421
  });
2477
2422
  }
2423
+ async function reapPortableStudioServersBlockingPorts(preferred, count) {
2424
+ if (process.platform !== "win32") {
2425
+ return 0;
2426
+ }
2427
+ const start = Math.max(1, Math.min(65535, Math.trunc(preferred)));
2428
+ const end = Math.max(start, Math.min(65535, start + Math.max(1, Math.trunc(count)) - 1));
2429
+ const script = `
2430
+ $ErrorActionPreference = 'SilentlyContinue'
2431
+ $ports = ${start}..${end}
2432
+ $connections = @(Get-NetTCPConnection -State Listen | Where-Object { $ports -contains $_.LocalPort })
2433
+ $owners = @{}
2434
+ foreach ($conn in $connections) {
2435
+ $pidValue = [int]$conn.OwningProcess
2436
+ if ($pidValue -le 0 -or $owners.ContainsKey($pidValue)) { continue }
2437
+ $proc = Get-CimInstance Win32_Process -Filter "ProcessId=$pidValue"
2438
+ if ($null -eq $proc) { continue }
2439
+ $path = [string]$proc.ExecutablePath
2440
+ $cmd = [string]$proc.CommandLine
2441
+ $isPortableStudio = (
2442
+ $path -match '\\\\Tapi\\\\Studio\\\\server\\\\releases\\\\' -or
2443
+ $cmd -match 'tapi-studio-server'
2444
+ )
2445
+ if ($isPortableStudio) {
2446
+ $owners[$pidValue] = $true
2447
+ }
2448
+ }
2449
+ $stopped = 0
2450
+ foreach ($pidValue in $owners.Keys) {
2451
+ try {
2452
+ Stop-Process -Id $pidValue -Force -ErrorAction Stop
2453
+ $stopped += 1
2454
+ } catch {}
2455
+ }
2456
+ [pscustomobject]@{ stopped = $stopped } | ConvertTo-Json -Compress
2457
+ `;
2458
+ try {
2459
+ const output = await runProcessCapture("powershell.exe", [
2460
+ "-NoProfile",
2461
+ "-ExecutionPolicy",
2462
+ "Bypass",
2463
+ "-Command",
2464
+ script,
2465
+ ]);
2466
+ const payload = JSON.parse(output.trim() || "{}");
2467
+ return typeof payload.stopped === "number" ? payload.stopped : 0;
2468
+ }
2469
+ catch {
2470
+ return 0;
2471
+ }
2472
+ }
2478
2473
  async function runDoctor(options) {
2479
2474
  console.log(`Tapi SDK: ${sdkVersion}`);
2480
2475
  console.log(`Node: ${process.version}`);
@@ -3026,6 +3021,7 @@ function cachedServiceArtifactName(manifest) {
3026
3021
  async function extractZip(zipPath, destination) {
3027
3022
  await rm(destination, { recursive: true, force: true });
3028
3023
  await mkdir(destination, { recursive: true });
3024
+ await ensureEnoughDiskSpaceForExtraction(zipPath, destination);
3029
3025
  try {
3030
3026
  await runProcessCapture("powershell.exe", [
3031
3027
  "-NoProfile",
@@ -3039,6 +3035,60 @@ async function extractZip(zipPath, destination) {
3039
3035
  throw new Error(formatZipExtractionFailure(zipPath, destination, error), { cause: error });
3040
3036
  }
3041
3037
  }
3038
+ async function ensureEnoughDiskSpaceForExtraction(zipPath, destination) {
3039
+ const zipInfo = await stat(zipPath).catch(() => undefined);
3040
+ if (!zipInfo?.isFile()) {
3041
+ return;
3042
+ }
3043
+ const requiredBytes = estimateRequiredExtractionBytes(zipInfo.size);
3044
+ const availableBytes = await availableBytesForPath(destination);
3045
+ if (availableBytes === undefined || availableBytes >= requiredBytes) {
3046
+ return;
3047
+ }
3048
+ throw new Error(formatInsufficientExtractionSpace(zipPath, destination, availableBytes, requiredBytes));
3049
+ }
3050
+ export function estimateRequiredExtractionBytes(zipBytes) {
3051
+ const scaled = Math.ceil(Math.max(0, zipBytes) * EXTRACTION_REQUIRED_MULTIPLIER);
3052
+ return Math.max(MIN_EXTRACTION_REQUIRED_BYTES, scaled);
3053
+ }
3054
+ async function availableBytesForPath(targetPath) {
3055
+ let probe = resolve(targetPath);
3056
+ for (;;) {
3057
+ if (existsSync(probe)) {
3058
+ try {
3059
+ const info = await statfs(probe);
3060
+ return Number(info.bavail) * Number(info.bsize);
3061
+ }
3062
+ catch {
3063
+ return undefined;
3064
+ }
3065
+ }
3066
+ const parent = dirname(probe);
3067
+ if (!parent || parent === probe) {
3068
+ return undefined;
3069
+ }
3070
+ probe = parent;
3071
+ }
3072
+ }
3073
+ export function formatInsufficientExtractionSpace(zipPath, destination, availableBytes, requiredBytes) {
3074
+ return [
3075
+ `Not enough free disk space to extract ${zipPath}.`,
3076
+ `Destination: ${destination}.`,
3077
+ `Available: ${formatBytes(availableBytes)}; estimated required before extraction: ${formatBytes(requiredBytes)}.`,
3078
+ "Tapi prunes old managed downloads and releases before extraction, but this drive is still too full.",
3079
+ "Free space under %LOCALAPPDATA%\\Tapi or move Studio downloads/releases with TAPI_STUDIO_HOME / --cache-dir / --install-dir.",
3080
+ ].join(" ");
3081
+ }
3082
+ function formatBytes(bytes) {
3083
+ if (!Number.isFinite(bytes) || bytes < 0) {
3084
+ return "unknown";
3085
+ }
3086
+ const gib = bytes / (1024 * 1024 * 1024);
3087
+ if (gib >= 1) {
3088
+ return `${gib.toFixed(2)} GB`;
3089
+ }
3090
+ return `${(bytes / (1024 * 1024)).toFixed(0)} MB`;
3091
+ }
3042
3092
  export function buildExpandArchiveCommand(zipPath, destination) {
3043
3093
  return `$ErrorActionPreference = 'Stop'; try { Expand-Archive -LiteralPath ${powerShellSingleQuoted(zipPath)} -DestinationPath ${powerShellSingleQuoted(destination)} -Force -ErrorAction Stop } catch { Write-Error $_; exit 1 }`;
3044
3094
  }
@@ -3090,7 +3140,7 @@ export async function pruneManagedFiles(directory, keepPaths, isManagedFileName,
3090
3140
  }
3091
3141
  return removed;
3092
3142
  }
3093
- async function pruneManagedDirs(directory, keepPaths, isManagedDirName, maxNoncurrentEntries = 0, label = "old Tapi release") {
3143
+ export async function pruneTapiReleaseDirs(directory, keepPaths, isManagedDirName, maxNoncurrentEntries = 0, label = "old Tapi release") {
3094
3144
  const keep = normalizedKeepPathSet(keepPaths);
3095
3145
  let entries;
3096
3146
  try {
@@ -3108,7 +3158,7 @@ async function pruneManagedDirs(directory, keepPaths, isManagedDirName, maxNoncu
3108
3158
  if (keep.has(normalizeFilesystemPath(candidate))) {
3109
3159
  continue;
3110
3160
  }
3111
- if (!existsSync(join(candidate, MANAGED_RELEASE_MARKER))) {
3161
+ if (!(await isTapiManagedReleaseDir(candidate))) {
3112
3162
  continue;
3113
3163
  }
3114
3164
  const info = await stat(candidate).catch(() => undefined);
@@ -3134,6 +3184,34 @@ async function pruneManagedDirs(directory, keepPaths, isManagedDirName, maxNoncu
3134
3184
  }
3135
3185
  return removed;
3136
3186
  }
3187
+ async function isTapiManagedReleaseDir(directory) {
3188
+ if (existsSync(join(directory, MANAGED_RELEASE_MARKER))) {
3189
+ return true;
3190
+ }
3191
+ if (looksLikeTapiServiceReleaseDir(directory) || looksLikeTapiStudioReleaseDir(directory)) {
3192
+ return true;
3193
+ }
3194
+ const entries = await readdir(directory).catch(() => undefined);
3195
+ return Array.isArray(entries) && entries.length === 0;
3196
+ }
3197
+ function looksLikeTapiServiceReleaseDir(directory) {
3198
+ return (existsSync(join(directory, "tapi-service-host.exe"))
3199
+ && existsSync(join(directory, "install_tapi_service.ps1"))
3200
+ && (existsSync(join(directory, "tapi-service-worker.exe"))
3201
+ || existsSync(join(directory, "tapi-service-worker", "tapi-service-worker.exe"))));
3202
+ }
3203
+ function looksLikeTapiStudioReleaseDir(directory) {
3204
+ return Boolean(findPortableStudioServerExe(directory, {
3205
+ version: "",
3206
+ channel: "",
3207
+ platform: SUPPORTED_STUDIO_PLATFORM,
3208
+ artifactName: "tapi-studio-server.zip",
3209
+ url: "",
3210
+ sha256: "",
3211
+ installerKind: "portable-server",
3212
+ serverExecutable: "tapi-studio-server.exe",
3213
+ }));
3214
+ }
3137
3215
  export function isTapiStudioDownloadArtifact(fileName) {
3138
3216
  const normalized = fileName.toLowerCase();
3139
3217
  return (normalized.endsWith(".zip")
@@ -3310,7 +3388,6 @@ Usage:
3310
3388
  tapi apis generate
3311
3389
  tapi triggers sync
3312
3390
  tapi sessions
3313
- tapi publish
3314
3391
  tapi service status
3315
3392
  tapi doctor
3316
3393
 
@@ -3326,7 +3403,6 @@ Commands:
3326
3403
  apis generate Generate a TypeScript runtime wrapper from the catalog
3327
3404
  triggers sync Upsert API-call triggers from tapi.config
3328
3405
  sessions List dev-mode API sessions and open takeover sessions
3329
- publish Upload local .tapi API drafts and publish ready requests
3330
3406
  service Inspect or control the local Tapi Windows service
3331
3407
  doctor Alias for studio doctor
3332
3408
  `);
@@ -3451,15 +3527,6 @@ Commands:
3451
3527
  repair Restart tapi-service using the current installed service
3452
3528
  `);
3453
3529
  }
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
3530
  function formatError(error) {
3464
3531
  return error instanceof Error ? error.message : String(error);
3465
3532
  }
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.25",
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
- }