@hitch42/cli 0.1.1-beta.7 → 0.1.1-beta.9
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/dist/hitch.js +87 -106
- package/package.json +2 -2
package/dist/hitch.js
CHANGED
|
@@ -55,8 +55,8 @@ async function healCredentialStore(credentialsPath, source, store, unrecognizedC
|
|
|
55
55
|
console.error(unrecognizedCount === void 0 ? `hitch: reset unreadable credentials file (backup: ${backupPath}).` : unrecognizedCount === 0 ? `hitch: migrated credentials to per-organization storage (backup: ${backupPath}).` : `hitch: removed ${unrecognizedCount} unrecognized credential ${unrecognizedCount === 1 ? "entry" : "entries"} (backup: ${backupPath}).`);
|
|
56
56
|
return store;
|
|
57
57
|
}
|
|
58
|
-
async function readRawCredentialStore(
|
|
59
|
-
const { credentialsPath } = storePaths(
|
|
58
|
+
async function readRawCredentialStore() {
|
|
59
|
+
const { credentialsPath } = storePaths();
|
|
60
60
|
let source;
|
|
61
61
|
try {
|
|
62
62
|
source = await readFile(credentialsPath, "utf8");
|
|
@@ -123,9 +123,9 @@ function organizationInstanceUrl(instanceUrl, orgCode) {
|
|
|
123
123
|
url.hostname = [orgCode, ...labels.slice(1)].join(".");
|
|
124
124
|
return url.toString().replace(/\/$/, "");
|
|
125
125
|
}
|
|
126
|
-
function storePaths(
|
|
127
|
-
const env =
|
|
128
|
-
const home =
|
|
126
|
+
function storePaths() {
|
|
127
|
+
const env = process.env;
|
|
128
|
+
const home = homedir();
|
|
129
129
|
const configDir = join(env.XDG_CONFIG_HOME || join(home, ".config"), "hitch");
|
|
130
130
|
return {
|
|
131
131
|
configDir,
|
|
@@ -134,25 +134,25 @@ function storePaths(options = {}) {
|
|
|
134
134
|
credentialsPath: join(configDir, "credentials.json")
|
|
135
135
|
};
|
|
136
136
|
}
|
|
137
|
-
async function resolveInstanceUrl(override
|
|
137
|
+
async function resolveInstanceUrl(override) {
|
|
138
138
|
if (override !== void 0) return normalizeInstanceUrl(override);
|
|
139
|
-
const envInstanceUrl =
|
|
139
|
+
const envInstanceUrl = process.env.HITCH_API_URL;
|
|
140
140
|
if (envInstanceUrl !== void 0 && envInstanceUrl.length > 0) return normalizeInstanceUrl(envInstanceUrl);
|
|
141
|
-
const { configPath } = storePaths(
|
|
141
|
+
const { configPath } = storePaths();
|
|
142
142
|
const config = await readJson(configPath);
|
|
143
143
|
if (config === void 0) return DEFAULT_INSTANCE_URL;
|
|
144
144
|
if (!isObject$2(config) || config.instanceUrl !== void 0 && typeof config.instanceUrl !== "string") throw new Error(`${configPath}: expected { instanceUrl?: string }.`);
|
|
145
145
|
return normalizeInstanceUrl(config.instanceUrl ?? "https://app.hitch42.com");
|
|
146
146
|
}
|
|
147
|
-
async function readCredentialStore(
|
|
148
|
-
const store = await readRawCredentialStore(
|
|
147
|
+
async function readCredentialStore() {
|
|
148
|
+
const store = await readRawCredentialStore();
|
|
149
149
|
return { instances: Object.fromEntries(Object.entries(store.instances).map(([instanceUrl, credentialsByOrg]) => [instanceUrl, Object.fromEntries(Object.entries(credentialsByOrg).flatMap(([orgCode, value]) => {
|
|
150
150
|
const credentials = parseCredentials(value);
|
|
151
151
|
return credentials === void 0 ? [] : [[orgCode, credentials]];
|
|
152
152
|
}))])) };
|
|
153
153
|
}
|
|
154
|
-
function environmentAuth(
|
|
155
|
-
const apiKey =
|
|
154
|
+
function environmentAuth() {
|
|
155
|
+
const apiKey = process.env.HITCH_API_KEY;
|
|
156
156
|
return apiKey !== void 0 && apiKey.length > 0 ? {
|
|
157
157
|
accessToken: apiKey,
|
|
158
158
|
source: "environment"
|
|
@@ -176,12 +176,12 @@ function resolveStoredAuthForOrg(credentialsByOrg, orgCode) {
|
|
|
176
176
|
const apiKeyOrgs = Object.values(credentialsByOrg).filter((credentials) => credentials.type === "apiKey").map(({ orgCode: storedOrg }) => storedOrg).sort();
|
|
177
177
|
if (apiKeyOrgs.length > 0) throw new AuthenticationError(`Stored API ${apiKeyOrgs.length === 1 ? "key is" : "keys are"} scoped to ${apiKeyOrgs.map((storedOrg) => `"${storedOrg}"`).join(", ")}, not "${orgCode}".`);
|
|
178
178
|
}
|
|
179
|
-
async function resolveAuth(instanceUrl,
|
|
180
|
-
const auth = environmentAuth(
|
|
179
|
+
async function resolveAuth(instanceUrl, orgCode) {
|
|
180
|
+
const auth = environmentAuth();
|
|
181
181
|
if (auth !== void 0) return auth;
|
|
182
182
|
const normalizedUrl = normalizeInstanceUrl(instanceUrl);
|
|
183
|
-
const credentialsByOrg = (await readCredentialStore(
|
|
184
|
-
const selectedOrg = orgCode ??
|
|
183
|
+
const credentialsByOrg = (await readCredentialStore()).instances[normalizedUrl] ?? {};
|
|
184
|
+
const selectedOrg = orgCode ?? process.env.HITCH_DEFAULT_ORG;
|
|
185
185
|
if (selectedOrg !== void 0 && selectedOrg.length > 0) return resolveStoredAuthForOrg(credentialsByOrg, selectedOrg);
|
|
186
186
|
const orgCodes = Object.keys(credentialsByOrg).sort();
|
|
187
187
|
const soleOrg = orgCodes.at(0);
|
|
@@ -195,9 +195,9 @@ function rejectEnvironmentKey(response, auth) {
|
|
|
195
195
|
function bearerToken(credentials) {
|
|
196
196
|
return "apiKey" in credentials ? credentials.apiKey : credentials.accessToken;
|
|
197
197
|
}
|
|
198
|
-
async function setCredentials(instanceUrl, credentials
|
|
199
|
-
const paths = storePaths(
|
|
200
|
-
const store = await readRawCredentialStore(
|
|
198
|
+
async function setCredentials(instanceUrl, credentials) {
|
|
199
|
+
const paths = storePaths();
|
|
200
|
+
const store = await readRawCredentialStore();
|
|
201
201
|
const normalizedUrl = normalizeInstanceUrl(instanceUrl);
|
|
202
202
|
const credentialsByOrg = store.instances[normalizedUrl];
|
|
203
203
|
store.instances[normalizedUrl] = {
|
|
@@ -208,9 +208,9 @@ async function setCredentials(instanceUrl, credentials, options = {}) {
|
|
|
208
208
|
await writeFile(paths.credentialsPath, `${JSON.stringify(store, null, 2)}\n`, { mode: 384 });
|
|
209
209
|
await chmod(paths.credentialsPath, 384);
|
|
210
210
|
}
|
|
211
|
-
async function deleteCredentials(instanceUrl, orgCode
|
|
212
|
-
const paths = storePaths(
|
|
213
|
-
const store = await readRawCredentialStore(
|
|
211
|
+
async function deleteCredentials(instanceUrl, orgCode) {
|
|
212
|
+
const paths = storePaths();
|
|
213
|
+
const store = await readRawCredentialStore();
|
|
214
214
|
const normalizedUrl = normalizeInstanceUrl(instanceUrl);
|
|
215
215
|
if (orgCode === void 0) delete store.instances[normalizedUrl];
|
|
216
216
|
else {
|
|
@@ -232,17 +232,17 @@ function instanceSlug(instanceUrl) {
|
|
|
232
232
|
const url = new URL(normalizeInstanceUrl(instanceUrl));
|
|
233
233
|
return `${url.protocol.slice(0, -1)}-${url.host}${url.pathname}`.replace(/[^a-zA-Z0-9.-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
234
234
|
}
|
|
235
|
-
function instanceCacheDir(instanceUrl
|
|
236
|
-
return join(storePaths(
|
|
235
|
+
function instanceCacheDir(instanceUrl) {
|
|
236
|
+
return join(storePaths().cacheDir, instanceSlug(instanceUrl));
|
|
237
237
|
}
|
|
238
|
-
function contractCacheDir(instanceUrl, organizationId
|
|
239
|
-
return join(instanceCacheDir(instanceUrl
|
|
238
|
+
function contractCacheDir(instanceUrl, organizationId) {
|
|
239
|
+
return join(instanceCacheDir(instanceUrl), organizationId);
|
|
240
240
|
}
|
|
241
|
-
function adminContractCacheDir(instanceUrl, organizationId
|
|
242
|
-
return join(contractCacheDir(instanceUrl, organizationId
|
|
241
|
+
function adminContractCacheDir(instanceUrl, organizationId) {
|
|
242
|
+
return join(contractCacheDir(instanceUrl, organizationId), "admin");
|
|
243
243
|
}
|
|
244
|
-
async function deleteInstanceCache(instanceUrl
|
|
245
|
-
await rm(instanceCacheDir(instanceUrl
|
|
244
|
+
async function deleteInstanceCache(instanceUrl) {
|
|
245
|
+
await rm(instanceCacheDir(instanceUrl), {
|
|
246
246
|
recursive: true,
|
|
247
247
|
force: true
|
|
248
248
|
});
|
|
@@ -365,17 +365,17 @@ async function authWithOrganization(fetcher, instanceUrl, auth, expectedOrg) {
|
|
|
365
365
|
};
|
|
366
366
|
}
|
|
367
367
|
async function loginWithDevice(instanceUrl, options = {}) {
|
|
368
|
-
const fetcher =
|
|
369
|
-
const now =
|
|
368
|
+
const fetcher = globalThis.fetch;
|
|
369
|
+
const now = Date.now;
|
|
370
370
|
const { body, response } = await postJson(fetcher, `${instanceUrl}/api/auth/device/code`, { client_id: CLIENT_ID });
|
|
371
371
|
if (!response.ok) throw new AuthenticationError(typeof body.error_description === "string" ? body.error_description : `Could not start sign-in (${response.status}).`);
|
|
372
372
|
const device = parseDeviceCode(body);
|
|
373
373
|
const verificationUrl = options.expectedOrg === void 0 ? device.verification_uri_complete : `${device.verification_uri_complete}&org=${encodeURIComponent(options.expectedOrg)}`;
|
|
374
|
-
|
|
374
|
+
console.log(`Open ${verificationUrl} and confirm code ${device.user_code}.`);
|
|
375
375
|
if (!options.noBrowser) try {
|
|
376
|
-
|
|
376
|
+
defaultOpenUrl(verificationUrl);
|
|
377
377
|
} catch {}
|
|
378
|
-
const token = await pollForToken(fetcher, instanceUrl, device, now,
|
|
378
|
+
const token = await pollForToken(fetcher, instanceUrl, device, now, (milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
379
379
|
const { body: organizationBody, response: organizationResponse } = await postJson(fetcher, `${instanceUrl}/api/auth/device/organization`, { device_code: device.device_code });
|
|
380
380
|
if (!organizationResponse.ok) throw new AuthenticationError("The sign-in did not resolve an organization.");
|
|
381
381
|
const organization = parseDeviceOrganization(organizationBody);
|
|
@@ -393,7 +393,7 @@ async function loginWithDevice(instanceUrl, options = {}) {
|
|
|
393
393
|
organizationId: organization.organizationId,
|
|
394
394
|
orgCode: organization.orgCode
|
|
395
395
|
};
|
|
396
|
-
await setCredentials(instanceUrl, credentials
|
|
396
|
+
await setCredentials(instanceUrl, credentials);
|
|
397
397
|
if (options.expectedOrg !== void 0 && organization.orgCode !== options.expectedOrg) throw new AuthenticationError(`logged in to "${organization.orgCode}" but this applet targets "${options.expectedOrg}" — confirm the right organization on the device page`);
|
|
398
398
|
return {
|
|
399
399
|
credentials,
|
|
@@ -416,9 +416,12 @@ function readWorkspaceConfig(dir) {
|
|
|
416
416
|
}
|
|
417
417
|
const config = module !== null && typeof module === "object" && "default" in module ? module.default : module;
|
|
418
418
|
if (config === null || typeof config !== "object" || !("sdk" in config) || typeof config.sdk !== "string" || config.sdk.length === 0 || !("org" in config) || typeof config.org !== "string" || !ORG_CODE_PATTERN.test(config.org)) throw new Error(`Invalid Hitch config in ${path}: "sdk" must be a package spec and "org" an organization code.`);
|
|
419
|
+
const githubWorkflow = "githubWorkflow" in config ? config.githubWorkflow : void 0;
|
|
420
|
+
if (githubWorkflow !== void 0 && typeof githubWorkflow !== "boolean") throw new Error(`Invalid Hitch config in ${path}: "githubWorkflow" must be a boolean.`);
|
|
419
421
|
return {
|
|
420
422
|
sdk: config.sdk,
|
|
421
|
-
org: config.org
|
|
423
|
+
org: config.org,
|
|
424
|
+
githubWorkflow
|
|
422
425
|
};
|
|
423
426
|
}
|
|
424
427
|
function findWorkspaceConfig(cwd) {
|
|
@@ -517,7 +520,7 @@ function createBridgeRouter(contract, options) {
|
|
|
517
520
|
url: `${options.instanceUrl}/api`,
|
|
518
521
|
headers: { Authorization: `Bearer ${bearerToken(options.auth)}` },
|
|
519
522
|
fetch: async (request, init) => {
|
|
520
|
-
const response = await
|
|
523
|
+
const response = await globalThis.fetch(request, init);
|
|
521
524
|
if ("source" in options.auth) rejectEnvironmentKey(response, options.auth);
|
|
522
525
|
return response;
|
|
523
526
|
}
|
|
@@ -532,9 +535,9 @@ function createBridgeRouter(contract, options) {
|
|
|
532
535
|
const description = node["~orpc"].route.summary ?? node["~orpc"].route.description;
|
|
533
536
|
const procedure = os.meta(description ? { description } : {}).input(cliInputSchema(inputSchema)).handler(async ({ input }) => {
|
|
534
537
|
if (isDelete && !input.yes) {
|
|
535
|
-
if (
|
|
538
|
+
if (process.stdin.isTTY !== true) throw new Error("Refusing to delete without --yes in non-interactive mode.");
|
|
536
539
|
const values = Object.entries(input).filter(([key, value]) => key !== "yes" && value !== void 0).map(([key, value]) => `${key}=${String(value)}`).join(" ");
|
|
537
|
-
const answer = await
|
|
540
|
+
const answer = await defaultPrompt$1(`Confirm ${path.join(" ")}${values.length === 0 ? "" : ` ${values}`}? [y/N] `);
|
|
538
541
|
if (!["y", "yes"].includes(answer.trim().toLowerCase())) throw new Error("Aborted.");
|
|
539
542
|
}
|
|
540
543
|
const detailed = Object.fromEntries(sections.flatMap((section) => {
|
|
@@ -553,7 +556,7 @@ function createBridgeRouter(contract, options) {
|
|
|
553
556
|
}
|
|
554
557
|
//#endregion
|
|
555
558
|
//#region src/generate.ts
|
|
556
|
-
const RECORD_ROUTE = /^\/v1\/([^/]+)(?:\/(\{id\}
|
|
559
|
+
const RECORD_ROUTE = /^\/v1\/([^/]+)(?:\/(\{id\}(?:\/history)?|query|update|aggregate))?$/;
|
|
557
560
|
const BATCH_PATH = "/v1/batch";
|
|
558
561
|
const BATCH_ITEM_PATH = "paths./v1/batch.post.requestBody.content.application/json.schema.properties.requests.items";
|
|
559
562
|
const BATCH_ID_PATH = /^\^\/v1\/([^/]+)\/\[0-9a-fA-F-\]\{36\}\$$/;
|
|
@@ -561,6 +564,7 @@ const VERBS = {
|
|
|
561
564
|
"get:": "list",
|
|
562
565
|
"post:": "create",
|
|
563
566
|
"get:{id}": "get",
|
|
567
|
+
"get:{id}/history": "history",
|
|
564
568
|
"patch:{id}": "update",
|
|
565
569
|
"delete:{id}": "delete",
|
|
566
570
|
"post:query": "query",
|
|
@@ -670,7 +674,7 @@ function prepareRecordSpec(spec) {
|
|
|
670
674
|
const prepared = flattenTrivialAllOf(cloned, isObject$1(cloned.components) && isObject$1(cloned.components.schemas) ? cloned.components.schemas : {});
|
|
671
675
|
prepared.paths = Object.fromEntries(Object.entries(prepared.paths).flatMap(([path, pathItem]) => {
|
|
672
676
|
if (path === BATCH_PATH) {
|
|
673
|
-
if (!isObject$1(pathItem)
|
|
677
|
+
if (!isObject$1(pathItem)) return [];
|
|
674
678
|
const operation = prepareBatchOperation(pathItem.post);
|
|
675
679
|
return operation === void 0 ? [] : [[path, { post: operation }]];
|
|
676
680
|
}
|
|
@@ -798,14 +802,14 @@ function objectCount(spec) {
|
|
|
798
802
|
return operation ? [operation.objectCode] : [];
|
|
799
803
|
}) : [])).size;
|
|
800
804
|
}
|
|
801
|
-
async function syncContractCache(instanceUrl, credentials
|
|
805
|
+
async function syncContractCache(instanceUrl, credentials) {
|
|
802
806
|
if (credentials.organizationId === void 0) throw new Error("Stored credentials do not include an organization. Run hitch login again.");
|
|
803
|
-
const response = await
|
|
807
|
+
const response = await globalThis.fetch(`${instanceUrl}/api/v1/openapi.json`, { headers: { Authorization: `Bearer ${bearerToken(credentials)}` } });
|
|
804
808
|
if ("source" in credentials) rejectEnvironmentKey(response, credentials);
|
|
805
809
|
if (!response.ok) throw new Error(`Failed to fetch the organization contract (${response.status} ${response.statusText}).`);
|
|
806
810
|
const prepared = prepareRecordSpec(await response.json());
|
|
807
811
|
const count = objectCount(prepared);
|
|
808
|
-
const outDir = contractCacheDir(instanceUrl, credentials.organizationId
|
|
812
|
+
const outDir = contractCacheDir(instanceUrl, credentials.organizationId);
|
|
809
813
|
await mkdir(outDir, { recursive: true });
|
|
810
814
|
await Promise.all((await readdir(outDir)).flatMap((name) => name === "admin" ? [] : [rm(resolve(outDir, name), {
|
|
811
815
|
recursive: true,
|
|
@@ -818,16 +822,16 @@ async function syncContractCache(instanceUrl, credentials, options = {}) {
|
|
|
818
822
|
}, null, 2)}\n`);
|
|
819
823
|
return count;
|
|
820
824
|
}
|
|
821
|
-
async function syncAdminContractCache(instanceUrl, credentials
|
|
825
|
+
async function syncAdminContractCache(instanceUrl, credentials) {
|
|
822
826
|
if (credentials.organizationId === void 0) throw new Error("Stored credentials do not include an organization. Run hitch login again.");
|
|
823
|
-
const outDir = adminContractCacheDir(instanceUrl, credentials.organizationId
|
|
827
|
+
const outDir = adminContractCacheDir(instanceUrl, credentials.organizationId);
|
|
824
828
|
let response;
|
|
825
829
|
try {
|
|
826
|
-
response = await
|
|
830
|
+
response = await globalThis.fetch(`${instanceUrl}/api/admin/v1/openapi.json`, { headers: { Authorization: `Bearer ${bearerToken(credentials)}` } });
|
|
827
831
|
if ("source" in credentials) rejectEnvironmentKey(response, credentials);
|
|
828
832
|
} catch (error) {
|
|
829
833
|
if (error instanceof AuthenticationError) throw error;
|
|
830
|
-
|
|
834
|
+
console.error(`Warning: failed to probe admin commands: ${error instanceof Error ? error.message : String(error)}`);
|
|
831
835
|
return false;
|
|
832
836
|
}
|
|
833
837
|
if (response.status === 401 || response.status === 403) {
|
|
@@ -838,7 +842,7 @@ async function syncAdminContractCache(instanceUrl, credentials, options = {}) {
|
|
|
838
842
|
return false;
|
|
839
843
|
}
|
|
840
844
|
if (!response.ok) {
|
|
841
|
-
|
|
845
|
+
console.error(`Warning: failed to probe admin commands (${response.status} ${response.statusText}).`);
|
|
842
846
|
return false;
|
|
843
847
|
}
|
|
844
848
|
const prepared = prepareAdminSpec(await response.json());
|
|
@@ -864,32 +868,31 @@ async function defaultPrompt(message) {
|
|
|
864
868
|
readline.close();
|
|
865
869
|
}
|
|
866
870
|
}
|
|
867
|
-
function rejectEnvironmentCredentialManagement(
|
|
868
|
-
if (
|
|
871
|
+
function rejectEnvironmentCredentialManagement() {
|
|
872
|
+
if (process.env.HITCH_API_KEY !== void 0) throw new AuthenticationError("HITCH_API_KEY is set — unset it to manage stored logins.");
|
|
869
873
|
}
|
|
870
874
|
function requireAuth(auth) {
|
|
871
875
|
if (auth === void 0) throw new NotLoggedInError("Not logged in. Run hitch login.");
|
|
872
876
|
return auth;
|
|
873
877
|
}
|
|
874
|
-
function createStaticRouter(activeInstanceUrl,
|
|
875
|
-
const fetcher =
|
|
876
|
-
const
|
|
877
|
-
const
|
|
878
|
-
const warn = dependencies.warn ?? console.error;
|
|
878
|
+
function createStaticRouter(activeInstanceUrl, selectedOrg) {
|
|
879
|
+
const fetcher = globalThis.fetch;
|
|
880
|
+
const print = console.log;
|
|
881
|
+
const warn = console.error;
|
|
879
882
|
return os.router({
|
|
880
883
|
login: os.meta({ description: "Sign in to a Hitch organization." }).input(z.object({
|
|
881
884
|
apiKey: z.union([z.string(), z.boolean()]).default(false).describe("Store an API key, prompting when no value is given"),
|
|
882
885
|
noBrowser: z.boolean().default(false).describe("Do not open a browser"),
|
|
883
886
|
instance: z.string().optional().describe("Hitch instance URL")
|
|
884
887
|
})).handler(async ({ input }) => {
|
|
885
|
-
rejectEnvironmentCredentialManagement(
|
|
888
|
+
rejectEnvironmentCredentialManagement();
|
|
886
889
|
if (input.apiKey !== false && input.noBrowser) throw new AuthenticationError("--api-key and --no-browser cannot be used together.");
|
|
887
|
-
const instanceUrl = input.instance === void 0 ? activeInstanceUrl : await resolveInstanceUrl(input.instance
|
|
890
|
+
const instanceUrl = input.instance === void 0 ? activeInstanceUrl : await resolveInstanceUrl(input.instance);
|
|
888
891
|
let me;
|
|
889
892
|
let membership;
|
|
890
893
|
let storedCredentials;
|
|
891
894
|
if (input.apiKey !== false) {
|
|
892
|
-
const apiKey = typeof input.apiKey === "string" ? input.apiKey : await
|
|
895
|
+
const apiKey = typeof input.apiKey === "string" ? input.apiKey : await defaultPrompt("Paste your API key: ");
|
|
893
896
|
if (apiKey.length === 0) throw new AuthenticationError("API key cannot be empty.");
|
|
894
897
|
me = await fetchMe(fetcher, instanceUrl, {
|
|
895
898
|
accessToken: apiKey,
|
|
@@ -908,37 +911,24 @@ function createStaticRouter(activeInstanceUrl, dependencies = {}, selectedOrg) {
|
|
|
908
911
|
organizationId: organization.id,
|
|
909
912
|
orgCode: organization.code
|
|
910
913
|
};
|
|
911
|
-
await setCredentials(instanceUrl, storedCredentials
|
|
914
|
+
await setCredentials(instanceUrl, storedCredentials);
|
|
912
915
|
} else {
|
|
913
916
|
const login = await loginWithDevice(instanceUrl, {
|
|
914
917
|
expectedOrg: selectedOrg,
|
|
915
|
-
|
|
916
|
-
noBrowser: input.noBrowser,
|
|
917
|
-
now,
|
|
918
|
-
openUrl: dependencies.openUrl,
|
|
919
|
-
print,
|
|
920
|
-
sleep: dependencies.sleep,
|
|
921
|
-
store: dependencies.store
|
|
918
|
+
noBrowser: input.noBrowser
|
|
922
919
|
});
|
|
923
920
|
me = login.me;
|
|
924
921
|
membership = login.membership;
|
|
925
922
|
storedCredentials = login.credentials;
|
|
926
923
|
}
|
|
927
924
|
const organizationUrl = organizationInstanceUrl(instanceUrl, storedCredentials.orgCode);
|
|
928
|
-
await syncContractCache(organizationUrl, storedCredentials
|
|
929
|
-
|
|
930
|
-
store: dependencies.store
|
|
931
|
-
});
|
|
932
|
-
await syncAdminContractCache(organizationUrl, storedCredentials, {
|
|
933
|
-
fetch: fetcher,
|
|
934
|
-
store: dependencies.store,
|
|
935
|
-
warn
|
|
936
|
-
});
|
|
925
|
+
await syncContractCache(organizationUrl, storedCredentials);
|
|
926
|
+
await syncAdminContractCache(organizationUrl, storedCredentials);
|
|
937
927
|
print(`Signed in as ${me.person.email} to ${membership.name}${storedCredentials.type === "apiKey" ? " (API key)" : ""}.`);
|
|
938
928
|
}),
|
|
939
929
|
logout: os.meta({ description: "Sign out and remove locally stored credentials." }).input(z.object({})).handler(async () => {
|
|
940
|
-
rejectEnvironmentCredentialManagement(
|
|
941
|
-
const credentials = Object.values((await readCredentialStore(
|
|
930
|
+
rejectEnvironmentCredentialManagement();
|
|
931
|
+
const credentials = Object.values((await readCredentialStore()).instances[normalizeInstanceUrl(activeInstanceUrl)] ?? {});
|
|
942
932
|
await Promise.all(credentials.filter((credential) => credential.type === "session").map(async (credential) => {
|
|
943
933
|
try {
|
|
944
934
|
const response = await fetcher(`${activeInstanceUrl}/api/auth/sign-out`, {
|
|
@@ -951,18 +941,18 @@ function createStaticRouter(activeInstanceUrl, dependencies = {}, selectedOrg) {
|
|
|
951
941
|
}
|
|
952
942
|
}));
|
|
953
943
|
const cacheUrls = [activeInstanceUrl, ...credentials.map((credential) => organizationInstanceUrl(activeInstanceUrl, credential.orgCode))];
|
|
954
|
-
await Promise.all([deleteCredentials(activeInstanceUrl
|
|
944
|
+
await Promise.all([deleteCredentials(activeInstanceUrl), ...[...new Set(cacheUrls)].map((url) => deleteInstanceCache(url))]);
|
|
955
945
|
print(`Logged out of ${activeInstanceUrl}.`);
|
|
956
946
|
}),
|
|
957
947
|
openapi: os.meta({ description: "Print the organization's OpenAPI spec as JSON (used by applet generate)" }).input(z.object({})).handler(async () => {
|
|
958
|
-
const auth = await authWithOrganization(fetcher, activeInstanceUrl, requireAuth(await resolveAuth(activeInstanceUrl,
|
|
948
|
+
const auth = await authWithOrganization(fetcher, activeInstanceUrl, requireAuth(await resolveAuth(activeInstanceUrl, selectedOrg)), selectedOrg);
|
|
959
949
|
const response = await fetcher(`${organizationInstanceUrl(activeInstanceUrl, auth.orgCode)}/api/v1/openapi.json`, { headers: { Authorization: `Bearer ${auth.accessToken}` } });
|
|
960
950
|
rejectEnvironmentKey(response, auth);
|
|
961
951
|
if (!response.ok) throw new AuthenticationError(`Failed to load the organization's OpenAPI spec (${response.status}).`);
|
|
962
952
|
process.stdout.write(`${JSON.stringify(await response.json(), null, 2)}\n`);
|
|
963
953
|
}),
|
|
964
954
|
whoami: os.meta({ description: "Show the current Hitch identity and organization." }).input(z.object({})).handler(async () => {
|
|
965
|
-
const auth = await authWithOrganization(fetcher, activeInstanceUrl, requireAuth(await resolveAuth(activeInstanceUrl,
|
|
955
|
+
const auth = await authWithOrganization(fetcher, activeInstanceUrl, requireAuth(await resolveAuth(activeInstanceUrl, selectedOrg)), selectedOrg);
|
|
966
956
|
const me = await fetchMe(fetcher, organizationInstanceUrl(activeInstanceUrl, auth.orgCode), auth);
|
|
967
957
|
const membership = me.memberships.find(({ orgId }) => orgId === auth.organizationId);
|
|
968
958
|
if (membership === void 0) throw new AuthenticationError(`The current identity is not a member of organization "${auth.orgCode}".`);
|
|
@@ -975,17 +965,10 @@ function createStaticRouter(activeInstanceUrl, dependencies = {}, selectedOrg) {
|
|
|
975
965
|
};
|
|
976
966
|
}),
|
|
977
967
|
sync: os.meta({ description: "Refresh the cached commands for the current organization." }).input(z.object({})).handler(async () => {
|
|
978
|
-
const auth = await authWithOrganization(fetcher, activeInstanceUrl, requireAuth(await resolveAuth(activeInstanceUrl,
|
|
968
|
+
const auth = await authWithOrganization(fetcher, activeInstanceUrl, requireAuth(await resolveAuth(activeInstanceUrl, selectedOrg)), selectedOrg);
|
|
979
969
|
const organizationUrl = organizationInstanceUrl(activeInstanceUrl, auth.orgCode);
|
|
980
|
-
const count = await syncContractCache(organizationUrl, auth
|
|
981
|
-
|
|
982
|
-
store: dependencies.store
|
|
983
|
-
});
|
|
984
|
-
await syncAdminContractCache(organizationUrl, auth, {
|
|
985
|
-
fetch: fetcher,
|
|
986
|
-
store: dependencies.store,
|
|
987
|
-
warn
|
|
988
|
-
});
|
|
970
|
+
const count = await syncContractCache(organizationUrl, auth);
|
|
971
|
+
await syncAdminContractCache(organizationUrl, auth);
|
|
989
972
|
print(`Synced ${count} object${count === 1 ? "" : "s"}.`);
|
|
990
973
|
})
|
|
991
974
|
});
|
|
@@ -1067,28 +1050,26 @@ async function loadContract(path, warn) {
|
|
|
1067
1050
|
if (module.contract === null || typeof module.contract !== "object") throw new Error(`${path}: generated contract export is missing.`);
|
|
1068
1051
|
return module.contract;
|
|
1069
1052
|
}
|
|
1070
|
-
async function buildRouter(
|
|
1071
|
-
const instanceUrl = await resolveInstanceUrl(
|
|
1072
|
-
const selectedOrg = resolveOrganization(options.orgCode, process.cwd(),
|
|
1073
|
-
const staticRouter = createStaticRouter(instanceUrl,
|
|
1053
|
+
async function buildRouter(options = {}) {
|
|
1054
|
+
const instanceUrl = await resolveInstanceUrl();
|
|
1055
|
+
const selectedOrg = resolveOrganization(options.orgCode, process.cwd(), process.env);
|
|
1056
|
+
const staticRouter = createStaticRouter(instanceUrl, selectedOrg);
|
|
1074
1057
|
if (options.staticOnly) return staticRouter;
|
|
1075
1058
|
let resolved;
|
|
1076
1059
|
try {
|
|
1077
|
-
resolved = await resolveAuth(instanceUrl,
|
|
1060
|
+
resolved = await resolveAuth(instanceUrl, selectedOrg);
|
|
1078
1061
|
} catch (error) {
|
|
1079
|
-
|
|
1062
|
+
console.error(`hitch: ${error instanceof Error ? error.message : String(error)}`);
|
|
1080
1063
|
return staticRouter;
|
|
1081
1064
|
}
|
|
1082
1065
|
if (resolved === void 0) return staticRouter;
|
|
1083
|
-
const auth = await authWithOrganization(
|
|
1084
|
-
const warn =
|
|
1066
|
+
const auth = await authWithOrganization(globalThis.fetch, instanceUrl, resolved, selectedOrg);
|
|
1067
|
+
const warn = console.error;
|
|
1085
1068
|
const organizationUrl = organizationInstanceUrl(instanceUrl, auth.orgCode);
|
|
1086
|
-
const [contract, adminContract] = await Promise.all([loadContract(resolve(contractCacheDir(organizationUrl, auth.organizationId
|
|
1069
|
+
const [contract, adminContract] = await Promise.all([loadContract(resolve(contractCacheDir(organizationUrl, auth.organizationId), "orpc.gen.ts"), warn), loadContract(resolve(adminContractCacheDir(organizationUrl, auth.organizationId), "orpc.gen.ts"), warn)]);
|
|
1087
1070
|
const bridgeOptions = {
|
|
1088
1071
|
instanceUrl: organizationUrl,
|
|
1089
|
-
auth
|
|
1090
|
-
fetch: dependencies.fetch,
|
|
1091
|
-
prompt: dependencies.prompt
|
|
1072
|
+
auth
|
|
1092
1073
|
};
|
|
1093
1074
|
const dynamicRouter = contract === void 0 ? {} : createBridgeRouter(contract, bridgeOptions);
|
|
1094
1075
|
const staticNamespaces = /* @__PURE__ */ new Set([...Object.keys(staticRouter), "admin"]);
|
|
@@ -1107,10 +1088,10 @@ async function buildRouter(dependencies = {}, options = {}) {
|
|
|
1107
1088
|
...staticRouter
|
|
1108
1089
|
});
|
|
1109
1090
|
}
|
|
1110
|
-
async function runCli(
|
|
1091
|
+
async function runCli(argv) {
|
|
1111
1092
|
const selected = extractOrganizationArg(argv ?? process.argv.slice(2));
|
|
1112
1093
|
const command = selected.argv.at(0);
|
|
1113
|
-
const router = await buildRouter(
|
|
1094
|
+
const router = await buildRouter({
|
|
1114
1095
|
orgCode: selected.orgCode,
|
|
1115
1096
|
staticOnly: [
|
|
1116
1097
|
"login",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hitch42/cli",
|
|
3
|
-
"version": "0.1.1-beta.
|
|
3
|
+
"version": "0.1.1-beta.9",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bin": {
|
|
6
6
|
"hitch": "./dist/hitch.js"
|
|
@@ -29,6 +29,6 @@
|
|
|
29
29
|
"publishConfig": {
|
|
30
30
|
"access": "public",
|
|
31
31
|
"registry": "https://registry.npmjs.org",
|
|
32
|
-
"tag": "
|
|
32
|
+
"tag": "latest"
|
|
33
33
|
}
|
|
34
34
|
}
|