@contextual-io/cli 0.1.1 → 0.1.3
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 +456 -1
- package/dist/commands/base.d.ts +41 -0
- package/dist/commands/base.js +164 -0
- package/dist/commands/config/add.d.ts +13 -0
- package/dist/commands/config/add.js +34 -0
- package/dist/commands/config/current.d.ts +10 -0
- package/dist/commands/config/current.js +15 -0
- package/dist/commands/config/delete.d.ts +10 -0
- package/dist/commands/config/delete.js +17 -0
- package/dist/commands/config/get.d.ts +12 -0
- package/dist/commands/config/get.js +25 -0
- package/dist/commands/config/list.d.ts +13 -0
- package/dist/commands/config/list.js +33 -0
- package/dist/commands/config/login.d.ts +8 -0
- package/dist/commands/config/login.js +21 -0
- package/dist/commands/config/use.d.ts +10 -0
- package/dist/commands/config/use.js +17 -0
- package/dist/commands/records/add.d.ts +14 -0
- package/dist/commands/records/add.js +73 -0
- package/dist/commands/records/delete.d.ts +13 -0
- package/dist/commands/records/delete.js +55 -0
- package/dist/commands/records/get.d.ts +13 -0
- package/dist/commands/records/get.js +53 -0
- package/dist/commands/records/list.d.ts +22 -0
- package/dist/commands/records/list.js +145 -0
- package/dist/commands/records/query.d.ts +19 -0
- package/dist/commands/records/query.js +134 -0
- package/dist/commands/records/replace.d.ts +14 -0
- package/dist/commands/records/replace.js +69 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/models/cli-configuration.d.ts +9 -0
- package/dist/models/cli-configuration.js +8 -0
- package/dist/models/errors.d.ts +5 -0
- package/dist/models/errors.js +14 -0
- package/dist/models/silo.d.ts +7 -0
- package/dist/models/silo.js +2 -0
- package/dist/models/uri.d.ts +7 -0
- package/dist/models/uri.js +7 -0
- package/dist/models/url-safe-id.d.ts +3 -0
- package/dist/models/url-safe-id.js +4 -0
- package/dist/models/user-config.d.ts +54 -0
- package/dist/models/user-config.js +24 -0
- package/dist/utils/auth.d.ts +6 -0
- package/dist/utils/auth.js +101 -0
- package/dist/utils/cli-configuration.d.ts +3 -0
- package/dist/utils/cli-configuration.js +19 -0
- package/dist/utils/endpoints.d.ts +3 -0
- package/dist/utils/endpoints.js +2 -0
- package/dist/utils/stream.d.ts +3 -0
- package/dist/utils/stream.js +21 -0
- package/dist/utils/time.d.ts +2 -0
- package/dist/utils/time.js +5 -0
- package/oclif.manifest.json +740 -2
- package/package.json +1 -1
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import fs, { createReadStream } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseUri } from "../../models/uri.js";
|
|
5
|
+
import { getNativeObjectApiEndpoint } from "../../utils/endpoints.js";
|
|
6
|
+
import { readStreamToEnd } from "../../utils/stream.js";
|
|
7
|
+
import { endTime, startTime } from "../../utils/time.js";
|
|
8
|
+
import { BaseCommand } from "../base.js";
|
|
9
|
+
export default class RecordsQuery extends BaseCommand {
|
|
10
|
+
static args = {
|
|
11
|
+
"uri": Args.string({ description: "uri of type" }),
|
|
12
|
+
};
|
|
13
|
+
static description = "query records";
|
|
14
|
+
static examples = [
|
|
15
|
+
"<%= config.bin %> <%= command.id %> native-object:my-type",
|
|
16
|
+
"<%= config.bin %> <%= command.id %> --type my-type --query-file query.json",
|
|
17
|
+
"<%= config.bin %> <%= command.id %> --type my-type --order-by field1:desc --query-file query.json",
|
|
18
|
+
"<%= config.bin %> <%= command.id %> --type my-type --include-total --query-file query.json",
|
|
19
|
+
];
|
|
20
|
+
static flags = {
|
|
21
|
+
export: Flags.boolean({
|
|
22
|
+
allowNo: false,
|
|
23
|
+
char: "e",
|
|
24
|
+
description: "export data as JSONL",
|
|
25
|
+
}),
|
|
26
|
+
"include-total": Flags.boolean({
|
|
27
|
+
allowNo: false,
|
|
28
|
+
description: "include total count",
|
|
29
|
+
}),
|
|
30
|
+
"order-by": Flags.string({
|
|
31
|
+
char: "o",
|
|
32
|
+
description: "order fields",
|
|
33
|
+
multiple: true,
|
|
34
|
+
}),
|
|
35
|
+
"page-size": Flags.integer({
|
|
36
|
+
description: "number of records per page",
|
|
37
|
+
max: 250,
|
|
38
|
+
min: 0,
|
|
39
|
+
}),
|
|
40
|
+
"page-token": Flags.string({
|
|
41
|
+
description: "page token to fetch",
|
|
42
|
+
}),
|
|
43
|
+
progress: Flags.boolean({
|
|
44
|
+
allowNo: false,
|
|
45
|
+
char: "p",
|
|
46
|
+
description: "show progress during export",
|
|
47
|
+
}),
|
|
48
|
+
"query-file": Flags.file({
|
|
49
|
+
char: "q",
|
|
50
|
+
default: "-",
|
|
51
|
+
description: "file to read. can read stdin if value is '-'",
|
|
52
|
+
}),
|
|
53
|
+
type: Flags.string({
|
|
54
|
+
char: "T",
|
|
55
|
+
description: "type",
|
|
56
|
+
}),
|
|
57
|
+
};
|
|
58
|
+
async run() {
|
|
59
|
+
const { uri } = this.args;
|
|
60
|
+
const { type: flagType } = this.flags;
|
|
61
|
+
let type = flagType;
|
|
62
|
+
if (uri) {
|
|
63
|
+
const { type: uriType } = parseUri(uri);
|
|
64
|
+
type = uriType;
|
|
65
|
+
}
|
|
66
|
+
if (!type) {
|
|
67
|
+
throw new Error("Type not provided");
|
|
68
|
+
}
|
|
69
|
+
const queryFile = this.flags["query-file"] === "-"
|
|
70
|
+
? "-"
|
|
71
|
+
: path.resolve(this.flags["query-file"]);
|
|
72
|
+
if (queryFile && queryFile !== "-" && !fs.existsSync(queryFile)) {
|
|
73
|
+
throw new Error("File does not exist");
|
|
74
|
+
}
|
|
75
|
+
const query = JSON.parse((await readStreamToEnd(queryFile && queryFile !== "-"
|
|
76
|
+
? createReadStream(queryFile)
|
|
77
|
+
: process.stdin)).join(""));
|
|
78
|
+
const params = {
|
|
79
|
+
query,
|
|
80
|
+
...(this.flags["page-size"] && { pageSize: this.flags["page-size"] }),
|
|
81
|
+
...(this.flags["page-token"] && { pageToken: this.flags["page-token"] }),
|
|
82
|
+
...(this.flags["include-total"] && { includeTotal: true }),
|
|
83
|
+
...(this.flags["order-by"] && { orderBy: this.flags["order-by"] }),
|
|
84
|
+
};
|
|
85
|
+
const runQuery = (params) => this.fetch(({ silo, tenantId }) => getNativeObjectApiEndpoint(tenantId, silo) + `/api/v1/$query/${type}`, {
|
|
86
|
+
body: JSON.stringify(params),
|
|
87
|
+
headers: {
|
|
88
|
+
"content-type": "application/json",
|
|
89
|
+
},
|
|
90
|
+
method: "POST",
|
|
91
|
+
});
|
|
92
|
+
if (this.flags.export) {
|
|
93
|
+
const pageSize = this.flags["page-size"] ?? 250;
|
|
94
|
+
params.pageSize = pageSize;
|
|
95
|
+
params.includeTotal = true;
|
|
96
|
+
const start = startTime();
|
|
97
|
+
let page = 1;
|
|
98
|
+
let nextPageToken;
|
|
99
|
+
let totalCount = 1;
|
|
100
|
+
do {
|
|
101
|
+
if (nextPageToken) {
|
|
102
|
+
params.pageToken = nextPageToken;
|
|
103
|
+
}
|
|
104
|
+
const xStart = startTime();
|
|
105
|
+
// eslint-disable-next-line no-await-in-loop
|
|
106
|
+
const x = await runQuery(params);
|
|
107
|
+
const xDuration = endTime(xStart);
|
|
108
|
+
if (x.totalCount) {
|
|
109
|
+
totalCount = x.totalCount;
|
|
110
|
+
}
|
|
111
|
+
x.items
|
|
112
|
+
.map(_ => JSON.stringify(_))
|
|
113
|
+
// eslint-disable-next-line unicorn/no-array-for-each
|
|
114
|
+
.forEach(_ => this.log(_));
|
|
115
|
+
nextPageToken = x.nextPageToken;
|
|
116
|
+
if (this.flags.progress) {
|
|
117
|
+
const duration = endTime(start);
|
|
118
|
+
const totalPages = Math.ceil(totalCount / pageSize);
|
|
119
|
+
const recsPerSec = Math.ceil((page * pageSize) / (duration * 1000));
|
|
120
|
+
const eta = (((duration / page) * (totalPages - page)) / 1000).toFixed(2);
|
|
121
|
+
process.stderr.write(`\r\u001B[Kexporting ${type} eta: ${eta}s totalCount: ${totalCount} page: ${page}/${totalPages} records/s: ${recsPerSec} lastCall: ${xDuration.toFixed(2)}ms`);
|
|
122
|
+
}
|
|
123
|
+
page++;
|
|
124
|
+
} while (nextPageToken);
|
|
125
|
+
if (this.flags.progress) {
|
|
126
|
+
process.stderr.write("\n");
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const rv = await runQuery(params);
|
|
131
|
+
this.logJson(rv);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from "../base.js";
|
|
2
|
+
export default class RecordsReplace extends BaseCommand<typeof RecordsReplace> {
|
|
3
|
+
static args: {
|
|
4
|
+
uri: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
static flags: {
|
|
9
|
+
id: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
"input-file": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
type: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Args, Flags } from "@oclif/core";
|
|
2
|
+
import fs, { createReadStream } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { parseUri } from "../../models/uri.js";
|
|
5
|
+
import { getNativeObjectApiEndpoint } from "../../utils/endpoints.js";
|
|
6
|
+
import { readStreamToEnd } from "../../utils/stream.js";
|
|
7
|
+
import { BaseCommand } from "../base.js";
|
|
8
|
+
export default class RecordsReplace extends BaseCommand {
|
|
9
|
+
static args = {
|
|
10
|
+
"uri": Args.string({ description: "uri of record" }),
|
|
11
|
+
};
|
|
12
|
+
static description = "replace a record";
|
|
13
|
+
static examples = [
|
|
14
|
+
"<%= config.bin %> <%= command.id %> native-object:my-type/instance-1 --input-file record.json",
|
|
15
|
+
"<%= config.bin %> <%= command.id %> native-object:my-type --id instance-1 --input-file record.json",
|
|
16
|
+
"<%= config.bin %> <%= command.id %> --type my-type --id instance-1 --input-file record.json",
|
|
17
|
+
];
|
|
18
|
+
static flags = {
|
|
19
|
+
id: Flags.string({
|
|
20
|
+
char: "i",
|
|
21
|
+
description: "id",
|
|
22
|
+
}),
|
|
23
|
+
"input-file": Flags.file({
|
|
24
|
+
char: "i",
|
|
25
|
+
default: "-",
|
|
26
|
+
description: "file to read. can read stdin if value is '-'",
|
|
27
|
+
}),
|
|
28
|
+
type: Flags.string({
|
|
29
|
+
char: "T",
|
|
30
|
+
description: "type",
|
|
31
|
+
}),
|
|
32
|
+
};
|
|
33
|
+
async run() {
|
|
34
|
+
const { uri } = this.args;
|
|
35
|
+
const { id: flagId, type: flagType } = this.flags;
|
|
36
|
+
let type = flagType;
|
|
37
|
+
let lookup = flagId;
|
|
38
|
+
if (uri) {
|
|
39
|
+
const { id, type: uriType } = parseUri(uri);
|
|
40
|
+
type = uriType;
|
|
41
|
+
if (id) {
|
|
42
|
+
lookup = id;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (!type) {
|
|
46
|
+
throw new Error("Type not provided");
|
|
47
|
+
}
|
|
48
|
+
if (!lookup) {
|
|
49
|
+
throw new Error("Id not provided");
|
|
50
|
+
}
|
|
51
|
+
const inputFile = this.flags["input-file"] === "-"
|
|
52
|
+
? "-"
|
|
53
|
+
: path.resolve(this.flags["input-file"]);
|
|
54
|
+
if (inputFile && inputFile !== "-" && !fs.existsSync(inputFile)) {
|
|
55
|
+
throw new Error("File does not exist");
|
|
56
|
+
}
|
|
57
|
+
const input = (await readStreamToEnd(inputFile && inputFile !== "-"
|
|
58
|
+
? createReadStream(inputFile)
|
|
59
|
+
: process.stdin)).join("");
|
|
60
|
+
const replace = (body) => this.fetch(({ silo, tenantId }) => getNativeObjectApiEndpoint(tenantId, silo) + `/api/v1/${type}/${lookup}`, {
|
|
61
|
+
body,
|
|
62
|
+
headers: {
|
|
63
|
+
"content-type": "application/json",
|
|
64
|
+
},
|
|
65
|
+
method: "PUT",
|
|
66
|
+
});
|
|
67
|
+
this.logJson(await replace(input));
|
|
68
|
+
}
|
|
69
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from "@oclif/core";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { run } from "@oclif/core";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const CliConfiguration: z.ZodObject<{
|
|
3
|
+
auth: z.ZodObject<{
|
|
4
|
+
audience: z.ZodString;
|
|
5
|
+
clientId: z.ZodString;
|
|
6
|
+
domain: z.ZodString;
|
|
7
|
+
}, z.core.$strip>;
|
|
8
|
+
}, z.core.$strip>;
|
|
9
|
+
export type CliConfiguration = z.infer<typeof CliConfiguration>;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export class HttpError extends Error {
|
|
2
|
+
response;
|
|
3
|
+
status;
|
|
4
|
+
constructor(message, response, status) {
|
|
5
|
+
const msg = [
|
|
6
|
+
message,
|
|
7
|
+
`status ${status}`,
|
|
8
|
+
typeof response === "string" ? response : JSON.stringify(response, null, 2),
|
|
9
|
+
].join("\n");
|
|
10
|
+
super(msg);
|
|
11
|
+
this.response = response;
|
|
12
|
+
this.status = status;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export const Uri = z.string().regex(/^native-object:[a-z0-9-]+(\/.+)?/);
|
|
3
|
+
export const parseUri = (uri) => {
|
|
4
|
+
const parsed = Uri.parse(uri);
|
|
5
|
+
const [type, id] = parsed.replace(/^native-object:/, "").split("/");
|
|
6
|
+
return { id, type };
|
|
7
|
+
};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export declare const AuthConfig: z.ZodObject<{
|
|
3
|
+
bearerToken: z.ZodString;
|
|
4
|
+
expiresAt: z.ZodString;
|
|
5
|
+
refreshToken: z.ZodOptional<z.ZodString>;
|
|
6
|
+
}, z.core.$strip>;
|
|
7
|
+
export type AuthConfig = z.infer<typeof AuthConfig>;
|
|
8
|
+
export declare const Config: z.ZodObject<{
|
|
9
|
+
silo: z.ZodEnum<{
|
|
10
|
+
prod: "prod";
|
|
11
|
+
qa: "qa";
|
|
12
|
+
dev: "dev";
|
|
13
|
+
}>;
|
|
14
|
+
tenantId: z.ZodString;
|
|
15
|
+
auth: z.ZodObject<{
|
|
16
|
+
audience: z.ZodString;
|
|
17
|
+
clientId: z.ZodString;
|
|
18
|
+
domain: z.ZodString;
|
|
19
|
+
}, z.core.$strip>;
|
|
20
|
+
bearerToken: z.ZodOptional<z.ZodString>;
|
|
21
|
+
expiresAt: z.ZodOptional<z.ZodString>;
|
|
22
|
+
refreshToken: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
export type Config = z.infer<typeof Config>;
|
|
25
|
+
export declare const CleanConfig: z.ZodObject<{
|
|
26
|
+
silo: z.ZodEnum<{
|
|
27
|
+
prod: "prod";
|
|
28
|
+
qa: "qa";
|
|
29
|
+
dev: "dev";
|
|
30
|
+
}>;
|
|
31
|
+
tenantId: z.ZodString;
|
|
32
|
+
configId: z.ZodString;
|
|
33
|
+
}, z.core.$strip>;
|
|
34
|
+
export type CleanConfig = z.infer<typeof CleanConfig>;
|
|
35
|
+
export declare const UserConfig: z.ZodObject<{
|
|
36
|
+
configs: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
37
|
+
silo: z.ZodEnum<{
|
|
38
|
+
prod: "prod";
|
|
39
|
+
qa: "qa";
|
|
40
|
+
dev: "dev";
|
|
41
|
+
}>;
|
|
42
|
+
tenantId: z.ZodString;
|
|
43
|
+
auth: z.ZodObject<{
|
|
44
|
+
audience: z.ZodString;
|
|
45
|
+
clientId: z.ZodString;
|
|
46
|
+
domain: z.ZodString;
|
|
47
|
+
}, z.core.$strip>;
|
|
48
|
+
bearerToken: z.ZodOptional<z.ZodString>;
|
|
49
|
+
expiresAt: z.ZodOptional<z.ZodString>;
|
|
50
|
+
refreshToken: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
51
|
+
}, z.core.$strip>>;
|
|
52
|
+
current: z.ZodOptional<z.ZodString>;
|
|
53
|
+
}, z.core.$strip>;
|
|
54
|
+
export type UserConfig = z.infer<typeof UserConfig>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { CliConfiguration } from "./cli-configuration.js";
|
|
3
|
+
import { Silo } from "./silo.js";
|
|
4
|
+
import { UrlSafeId } from "./url-safe-id.js";
|
|
5
|
+
export const AuthConfig = z.object({
|
|
6
|
+
bearerToken: z.string(),
|
|
7
|
+
expiresAt: z.string(),
|
|
8
|
+
refreshToken: z.string().optional(),
|
|
9
|
+
});
|
|
10
|
+
export const Config = z.object({
|
|
11
|
+
silo: Silo,
|
|
12
|
+
tenantId: UrlSafeId,
|
|
13
|
+
}).extend(CliConfiguration.shape)
|
|
14
|
+
.extend(AuthConfig.partial().shape);
|
|
15
|
+
export const CleanConfig = Config.pick({
|
|
16
|
+
silo: true,
|
|
17
|
+
tenantId: true,
|
|
18
|
+
}).extend({
|
|
19
|
+
configId: z.string(),
|
|
20
|
+
});
|
|
21
|
+
export const UserConfig = z.object({
|
|
22
|
+
configs: z.record(UrlSafeId, Config),
|
|
23
|
+
current: UrlSafeId.optional(),
|
|
24
|
+
});
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { CliConfiguration } from "../models/cli-configuration.js";
|
|
2
|
+
import { Silo } from "../models/silo.js";
|
|
3
|
+
import { AuthConfig } from "../models/user-config.js";
|
|
4
|
+
export declare const codeExchange: ({ audience, clientId, domain }: CliConfiguration["auth"]) => Promise<AuthConfig>;
|
|
5
|
+
export declare const rehydrateToken: ({ clientId, domain }: CliConfiguration["auth"], refreshToken: string) => Promise<AuthConfig>;
|
|
6
|
+
export declare const getUserProfile: (tenantId: string, silo: Silo, bearerToken: string) => Promise<any>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/* eslint-disable camelcase */
|
|
2
|
+
import fetch from "cross-fetch";
|
|
3
|
+
import open from "open";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { AuthConfig } from "../models/user-config.js";
|
|
6
|
+
import { getAuthApiEndpoint } from "./endpoints.js";
|
|
7
|
+
const scope = "offline_access openid";
|
|
8
|
+
const TokenResponse = z.object({
|
|
9
|
+
access_token: z.string(),
|
|
10
|
+
expires_in: z.number(),
|
|
11
|
+
refresh_token: z.string().optional(),
|
|
12
|
+
});
|
|
13
|
+
const parseTokenResponse = (tokenJson) => {
|
|
14
|
+
const { access_token, expires_in, refresh_token } = TokenResponse.parse(tokenJson);
|
|
15
|
+
const expiresAt = new Date();
|
|
16
|
+
expiresAt.setUTCSeconds(expiresAt.getUTCSeconds() + expires_in);
|
|
17
|
+
return AuthConfig.parse({
|
|
18
|
+
bearerToken: access_token,
|
|
19
|
+
expiresAt: expiresAt.toISOString(),
|
|
20
|
+
refreshToken: refresh_token,
|
|
21
|
+
});
|
|
22
|
+
};
|
|
23
|
+
export const codeExchange = async ({ audience, clientId, domain }) => {
|
|
24
|
+
const authEndpoint = `https://${domain}`;
|
|
25
|
+
const codeRsp = await fetch(`${authEndpoint}/oauth/device/code`, {
|
|
26
|
+
body: new URLSearchParams({
|
|
27
|
+
audience,
|
|
28
|
+
client_id: clientId,
|
|
29
|
+
scope,
|
|
30
|
+
}).toString(),
|
|
31
|
+
headers: {
|
|
32
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
33
|
+
},
|
|
34
|
+
method: "POST",
|
|
35
|
+
});
|
|
36
|
+
const codeJson = await codeRsp.json();
|
|
37
|
+
const { device_code, interval, verification_uri_complete } = codeJson;
|
|
38
|
+
console.log("We've launched your web browser to complete the login process.");
|
|
39
|
+
console.log("Verification code: %s", device_code);
|
|
40
|
+
console.log();
|
|
41
|
+
console.log("Waiting for login to complete...");
|
|
42
|
+
await open(`${verification_uri_complete}&audience=${audience}`);
|
|
43
|
+
while (true) {
|
|
44
|
+
// eslint-disable-next-line no-await-in-loop
|
|
45
|
+
const tokenRsp = await fetch(`${authEndpoint}/oauth/token`, {
|
|
46
|
+
body: new URLSearchParams({
|
|
47
|
+
audience,
|
|
48
|
+
client_id: clientId,
|
|
49
|
+
device_code,
|
|
50
|
+
grant_type: "urn:ietf:params:oauth:grant-type:device_code",
|
|
51
|
+
}),
|
|
52
|
+
headers: {
|
|
53
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
54
|
+
},
|
|
55
|
+
method: "POST",
|
|
56
|
+
});
|
|
57
|
+
// eslint-disable-next-line no-await-in-loop
|
|
58
|
+
const tokenJson = await tokenRsp.json();
|
|
59
|
+
if ("error" in tokenJson) {
|
|
60
|
+
switch (tokenJson.error) {
|
|
61
|
+
case "authorization_pending": {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
default: {
|
|
65
|
+
console.log("error", tokenJson);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// eslint-disable-next-line no-await-in-loop
|
|
69
|
+
await new Promise(resolve => { setTimeout(resolve, interval * 1000); });
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
const rv = parseTokenResponse(tokenJson);
|
|
73
|
+
return rv;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
export const rehydrateToken = async ({ clientId, domain }, refreshToken) => {
|
|
78
|
+
const refreshRsp = await fetch(`https://${domain}/oauth/token`, {
|
|
79
|
+
body: new URLSearchParams({
|
|
80
|
+
client_id: clientId,
|
|
81
|
+
grant_type: "refresh_token",
|
|
82
|
+
refresh_token: refreshToken,
|
|
83
|
+
scope,
|
|
84
|
+
}).toString(),
|
|
85
|
+
headers: {
|
|
86
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
87
|
+
},
|
|
88
|
+
method: "POST",
|
|
89
|
+
});
|
|
90
|
+
const tokenJson = await refreshRsp.json();
|
|
91
|
+
return parseTokenResponse(tokenJson);
|
|
92
|
+
};
|
|
93
|
+
export const getUserProfile = async (tenantId, silo, bearerToken) => {
|
|
94
|
+
const rsp = await fetch(getAuthApiEndpoint(tenantId, silo) + "/api/v1/me/profile", {
|
|
95
|
+
headers: {
|
|
96
|
+
authorization: `Bearer ${bearerToken}`,
|
|
97
|
+
},
|
|
98
|
+
method: "GET",
|
|
99
|
+
});
|
|
100
|
+
return rsp.json();
|
|
101
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fetch from "cross-fetch";
|
|
2
|
+
import { CliConfiguration } from "../models/cli-configuration.js";
|
|
3
|
+
import { getNativeObjectApiEndpoint } from "./endpoints.js";
|
|
4
|
+
export const getCliConfiguration = async (tenantId, silo) => {
|
|
5
|
+
const url = getNativeObjectApiEndpoint(tenantId, silo) + "/.well-known/cli-configuration";
|
|
6
|
+
let rsp;
|
|
7
|
+
try {
|
|
8
|
+
rsp = await fetch(url, {
|
|
9
|
+
method: "GET",
|
|
10
|
+
});
|
|
11
|
+
if (!rsp.ok)
|
|
12
|
+
throw new Error("not ok");
|
|
13
|
+
}
|
|
14
|
+
catch {
|
|
15
|
+
throw new Error(`unable to fetch cli configuration: ${JSON.stringify({ silo, tenantId })}`);
|
|
16
|
+
}
|
|
17
|
+
const val = await rsp.json();
|
|
18
|
+
return CliConfiguration.parse(val);
|
|
19
|
+
};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export const getNativeObjectApiEndpoint = (tenantId, silo) => `https://native-object.${tenantId}.my${silo === "prod" ? "" : `.${silo}`}.contextual.io`;
|
|
2
|
+
export const getAuthApiEndpoint = (tenantId, silo) => `https://auth.${tenantId}.my${silo === "prod" ? "" : `.${silo}`}.contextual.io`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Transform } from "node:stream";
|
|
2
|
+
export const readStreamToEnd = (stream) => {
|
|
3
|
+
const chunks = [];
|
|
4
|
+
return new Promise((resolve, reject) => {
|
|
5
|
+
stream.on("data", chunk => chunks.push(chunk));
|
|
6
|
+
stream.on("error", err => reject(err));
|
|
7
|
+
stream.on("end", () => resolve(chunks));
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
export const mapStream = (fn) => new Transform({
|
|
11
|
+
objectMode: true,
|
|
12
|
+
async transform(chunk, _, callback) {
|
|
13
|
+
try {
|
|
14
|
+
const xxx = fn(chunk);
|
|
15
|
+
callback(null, (xxx instanceof Promise) ? await xxx : xxx);
|
|
16
|
+
}
|
|
17
|
+
catch (error) {
|
|
18
|
+
callback(error instanceof Error ? error : new Error(`${error}`));
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
});
|