@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 +12 -13
- package/dist/cli.d.ts +16 -0
- package/dist/cli.js +239 -172
- package/dist/index.d.ts +2 -2
- package/dist/index.js +3 -3
- package/dist/services.d.ts +17 -0
- package/dist/{website-apis.js → services.js} +18 -18
- package/dist/triggers.js +6 -6
- package/dist/types.d.ts +13 -2
- package/package.json +1 -1
- package/dist/website-apis.d.ts +0 -17
package/README.md
CHANGED
|
@@ -63,19 +63,18 @@ tapi sessions
|
|
|
63
63
|
tapi publish
|
|
64
64
|
```
|
|
65
65
|
|
|
66
|
-
Local
|
|
67
|
-
|
|
68
|
-
```text
|
|
69
|
-
.tapi/project.json
|
|
70
|
-
.tapi/
|
|
71
|
-
.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
2463
|
-
|
|
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
|
|
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 (!
|
|
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
|
|
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(
|
|
11
|
-
const {
|
|
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/
|
|
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(
|
|
25
|
-
const {
|
|
26
|
-
return this.http.post(`/api/sdk/v1/
|
|
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(
|
|
29
|
-
return this.runCloudBatch(
|
|
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(
|
|
39
|
-
const {
|
|
38
|
+
async runCloudBatch(serviceRequest, request) {
|
|
39
|
+
const { serviceName, requestKey } = splitServiceRequest(serviceRequest);
|
|
40
40
|
try {
|
|
41
|
-
return await this.http.post(`/api/sdk/v1/
|
|
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(
|
|
51
|
-
const {
|
|
52
|
-
return this.http.get(`/api/sdk/v1/
|
|
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
|
|
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("
|
|
59
|
+
throw new Error("service request must be formatted as '<serviceName>.<requestKey>'");
|
|
60
60
|
}
|
|
61
61
|
return {
|
|
62
|
-
|
|
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/
|
|
7
|
+
return this.http.get("/api/sdk/v1/service-triggers");
|
|
8
8
|
}
|
|
9
9
|
create(request) {
|
|
10
|
-
return this.http.post("/api/sdk/v1/
|
|
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/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
-
|
|
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
|
-
|
|
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
package/dist/website-apis.d.ts
DELETED
|
@@ -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
|
-
}
|