@tapi-dev/sdk 0.1.17 → 0.1.21
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 +393 -292
- package/dist/cli.d.ts +10 -2
- package/dist/cli.js +629 -142
- package/dist/index.d.ts +4 -0
- package/dist/index.js +6 -1
- package/dist/late-input.d.ts +24 -0
- package/dist/late-input.js +112 -0
- package/dist/runs.d.ts +1 -0
- package/dist/runs.js +3 -0
- package/dist/sessions.d.ts +10 -0
- package/dist/sessions.js +14 -0
- package/dist/types.d.ts +49 -2
- package/dist/website-apis.d.ts +3 -1
- package/dist/website-apis.js +16 -3
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -8,6 +8,7 @@ import { mkdir, readFile, readdir, rename, rm, stat, unlink, writeFile } from "n
|
|
|
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";
|
|
11
|
+
import { emitKeypressEvents } from "node:readline";
|
|
11
12
|
import { Readable } from "node:stream";
|
|
12
13
|
import { pipeline } from "node:stream/promises";
|
|
13
14
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
@@ -174,6 +175,23 @@ export async function runCli(argv = process.argv.slice(2)) {
|
|
|
174
175
|
printTriggersHelp();
|
|
175
176
|
return 1;
|
|
176
177
|
}
|
|
178
|
+
if (command === "sessions") {
|
|
179
|
+
const sessionsCommand = subcommand && !subcommand.startsWith("-") ? subcommand : "list";
|
|
180
|
+
const sessionsArgs = subcommand && !subcommand.startsWith("-") ? rest : [subcommand, ...rest].filter(Boolean);
|
|
181
|
+
if (sessionsCommand === "help" || sessionsCommand === "--help" || sessionsCommand === "-h" || hasHelpFlag(sessionsArgs)) {
|
|
182
|
+
printSessionsHelp();
|
|
183
|
+
return 0;
|
|
184
|
+
}
|
|
185
|
+
if (sessionsCommand === "list") {
|
|
186
|
+
return listDevSessions(sessionsArgs);
|
|
187
|
+
}
|
|
188
|
+
if (sessionsCommand === "open") {
|
|
189
|
+
return openDevSession(sessionsArgs);
|
|
190
|
+
}
|
|
191
|
+
console.error(`Unknown sessions command: ${sessionsCommand}`);
|
|
192
|
+
printSessionsHelp();
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
177
195
|
if (command !== "studio") {
|
|
178
196
|
console.error(`Unknown command: ${command}`);
|
|
179
197
|
printHelp();
|
|
@@ -615,6 +633,350 @@ async function syncApiTriggers(args) {
|
|
|
615
633
|
return 1;
|
|
616
634
|
}
|
|
617
635
|
}
|
|
636
|
+
function parseSessionsOptions(args) {
|
|
637
|
+
const workspace = loadWorkspace();
|
|
638
|
+
let apiBaseUrl = envString("TAPI_BASE_URL") || workspace?.apiBaseUrl || DEFAULT_STUDIO_API_BASE_URL;
|
|
639
|
+
let apiKey = envString("TAPI_API_KEY") || "";
|
|
640
|
+
let projectId = envString("TAPI_PROJECT_ID") || workspace?.projectId || "";
|
|
641
|
+
let site = "";
|
|
642
|
+
let sessionId = "";
|
|
643
|
+
let json = false;
|
|
644
|
+
let noInteractive = false;
|
|
645
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
646
|
+
const arg = args[index];
|
|
647
|
+
if (!arg)
|
|
648
|
+
continue;
|
|
649
|
+
if (arg === "--api-base-url" || arg === "--server") {
|
|
650
|
+
apiBaseUrl = requireOptionValue(args, ++index, arg);
|
|
651
|
+
continue;
|
|
652
|
+
}
|
|
653
|
+
if (arg.startsWith("--api-base-url=")) {
|
|
654
|
+
apiBaseUrl = arg.slice("--api-base-url=".length);
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
if (arg.startsWith("--server=")) {
|
|
658
|
+
apiBaseUrl = arg.slice("--server=".length);
|
|
659
|
+
continue;
|
|
660
|
+
}
|
|
661
|
+
if (arg === "--api-key") {
|
|
662
|
+
apiKey = requireOptionValue(args, ++index, "--api-key");
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (arg.startsWith("--api-key=")) {
|
|
666
|
+
apiKey = arg.slice("--api-key=".length);
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (arg === "--project") {
|
|
670
|
+
projectId = normalizeProjectValue(requireOptionValue(args, ++index, "--project"), "project");
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
if (arg.startsWith("--project=")) {
|
|
674
|
+
projectId = normalizeProjectValue(arg.slice("--project=".length), "project");
|
|
675
|
+
continue;
|
|
676
|
+
}
|
|
677
|
+
if (arg === "--site") {
|
|
678
|
+
site = requireOptionValue(args, ++index, "--site").trim();
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
if (arg.startsWith("--site=")) {
|
|
682
|
+
site = arg.slice("--site=".length).trim();
|
|
683
|
+
continue;
|
|
684
|
+
}
|
|
685
|
+
if (arg === "--json") {
|
|
686
|
+
json = true;
|
|
687
|
+
noInteractive = true;
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
690
|
+
if (arg === "--no-interactive") {
|
|
691
|
+
noInteractive = true;
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
if (arg.startsWith("--")) {
|
|
695
|
+
throw new Error(`Unknown sessions option: ${arg}`);
|
|
696
|
+
}
|
|
697
|
+
if (!sessionId) {
|
|
698
|
+
sessionId = arg;
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
throw new Error(`Unexpected sessions argument: ${arg}`);
|
|
702
|
+
}
|
|
703
|
+
if (!apiKey) {
|
|
704
|
+
throw new Error("sessions command requires --api-key or TAPI_API_KEY.");
|
|
705
|
+
}
|
|
706
|
+
if (!projectId) {
|
|
707
|
+
throw new Error("sessions command requires --project, TAPI_PROJECT_ID, or .tapi/project.json.");
|
|
708
|
+
}
|
|
709
|
+
return {
|
|
710
|
+
sessionId,
|
|
711
|
+
options: {
|
|
712
|
+
apiBaseUrl: normalizeHttpUrl(apiBaseUrl, "Tapi API base URL"),
|
|
713
|
+
apiKey,
|
|
714
|
+
projectId,
|
|
715
|
+
site: site || undefined,
|
|
716
|
+
json,
|
|
717
|
+
noInteractive,
|
|
718
|
+
},
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
async function listDevSessions(args) {
|
|
722
|
+
try {
|
|
723
|
+
const { options } = parseSessionsOptions(args);
|
|
724
|
+
const sessions = await fetchDevSessions(options);
|
|
725
|
+
if (options.json) {
|
|
726
|
+
console.log(JSON.stringify(sessions, null, 2));
|
|
727
|
+
return 0;
|
|
728
|
+
}
|
|
729
|
+
const rows = flattenDevSessions(sessions);
|
|
730
|
+
if (rows.length === 0) {
|
|
731
|
+
console.log(`No Tapi dev sessions found for project ${sessions.projectId || options.projectId}.`);
|
|
732
|
+
return 0;
|
|
733
|
+
}
|
|
734
|
+
if (!options.noInteractive && process.stdin.isTTY && process.stdout.isTTY && rows.some((session) => session.openable)) {
|
|
735
|
+
return await runSessionsPicker(sessions, options);
|
|
736
|
+
}
|
|
737
|
+
console.log(renderDevSessions(sessions));
|
|
738
|
+
const openable = rows.find((session) => session.openable);
|
|
739
|
+
if (openable) {
|
|
740
|
+
console.log("\nOpen a takeover session with `tapi sessions open <session-id>`.");
|
|
741
|
+
}
|
|
742
|
+
return 0;
|
|
743
|
+
}
|
|
744
|
+
catch (error) {
|
|
745
|
+
console.error(formatError(error));
|
|
746
|
+
return 1;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
async function openDevSession(args) {
|
|
750
|
+
try {
|
|
751
|
+
const { sessionId, options } = parseSessionsOptions(args);
|
|
752
|
+
if (!sessionId) {
|
|
753
|
+
throw new Error("sessions open requires a session id.");
|
|
754
|
+
}
|
|
755
|
+
const sessions = await fetchDevSessions(options);
|
|
756
|
+
const session = findDevSession(sessions, sessionId);
|
|
757
|
+
if (!session) {
|
|
758
|
+
throw new Error(`Tapi session not found: ${sessionId}`);
|
|
759
|
+
}
|
|
760
|
+
return await openDevSessionInStudio(session, options);
|
|
761
|
+
}
|
|
762
|
+
catch (error) {
|
|
763
|
+
console.error(formatError(error));
|
|
764
|
+
return 1;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async function fetchDevSessions(options) {
|
|
768
|
+
const client = new TapiClient({
|
|
769
|
+
baseUrl: options.apiBaseUrl,
|
|
770
|
+
apiKey: options.apiKey,
|
|
771
|
+
projectId: options.projectId,
|
|
772
|
+
});
|
|
773
|
+
return await withCliSpinner("Fetching Tapi dev sessions", () => client.sessions.list({ site: options.site }));
|
|
774
|
+
}
|
|
775
|
+
function flattenDevSessions(sessions) {
|
|
776
|
+
return (sessions.groups || []).flatMap((group) => group.sessions || []);
|
|
777
|
+
}
|
|
778
|
+
function findDevSession(sessions, sessionId) {
|
|
779
|
+
const normalized = sessionId.trim();
|
|
780
|
+
return flattenDevSessions(sessions).find((session) => session.id === normalized || session.id.startsWith(normalized));
|
|
781
|
+
}
|
|
782
|
+
async function runSessionsPicker(sessions, options) {
|
|
783
|
+
let currentSessions = sessions;
|
|
784
|
+
let rows = flattenDevSessions(currentSessions);
|
|
785
|
+
let openableIndexes = openableSessionIndexes(rows);
|
|
786
|
+
if (openableIndexes.length === 0) {
|
|
787
|
+
console.log(renderDevSessions(currentSessions));
|
|
788
|
+
return 0;
|
|
789
|
+
}
|
|
790
|
+
let selectedIndex = openableIndexes[0];
|
|
791
|
+
const input = process.stdin;
|
|
792
|
+
return await new Promise((resolvePromise) => {
|
|
793
|
+
let onKeypress;
|
|
794
|
+
const cleanup = () => {
|
|
795
|
+
input.off("keypress", onKeypress);
|
|
796
|
+
input.setRawMode?.(false);
|
|
797
|
+
input.pause();
|
|
798
|
+
};
|
|
799
|
+
const move = (direction) => {
|
|
800
|
+
if (openableIndexes.length === 0)
|
|
801
|
+
return;
|
|
802
|
+
const currentOpenableIndex = openableIndexes.indexOf(selectedIndex);
|
|
803
|
+
const nextOpenableIndex = (currentOpenableIndex + direction + openableIndexes.length) % openableIndexes.length;
|
|
804
|
+
selectedIndex = openableIndexes[nextOpenableIndex];
|
|
805
|
+
render();
|
|
806
|
+
};
|
|
807
|
+
const render = () => {
|
|
808
|
+
process.stdout.write("\x1Bc");
|
|
809
|
+
process.stdout.write(renderDevSessions(currentSessions, rows[selectedIndex]?.id));
|
|
810
|
+
process.stdout.write("\n\nUse Up/Down and Enter to open Studio. Press r to refresh, q to exit.\n");
|
|
811
|
+
};
|
|
812
|
+
const finish = (code) => {
|
|
813
|
+
cleanup();
|
|
814
|
+
resolvePromise(code);
|
|
815
|
+
};
|
|
816
|
+
onKeypress = (_input, key = {}) => {
|
|
817
|
+
if (key.ctrl && key.name === "c") {
|
|
818
|
+
finish(130);
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
if (key.name === "q" || key.name === "escape") {
|
|
822
|
+
finish(0);
|
|
823
|
+
return;
|
|
824
|
+
}
|
|
825
|
+
if (key.name === "up") {
|
|
826
|
+
move(-1);
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
if (key.name === "down") {
|
|
830
|
+
move(1);
|
|
831
|
+
return;
|
|
832
|
+
}
|
|
833
|
+
if (key.name === "r") {
|
|
834
|
+
fetchDevSessions(options)
|
|
835
|
+
.then((nextSessions) => {
|
|
836
|
+
currentSessions = nextSessions;
|
|
837
|
+
rows = flattenDevSessions(currentSessions);
|
|
838
|
+
openableIndexes = openableSessionIndexes(rows);
|
|
839
|
+
if (!rows[selectedIndex]?.openable) {
|
|
840
|
+
selectedIndex = openableIndexes[0] ?? 0;
|
|
841
|
+
}
|
|
842
|
+
render();
|
|
843
|
+
})
|
|
844
|
+
.catch((error) => {
|
|
845
|
+
process.stderr.write(`\n${formatError(error)}\n`);
|
|
846
|
+
render();
|
|
847
|
+
});
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
if (key.name === "return" || key.name === "enter") {
|
|
851
|
+
const session = rows[selectedIndex];
|
|
852
|
+
if (!session?.openable) {
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
cleanup();
|
|
856
|
+
openDevSessionInStudio(session, options)
|
|
857
|
+
.then(resolvePromise)
|
|
858
|
+
.catch((error) => {
|
|
859
|
+
console.error(formatError(error));
|
|
860
|
+
resolvePromise(1);
|
|
861
|
+
});
|
|
862
|
+
}
|
|
863
|
+
};
|
|
864
|
+
emitKeypressEvents(input);
|
|
865
|
+
input.setRawMode?.(true);
|
|
866
|
+
input.resume();
|
|
867
|
+
input.on("keypress", onKeypress);
|
|
868
|
+
render();
|
|
869
|
+
});
|
|
870
|
+
}
|
|
871
|
+
function openableSessionIndexes(rows) {
|
|
872
|
+
return rows
|
|
873
|
+
.map((session, index) => (session.openable ? index : -1))
|
|
874
|
+
.filter((index) => index >= 0);
|
|
875
|
+
}
|
|
876
|
+
async function openDevSessionInStudio(session, options) {
|
|
877
|
+
if (!session.openable) {
|
|
878
|
+
throw new Error(`Tapi session ${session.id} is not awaiting developer takeover.`);
|
|
879
|
+
}
|
|
880
|
+
const params = studioSessionLaunchParams(session);
|
|
881
|
+
const studioOptions = parseStudioOptions([
|
|
882
|
+
"--api-base-url",
|
|
883
|
+
options.apiBaseUrl,
|
|
884
|
+
"--project",
|
|
885
|
+
options.projectId,
|
|
886
|
+
]);
|
|
887
|
+
studioOptions.launchUrlQuery = params.toString();
|
|
888
|
+
const selected = await trySelectDevSessionInOpenStudio(session, studioOptions);
|
|
889
|
+
if (selected.selected) {
|
|
890
|
+
console.log(`Selected Tapi Studio session in existing Studio: ${selected.baseUrl}`);
|
|
891
|
+
return 0;
|
|
892
|
+
}
|
|
893
|
+
return await openStudio(studioOptions);
|
|
894
|
+
}
|
|
895
|
+
function studioSessionLaunchParams(session) {
|
|
896
|
+
const params = new URLSearchParams();
|
|
897
|
+
params.set("runId", session.id);
|
|
898
|
+
params.set("apiRunId", session.id);
|
|
899
|
+
if (session.sitemap)
|
|
900
|
+
params.set("sitemap", session.sitemap);
|
|
901
|
+
if (session.runtimeSessionId)
|
|
902
|
+
params.set("runtimeSessionId", session.runtimeSessionId);
|
|
903
|
+
return params;
|
|
904
|
+
}
|
|
905
|
+
function studioSessionSelectIntent(session) {
|
|
906
|
+
return {
|
|
907
|
+
sessionId: session.id,
|
|
908
|
+
runId: session.id,
|
|
909
|
+
apiRunId: session.id,
|
|
910
|
+
...(session.sitemap ? { sitemap: session.sitemap } : {}),
|
|
911
|
+
...(session.runtimeSessionId ? { runtimeSessionId: session.runtimeSessionId } : {}),
|
|
912
|
+
source: "cli",
|
|
913
|
+
selectedAt: new Date().toISOString(),
|
|
914
|
+
};
|
|
915
|
+
}
|
|
916
|
+
export async function trySelectDevSessionInOpenStudio(session, options, fetchImpl = fetch) {
|
|
917
|
+
const record = await readStudioInstanceRecord(options);
|
|
918
|
+
if (!record) {
|
|
919
|
+
return { selected: false, reason: "instance_record_missing" };
|
|
920
|
+
}
|
|
921
|
+
try {
|
|
922
|
+
const identityResponse = await fetchWithTimeout(`${record.baseUrl.replace(/\/+$/, "")}/api/studio/control/identity`, { headers: { Accept: "application/json" } }, 1_000, fetchImpl);
|
|
923
|
+
if (!identityResponse.ok) {
|
|
924
|
+
await unlinkIfExists(studioInstanceRecordPathForOptions(options));
|
|
925
|
+
return { selected: false, reason: `identity_http_${identityResponse.status}` };
|
|
926
|
+
}
|
|
927
|
+
const identity = await readJsonBody(identityResponse);
|
|
928
|
+
if (!studioInstanceIdentityMatches(identity, options)) {
|
|
929
|
+
return { selected: false, reason: "identity_mismatch" };
|
|
930
|
+
}
|
|
931
|
+
const selectResponse = await fetchWithTimeout(`${record.baseUrl.replace(/\/+$/, "")}/api/studio/control/select-session`, {
|
|
932
|
+
method: "POST",
|
|
933
|
+
headers: {
|
|
934
|
+
Accept: "application/json",
|
|
935
|
+
"Content-Type": "application/json",
|
|
936
|
+
},
|
|
937
|
+
body: JSON.stringify(studioSessionSelectIntent(session)),
|
|
938
|
+
}, 1_500, fetchImpl);
|
|
939
|
+
if (!selectResponse.ok) {
|
|
940
|
+
return { selected: false, reason: `select_http_${selectResponse.status}` };
|
|
941
|
+
}
|
|
942
|
+
return { selected: true, baseUrl: record.baseUrl };
|
|
943
|
+
}
|
|
944
|
+
catch (error) {
|
|
945
|
+
return { selected: false, reason: formatError(error) };
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
function renderDevSessions(sessions, selectedId = "") {
|
|
949
|
+
const lines = [`Tapi sessions for project ${sessions.projectId || "unknown"}`];
|
|
950
|
+
for (const group of sessions.groups || []) {
|
|
951
|
+
lines.push("");
|
|
952
|
+
lines.push(`[${group.sitemap || "unknown"}]`);
|
|
953
|
+
if (!group.sessions || group.sessions.length === 0) {
|
|
954
|
+
lines.push(" No sessions.");
|
|
955
|
+
continue;
|
|
956
|
+
}
|
|
957
|
+
for (const session of group.sessions) {
|
|
958
|
+
const selected = selectedId && session.id === selectedId ? ">" : " ";
|
|
959
|
+
const openable = session.openable ? "*" : " ";
|
|
960
|
+
const id = truncateText(session.id || "", 18).padEnd(18, " ");
|
|
961
|
+
const status = truncateText(String(session.status || ""), 24).padEnd(24, " ");
|
|
962
|
+
const apiName = session.apiName && session.requestKey
|
|
963
|
+
? `${session.apiName}.${session.requestKey}`
|
|
964
|
+
: session.apiName || session.requestKey || "unknown";
|
|
965
|
+
const state = session.stateLabel ? ` state=${session.stateLabel}` : "";
|
|
966
|
+
const url = session.currentUrl ? ` ${truncateText(session.currentUrl, 60)}` : "";
|
|
967
|
+
lines.push(`${selected}${openable} ${id} ${status} ${apiName}${state}${url}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
lines.push("");
|
|
971
|
+
lines.push("* openable awaiting-takeover session");
|
|
972
|
+
return lines.join("\n");
|
|
973
|
+
}
|
|
974
|
+
function truncateText(value, maxLength) {
|
|
975
|
+
if (value.length <= maxLength) {
|
|
976
|
+
return value;
|
|
977
|
+
}
|
|
978
|
+
return `${value.slice(0, Math.max(0, maxLength - 3))}...`;
|
|
979
|
+
}
|
|
618
980
|
function defaultTriggerConfigPath(root) {
|
|
619
981
|
for (const name of ["tapi.config.ts", "tapi.config.mjs", "tapi.config.js", "tapi.config.cjs", "tapi.config.json"]) {
|
|
620
982
|
const path = join(root, name);
|
|
@@ -856,23 +1218,23 @@ function renderGeneratedApiClient(catalog) {
|
|
|
856
1218
|
const lines = operations.map(({ key, operationName }) => ` ${operationName}: (inputs: Record<string, unknown> = {}, options: GeneratedRunOptions = {}) => client.websiteApis.run(${JSON.stringify(key)}, { ...options, inputs }),`);
|
|
857
1219
|
return ` ${namespace}: {\n${lines.join("\n")}\n },`;
|
|
858
1220
|
});
|
|
859
|
-
return `/* Generated by Tapi. Do not edit by hand. */
|
|
860
|
-
import { TapiClient, type TapiClientOptions, type RuntimeRunOptions } from "@tapi-dev/sdk";
|
|
861
|
-
|
|
862
|
-
export interface GeneratedRunOptions {
|
|
863
|
-
runtime?: RuntimeRunOptions;
|
|
864
|
-
priority?: number;
|
|
865
|
-
runnerId?: string;
|
|
866
|
-
idempotencyKey?: string;
|
|
867
|
-
site?: string;
|
|
868
|
-
}
|
|
869
|
-
|
|
870
|
-
export function createTapiGeneratedClient(options: TapiClientOptions) {
|
|
871
|
-
const client = new TapiClient(options);
|
|
872
|
-
return {
|
|
873
|
-
${namespaceBlocks.join("\n")}
|
|
874
|
-
};
|
|
875
|
-
}
|
|
1221
|
+
return `/* Generated by Tapi. Do not edit by hand. */
|
|
1222
|
+
import { TapiClient, type TapiClientOptions, type RuntimeRunOptions } from "@tapi-dev/sdk";
|
|
1223
|
+
|
|
1224
|
+
export interface GeneratedRunOptions {
|
|
1225
|
+
runtime?: RuntimeRunOptions;
|
|
1226
|
+
priority?: number;
|
|
1227
|
+
runnerId?: string;
|
|
1228
|
+
idempotencyKey?: string;
|
|
1229
|
+
site?: string;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
export function createTapiGeneratedClient(options: TapiClientOptions) {
|
|
1233
|
+
const client = new TapiClient(options);
|
|
1234
|
+
return {
|
|
1235
|
+
${namespaceBlocks.join("\n")}
|
|
1236
|
+
};
|
|
1237
|
+
}
|
|
876
1238
|
`;
|
|
877
1239
|
}
|
|
878
1240
|
function safeIdentifier(value) {
|
|
@@ -1889,7 +2251,7 @@ async function openStudio(options) {
|
|
|
1889
2251
|
}
|
|
1890
2252
|
if (!exePath || !existsSync(exePath)) {
|
|
1891
2253
|
console.error("Tapi Studio executable was not found.");
|
|
1892
|
-
console.error("Run `
|
|
2254
|
+
console.error("Run `tapi studio install --channel pilot`, or set TAPI_STUDIO_EXE to the installed executable path.");
|
|
1893
2255
|
return 1;
|
|
1894
2256
|
}
|
|
1895
2257
|
const child = spawn(exePath, [], {
|
|
@@ -2004,6 +2366,9 @@ async function launchPortableStudioServer(exePath, options, manifest) {
|
|
|
2004
2366
|
env.PLAYWRIGHT_BROWSERS_PATH = playwrightBrowsersPath;
|
|
2005
2367
|
}
|
|
2006
2368
|
const url = `http://${host}:${port}`;
|
|
2369
|
+
const launchQuery = options.launchUrlQuery ? options.launchUrlQuery.replace(/^\?+/, "") : "";
|
|
2370
|
+
const launchUrl = launchQuery ? `${url}/?${launchQuery}` : url;
|
|
2371
|
+
let childPid = 0;
|
|
2007
2372
|
await withCliSpinner("Starting Tapi Studio", async () => {
|
|
2008
2373
|
const child = spawn(exePath, [], {
|
|
2009
2374
|
detached: true,
|
|
@@ -2011,11 +2376,25 @@ async function launchPortableStudioServer(exePath, options, manifest) {
|
|
|
2011
2376
|
stdio: "ignore",
|
|
2012
2377
|
windowsHide: true,
|
|
2013
2378
|
});
|
|
2379
|
+
childPid = child.pid ?? 0;
|
|
2014
2380
|
child.unref();
|
|
2015
2381
|
await waitForHttpOk(`${url}/healthz`, PORTABLE_STUDIO_SERVER_READY_TIMEOUT_MS, PORTABLE_STUDIO_SERVER_READY_INTERVAL_MS);
|
|
2016
2382
|
});
|
|
2017
|
-
|
|
2018
|
-
|
|
2383
|
+
await writeStudioInstanceRecord(options, {
|
|
2384
|
+
version: 1,
|
|
2385
|
+
baseUrl: url,
|
|
2386
|
+
host,
|
|
2387
|
+
port,
|
|
2388
|
+
pid: childPid || undefined,
|
|
2389
|
+
apiBaseUrl: options.apiBaseUrl,
|
|
2390
|
+
projectId: options.projectId,
|
|
2391
|
+
workspaceRoot: options.workspaceRoot,
|
|
2392
|
+
workspaceConfig: options.workspace?.configPath,
|
|
2393
|
+
manifestVersion: manifest.version,
|
|
2394
|
+
updatedAt: new Date().toISOString(),
|
|
2395
|
+
});
|
|
2396
|
+
openBrowser(launchUrl);
|
|
2397
|
+
console.log(`Opened Tapi Studio ${manifest.version}: ${launchUrl}`);
|
|
2019
2398
|
}
|
|
2020
2399
|
export function findPortablePlaywrightBrowsersDir(exePath) {
|
|
2021
2400
|
const exeDir = dirname(exePath);
|
|
@@ -2061,6 +2440,19 @@ export async function waitForHttpOk(url, timeoutMs = PORTABLE_STUDIO_SERVER_READ
|
|
|
2061
2440
|
const suffix = lastError ? ` Last error: ${lastError}.` : "";
|
|
2062
2441
|
throw new Error(`Tapi Studio server did not become ready at ${url} within ${Math.ceil(timeoutMs / 1000)}s.${suffix}`);
|
|
2063
2442
|
}
|
|
2443
|
+
async function fetchWithTimeout(url, init, timeoutMs, fetchImpl = fetch) {
|
|
2444
|
+
const controller = new AbortController();
|
|
2445
|
+
const timeout = setTimeout(() => controller.abort(), Math.max(1, timeoutMs));
|
|
2446
|
+
try {
|
|
2447
|
+
return await fetchImpl(url, {
|
|
2448
|
+
...init,
|
|
2449
|
+
signal: controller.signal,
|
|
2450
|
+
});
|
|
2451
|
+
}
|
|
2452
|
+
finally {
|
|
2453
|
+
clearTimeout(timeout);
|
|
2454
|
+
}
|
|
2455
|
+
}
|
|
2064
2456
|
function delay(ms) {
|
|
2065
2457
|
return new Promise((resolvePromise) => {
|
|
2066
2458
|
setTimeout(resolvePromise, Math.max(0, ms));
|
|
@@ -2184,7 +2576,7 @@ async function requestStudioInstallToken(apiBaseUrl, channel, firebaseIdToken, f
|
|
|
2184
2576
|
if (!response.ok) {
|
|
2185
2577
|
const detail = typeof responseBody?.detail === "string" ? responseBody.detail : "";
|
|
2186
2578
|
if (detail === "pending_approval" || detail === "access_pending") {
|
|
2187
|
-
throw new StudioInstallApprovalError("Access request submitted. Check your email for approval, then rerun `
|
|
2579
|
+
throw new StudioInstallApprovalError("Access request submitted. Check your email for approval, then rerun `tapi studio install`.", detail, response.status);
|
|
2188
2580
|
}
|
|
2189
2581
|
if (detail === "access_rejected") {
|
|
2190
2582
|
throw new StudioInstallApprovalError("Studio install access was rejected. Contact the Tapi admin if this is unexpected.", detail, response.status);
|
|
@@ -2371,6 +2763,81 @@ async function writeStudioAuthCache(credentials) {
|
|
|
2371
2763
|
function getStudioAuthCachePath() {
|
|
2372
2764
|
return join(getDefaultTapiDataDir(), "auth.json");
|
|
2373
2765
|
}
|
|
2766
|
+
export function studioInstanceRecordPathForOptions(options) {
|
|
2767
|
+
const key = createHash("sha256")
|
|
2768
|
+
.update(JSON.stringify({
|
|
2769
|
+
apiBaseUrl: comparableHttpUrl(options.apiBaseUrl),
|
|
2770
|
+
projectId: options.projectId ?? "",
|
|
2771
|
+
workspaceRoot: comparableFsPath(options.workspaceRoot ?? ""),
|
|
2772
|
+
}))
|
|
2773
|
+
.digest("hex")
|
|
2774
|
+
.slice(0, 24);
|
|
2775
|
+
return join(getDefaultTapiDataDir(), "Studio", "instances", `${key}.json`);
|
|
2776
|
+
}
|
|
2777
|
+
async function writeStudioInstanceRecord(options, record) {
|
|
2778
|
+
await writeJsonFile(studioInstanceRecordPathForOptions(options), record);
|
|
2779
|
+
}
|
|
2780
|
+
async function readStudioInstanceRecord(options) {
|
|
2781
|
+
try {
|
|
2782
|
+
const parsed = JSON.parse(await readFile(studioInstanceRecordPathForOptions(options), "utf8"));
|
|
2783
|
+
if (!isRecord(parsed)) {
|
|
2784
|
+
return null;
|
|
2785
|
+
}
|
|
2786
|
+
const baseUrl = typeof parsed.baseUrl === "string" ? parsed.baseUrl.trim() : "";
|
|
2787
|
+
const host = typeof parsed.host === "string" ? parsed.host.trim() : "";
|
|
2788
|
+
const port = typeof parsed.port === "number" && Number.isFinite(parsed.port) ? parsed.port : 0;
|
|
2789
|
+
if (!baseUrl || !host || port <= 0) {
|
|
2790
|
+
return null;
|
|
2791
|
+
}
|
|
2792
|
+
return {
|
|
2793
|
+
version: 1,
|
|
2794
|
+
baseUrl,
|
|
2795
|
+
host,
|
|
2796
|
+
port,
|
|
2797
|
+
pid: typeof parsed.pid === "number" && Number.isFinite(parsed.pid) ? parsed.pid : undefined,
|
|
2798
|
+
apiBaseUrl: typeof parsed.apiBaseUrl === "string" ? parsed.apiBaseUrl : "",
|
|
2799
|
+
projectId: typeof parsed.projectId === "string" ? parsed.projectId : undefined,
|
|
2800
|
+
workspaceRoot: typeof parsed.workspaceRoot === "string" ? parsed.workspaceRoot : undefined,
|
|
2801
|
+
workspaceConfig: typeof parsed.workspaceConfig === "string" ? parsed.workspaceConfig : undefined,
|
|
2802
|
+
manifestVersion: typeof parsed.manifestVersion === "string" ? parsed.manifestVersion : undefined,
|
|
2803
|
+
updatedAt: typeof parsed.updatedAt === "string" ? parsed.updatedAt : "",
|
|
2804
|
+
};
|
|
2805
|
+
}
|
|
2806
|
+
catch (error) {
|
|
2807
|
+
if (error.code === "ENOENT") {
|
|
2808
|
+
return null;
|
|
2809
|
+
}
|
|
2810
|
+
return null;
|
|
2811
|
+
}
|
|
2812
|
+
}
|
|
2813
|
+
function studioInstanceIdentityMatches(identity, options) {
|
|
2814
|
+
if (!identity || identity.ok !== true) {
|
|
2815
|
+
return false;
|
|
2816
|
+
}
|
|
2817
|
+
const expectedProject = (options.projectId ?? "").trim();
|
|
2818
|
+
const actualProject = typeof identity.projectId === "string" ? identity.projectId.trim() : "";
|
|
2819
|
+
if (expectedProject && actualProject && actualProject !== expectedProject) {
|
|
2820
|
+
return false;
|
|
2821
|
+
}
|
|
2822
|
+
const expectedWorkspace = comparableFsPath(options.workspaceRoot ?? "");
|
|
2823
|
+
const actualWorkspace = comparableFsPath(typeof identity.workspaceRoot === "string" ? identity.workspaceRoot : "");
|
|
2824
|
+
if (expectedWorkspace && actualWorkspace && actualWorkspace !== expectedWorkspace) {
|
|
2825
|
+
return false;
|
|
2826
|
+
}
|
|
2827
|
+
const expectedApiBaseUrl = comparableHttpUrl(options.apiBaseUrl);
|
|
2828
|
+
const actualApiBaseUrl = comparableHttpUrl(typeof identity.apiBaseUrl === "string" ? identity.apiBaseUrl : "");
|
|
2829
|
+
if (expectedApiBaseUrl && actualApiBaseUrl && actualApiBaseUrl !== expectedApiBaseUrl) {
|
|
2830
|
+
return false;
|
|
2831
|
+
}
|
|
2832
|
+
return true;
|
|
2833
|
+
}
|
|
2834
|
+
function comparableHttpUrl(value) {
|
|
2835
|
+
return String(value || "").trim().replace(/\/+$/, "").toLowerCase();
|
|
2836
|
+
}
|
|
2837
|
+
function comparableFsPath(value) {
|
|
2838
|
+
const trimmed = String(value || "").trim();
|
|
2839
|
+
return trimmed ? resolve(trimmed).toLowerCase() : "";
|
|
2840
|
+
}
|
|
2374
2841
|
function getDefaultTapiDataDir() {
|
|
2375
2842
|
if (process.platform === "win32") {
|
|
2376
2843
|
const localAppData = process.env.LOCALAPPDATA
|
|
@@ -2831,146 +3298,166 @@ function readSdkVersion() {
|
|
|
2831
3298
|
function printHelp() {
|
|
2832
3299
|
console.log(`Tapi CLI
|
|
2833
3300
|
|
|
2834
|
-
Usage:
|
|
2835
|
-
tapi init --project PROJECT
|
|
2836
|
-
tapi link --project PROJECT
|
|
2837
|
-
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
2838
|
-
tapi studio
|
|
2839
|
-
tapi studio open
|
|
2840
|
-
tapi studio doctor
|
|
2841
|
-
tapi apis describe <namespace.operation>
|
|
2842
|
-
tapi apis sync
|
|
2843
|
-
tapi apis generate
|
|
2844
|
-
tapi triggers sync
|
|
2845
|
-
tapi
|
|
2846
|
-
tapi
|
|
2847
|
-
tapi
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
studio
|
|
2854
|
-
studio
|
|
2855
|
-
studio
|
|
2856
|
-
|
|
2857
|
-
apis
|
|
2858
|
-
apis
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
3301
|
+
Usage:
|
|
3302
|
+
tapi init --project PROJECT
|
|
3303
|
+
tapi link --project PROJECT
|
|
3304
|
+
tapi studio install [--channel pilot] [--api-base-url URL]
|
|
3305
|
+
tapi studio
|
|
3306
|
+
tapi studio open
|
|
3307
|
+
tapi studio doctor
|
|
3308
|
+
tapi apis describe <namespace.operation>
|
|
3309
|
+
tapi apis sync
|
|
3310
|
+
tapi apis generate
|
|
3311
|
+
tapi triggers sync
|
|
3312
|
+
tapi sessions
|
|
3313
|
+
tapi publish
|
|
3314
|
+
tapi service status
|
|
3315
|
+
tapi doctor
|
|
3316
|
+
|
|
3317
|
+
Commands:
|
|
3318
|
+
init Create .tapi/project.json for this repo
|
|
3319
|
+
link Rebind this repo to an existing Tapi project
|
|
3320
|
+
studio install Download, verify, and run the Tapi Studio installer
|
|
3321
|
+
studio Open Tapi Studio for this repo
|
|
3322
|
+
studio open Open Tapi Studio for this repo
|
|
3323
|
+
studio doctor Check local SDK and Studio release configuration
|
|
3324
|
+
apis describe Print a generated website API input/output contract
|
|
3325
|
+
apis sync Save the published API catalog to .tapi/generated
|
|
3326
|
+
apis generate Generate a TypeScript runtime wrapper from the catalog
|
|
3327
|
+
triggers sync Upsert API-call triggers from tapi.config
|
|
3328
|
+
sessions List dev-mode API sessions and open takeover sessions
|
|
3329
|
+
publish Upload local .tapi API drafts and publish ready requests
|
|
3330
|
+
service Inspect or control the local Tapi Windows service
|
|
3331
|
+
doctor Alias for studio doctor
|
|
3332
|
+
`);
|
|
3333
|
+
}
|
|
3334
|
+
function printSessionsHelp() {
|
|
3335
|
+
console.log(`Tapi dev session commands
|
|
3336
|
+
|
|
3337
|
+
Usage:
|
|
3338
|
+
tapi sessions [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3339
|
+
tapi sessions list [--json] [--site SITE]
|
|
3340
|
+
tapi sessions open <session-id> [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3341
|
+
|
|
3342
|
+
Options:
|
|
3343
|
+
--api-base-url <url> Tapi API base URL
|
|
3344
|
+
--server <url> Alias for --api-base-url
|
|
3345
|
+
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
3346
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3347
|
+
--site <name> Limit sessions to one sitemap/site
|
|
3348
|
+
--json Print raw grouped session JSON
|
|
3349
|
+
--no-interactive Print the grouped list without the arrow-key picker
|
|
2863
3350
|
`);
|
|
2864
3351
|
}
|
|
2865
3352
|
function printApisHelp() {
|
|
2866
|
-
console.log(`Tapi generated website API commands
|
|
2867
|
-
|
|
2868
|
-
Usage:
|
|
2869
|
-
tapi apis describe <namespace.operation> [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
2870
|
-
tapi apis sync [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
2871
|
-
tapi apis generate [--api-base-url URL] [--api-key KEY] [--project PROJECT] [--out FILE]
|
|
2872
|
-
|
|
2873
|
-
Options:
|
|
2874
|
-
--api-base-url <url> Tapi API base URL
|
|
2875
|
-
--server <url> Alias for --api-base-url
|
|
2876
|
-
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
2877
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
2878
|
-
--catalog <path> Catalog JSON output path
|
|
2879
|
-
--out <path> Generated TypeScript output path
|
|
3353
|
+
console.log(`Tapi generated website API commands
|
|
3354
|
+
|
|
3355
|
+
Usage:
|
|
3356
|
+
tapi apis describe <namespace.operation> [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3357
|
+
tapi apis sync [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3358
|
+
tapi apis generate [--api-base-url URL] [--api-key KEY] [--project PROJECT] [--out FILE]
|
|
3359
|
+
|
|
3360
|
+
Options:
|
|
3361
|
+
--api-base-url <url> Tapi API base URL
|
|
3362
|
+
--server <url> Alias for --api-base-url
|
|
3363
|
+
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
3364
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3365
|
+
--catalog <path> Catalog JSON output path
|
|
3366
|
+
--out <path> Generated TypeScript output path
|
|
2880
3367
|
`);
|
|
2881
3368
|
}
|
|
2882
3369
|
function printTriggersHelp() {
|
|
2883
|
-
console.log(`Tapi API trigger commands
|
|
2884
|
-
|
|
2885
|
-
Usage:
|
|
2886
|
-
tapi triggers sync [--config FILE] [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
2887
|
-
|
|
2888
|
-
Options:
|
|
2889
|
-
--config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
|
|
2890
|
-
--api-base-url <url> Tapi API base URL
|
|
2891
|
-
--server <url> Alias for --api-base-url
|
|
2892
|
-
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
2893
|
-
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
2894
|
-
|
|
2895
|
-
Config:
|
|
2896
|
-
export default {
|
|
2897
|
-
triggers: {
|
|
2898
|
-
nightlyBalance: {
|
|
2899
|
-
apiRequest: "schwab.get_balance",
|
|
2900
|
-
interval: "1h",
|
|
2901
|
-
inputs: { accountId: "main" },
|
|
2902
|
-
runtime: { profileRef: "perm_default" },
|
|
2903
|
-
},
|
|
2904
|
-
},
|
|
2905
|
-
};
|
|
3370
|
+
console.log(`Tapi API trigger commands
|
|
3371
|
+
|
|
3372
|
+
Usage:
|
|
3373
|
+
tapi triggers sync [--config FILE] [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
3374
|
+
|
|
3375
|
+
Options:
|
|
3376
|
+
--config <path> Trigger config path; defaults to tapi.config.ts/js/json in the workspace root
|
|
3377
|
+
--api-base-url <url> Tapi API base URL
|
|
3378
|
+
--server <url> Alias for --api-base-url
|
|
3379
|
+
--api-key <key> Tapi API key; defaults to TAPI_API_KEY
|
|
3380
|
+
--project <id> Tapi project id; defaults to TAPI_PROJECT_ID or .tapi/project.json
|
|
3381
|
+
|
|
3382
|
+
Config:
|
|
3383
|
+
export default {
|
|
3384
|
+
triggers: {
|
|
3385
|
+
nightlyBalance: {
|
|
3386
|
+
apiRequest: "schwab.get_balance",
|
|
3387
|
+
interval: "1h",
|
|
3388
|
+
inputs: { accountId: "main" },
|
|
3389
|
+
runtime: { profileRef: "perm_default" },
|
|
3390
|
+
},
|
|
3391
|
+
},
|
|
3392
|
+
};
|
|
2906
3393
|
`);
|
|
2907
3394
|
}
|
|
2908
3395
|
function printStudioHelp() {
|
|
2909
|
-
console.log(`Tapi Studio commands
|
|
2910
|
-
|
|
2911
|
-
Usage:
|
|
2912
|
-
tapi studio [options]
|
|
2913
|
-
tapi studio install [options]
|
|
2914
|
-
tapi studio open [options]
|
|
2915
|
-
tapi studio doctor [options]
|
|
2916
|
-
|
|
2917
|
-
Options:
|
|
3396
|
+
console.log(`Tapi Studio commands
|
|
3397
|
+
|
|
3398
|
+
Usage:
|
|
3399
|
+
tapi studio [options]
|
|
3400
|
+
tapi studio install [options]
|
|
3401
|
+
tapi studio open [options]
|
|
3402
|
+
tapi studio doctor [options]
|
|
3403
|
+
|
|
3404
|
+
Options:
|
|
2918
3405
|
--channel <name> Release channel: pilot, stable, or nightly
|
|
2919
|
-
--api-base-url <url> Tapi API base URL for install authorization and protected downloads
|
|
2920
|
-
--server <url> Alias for --api-base-url
|
|
2921
|
-
--workspace <path> Search this directory for .tapi/project.json
|
|
2922
|
-
--no-workspace Do not load .tapi/project.json
|
|
2923
|
-
--project <id> Tapi project id for workspace-bound Studio
|
|
2924
|
-
--project-slug <slug> Optional display slug for the bound project
|
|
2925
|
-
--install-token <tok> Preissued Studio install token (skips browser sign-in)
|
|
2926
|
-
--manifest <url> Exact release manifest URL for doctor only
|
|
2927
|
-
--cache-dir <path> Installer download cache directory
|
|
2928
|
-
--install-dir <path> Portable Studio server release directory
|
|
2929
|
-
--download-only Download and verify without running the installer
|
|
2930
|
-
--silent Run the NSIS installer with /S
|
|
2931
|
-
--exe <path> Tapi Studio executable path for open/doctor
|
|
3406
|
+
--api-base-url <url> Tapi API base URL for install authorization and protected downloads
|
|
3407
|
+
--server <url> Alias for --api-base-url
|
|
3408
|
+
--workspace <path> Search this directory for .tapi/project.json
|
|
3409
|
+
--no-workspace Do not load .tapi/project.json
|
|
3410
|
+
--project <id> Tapi project id for workspace-bound Studio
|
|
3411
|
+
--project-slug <slug> Optional display slug for the bound project
|
|
3412
|
+
--install-token <tok> Preissued Studio install token (skips browser sign-in)
|
|
3413
|
+
--manifest <url> Exact release manifest URL for doctor only
|
|
3414
|
+
--cache-dir <path> Installer download cache directory
|
|
3415
|
+
--install-dir <path> Portable Studio server release directory
|
|
3416
|
+
--download-only Download and verify without running the installer
|
|
3417
|
+
--silent Run the NSIS installer with /S
|
|
3418
|
+
--exe <path> Tapi Studio executable path for open/doctor
|
|
2932
3419
|
`);
|
|
2933
3420
|
}
|
|
2934
3421
|
function printWorkspaceHelp(command) {
|
|
2935
|
-
console.log(`Tapi workspace ${command}
|
|
2936
|
-
|
|
2937
|
-
Usage:
|
|
2938
|
-
tapi ${command} --project PROJECT [--api-base-url URL] [--root PATH] [--force]
|
|
2939
|
-
|
|
2940
|
-
Options:
|
|
2941
|
-
--project <id> Tapi project id/name to bind this repo to
|
|
2942
|
-
--project-slug <slug> Optional display slug
|
|
2943
|
-
--api-base-url <url> Tapi API base URL stored in .tapi/project.json
|
|
2944
|
-
--server <url> Alias for --api-base-url
|
|
2945
|
-
--root <path> Directory where .tapi/project.json should be written
|
|
2946
|
-
--force Overwrite an existing workspace config
|
|
3422
|
+
console.log(`Tapi workspace ${command}
|
|
3423
|
+
|
|
3424
|
+
Usage:
|
|
3425
|
+
tapi ${command} --project PROJECT [--api-base-url URL] [--root PATH] [--force]
|
|
3426
|
+
|
|
3427
|
+
Options:
|
|
3428
|
+
--project <id> Tapi project id/name to bind this repo to
|
|
3429
|
+
--project-slug <slug> Optional display slug
|
|
3430
|
+
--api-base-url <url> Tapi API base URL stored in .tapi/project.json
|
|
3431
|
+
--server <url> Alias for --api-base-url
|
|
3432
|
+
--root <path> Directory where .tapi/project.json should be written
|
|
3433
|
+
--force Overwrite an existing workspace config
|
|
2947
3434
|
`);
|
|
2948
3435
|
}
|
|
2949
3436
|
function printServiceHelp() {
|
|
2950
|
-
console.log(`Tapi service commands
|
|
2951
|
-
|
|
2952
|
-
Usage:
|
|
2953
|
-
tapi service status
|
|
2954
|
-
tapi service start
|
|
2955
|
-
tapi service stop
|
|
2956
|
-
tapi service restart
|
|
2957
|
-
tapi service repair
|
|
2958
|
-
|
|
2959
|
-
Commands:
|
|
2960
|
-
status Print installed/running status for tapi-service
|
|
2961
|
-
start Start tapi-service
|
|
2962
|
-
stop Stop tapi-service
|
|
2963
|
-
restart Restart tapi-service
|
|
2964
|
-
repair Restart tapi-service using the current installed service
|
|
3437
|
+
console.log(`Tapi service commands
|
|
3438
|
+
|
|
3439
|
+
Usage:
|
|
3440
|
+
tapi service status
|
|
3441
|
+
tapi service start
|
|
3442
|
+
tapi service stop
|
|
3443
|
+
tapi service restart
|
|
3444
|
+
tapi service repair
|
|
3445
|
+
|
|
3446
|
+
Commands:
|
|
3447
|
+
status Print installed/running status for tapi-service
|
|
3448
|
+
start Start tapi-service
|
|
3449
|
+
stop Stop tapi-service
|
|
3450
|
+
restart Restart tapi-service
|
|
3451
|
+
repair Restart tapi-service using the current installed service
|
|
2965
3452
|
`);
|
|
2966
3453
|
}
|
|
2967
3454
|
function printPublishHelp() {
|
|
2968
|
-
console.log(`Tapi publish
|
|
2969
|
-
|
|
2970
|
-
Usage:
|
|
2971
|
-
tapi publish [--api-base-url URL] [--api-key KEY] [--project PROJECT]
|
|
2972
|
-
|
|
2973
|
-
Publishes ready generated API requests from local .tapi/apis files.
|
|
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.
|
|
2974
3461
|
`);
|
|
2975
3462
|
}
|
|
2976
3463
|
function formatError(error) {
|