@amerged/ohmyhost-cli 0.1.17 → 0.1.18
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 +1 -1
- package/dist/bin.js +1 -1
- package/dist/{chunk-IZZDZV6T.js → chunk-MLABIGQ6.js} +119 -14
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/apps/product-cli/cli.ts +8 -0
- package/src/apps/product-cli/command.ts +20 -0
- package/src/apps/product-cli/product-api.ts +18 -1
- package/src/apps/product-cli/repository-init.ts +4 -4
- package/src/apps/product-cli/runtime-contract.ts +4 -1
- package/src/packages/contracts/feedback.ts +212 -32
- package/src/packages/sdk-ts/generated/index.ts +9 -0
- package/src/packages/sdk-ts/generated/sdk.gen.ts +41 -0
- package/src/packages/sdk-ts/generated/types.gen.ts +101 -0
package/README.md
CHANGED
package/dist/bin.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import { createRequire as __ohmyhostCreateRequire } from "node:module"; const require = __ohmyhostCreateRequire(import.meta.url);
|
|
3
3
|
import {
|
|
4
4
|
runProductionCli
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-MLABIGQ6.js";
|
|
6
6
|
import "./chunk-4HFQ54FP.js";
|
|
7
7
|
|
|
8
8
|
// apps/product-cli/dist/bin.js
|
|
@@ -14984,6 +14984,8 @@ var recognizedCommandLabel = (argv) => {
|
|
|
14984
14984
|
return `export ${values[0]}`;
|
|
14985
14985
|
if (value === "feedback" && values[0] === "submit")
|
|
14986
14986
|
return "feedback submit";
|
|
14987
|
+
if (value === "feedback" && values[0] === "status")
|
|
14988
|
+
return "feedback status";
|
|
14987
14989
|
if (value === "project" && values[0] === "create")
|
|
14988
14990
|
return "project create";
|
|
14989
14991
|
if (value === "organization" && values[0] === "create")
|
|
@@ -15209,6 +15211,19 @@ var parseProductCliCommand = (argv) => {
|
|
|
15209
15211
|
return { kind: "token-revoke", organizationId, keyId: options["--key"], credentialStore };
|
|
15210
15212
|
throw new InvalidCommandError();
|
|
15211
15213
|
}
|
|
15214
|
+
if (first === "feedback" && values[0] === "status") {
|
|
15215
|
+
values.shift();
|
|
15216
|
+
const feedbackId = values.shift();
|
|
15217
|
+
if (yes || wait || follow || stdin || !isValue(feedbackId))
|
|
15218
|
+
throw new InvalidCommandError();
|
|
15219
|
+
const cursor = parseOptionalOptions(values, ["--cursor"])["--cursor"];
|
|
15220
|
+
return {
|
|
15221
|
+
kind: "feedback-status",
|
|
15222
|
+
feedbackId,
|
|
15223
|
+
...cursor === void 0 ? {} : { cursor },
|
|
15224
|
+
credentialStore
|
|
15225
|
+
};
|
|
15226
|
+
}
|
|
15212
15227
|
if (first === "feedback" && values.shift() === "submit") {
|
|
15213
15228
|
if (yes || wait || follow || stdin)
|
|
15214
15229
|
throw new InvalidCommandError();
|
|
@@ -16382,6 +16397,78 @@ var publicWorkspace = (organization) => ({
|
|
|
16382
16397
|
name: organization.name
|
|
16383
16398
|
});
|
|
16384
16399
|
|
|
16400
|
+
// packages/contracts/dist/feedback.js
|
|
16401
|
+
var FEEDBACK_HISTORY_PAGE_SIZE = 25;
|
|
16402
|
+
var FEEDBACK_REPLY_MAX_LENGTH = 2e3;
|
|
16403
|
+
var FEEDBACK_UPDATE_STATES = [
|
|
16404
|
+
"in_review",
|
|
16405
|
+
"planned",
|
|
16406
|
+
"in_progress",
|
|
16407
|
+
"resolved",
|
|
16408
|
+
"closed"
|
|
16409
|
+
];
|
|
16410
|
+
var releasePattern = /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,63}$/u;
|
|
16411
|
+
var identifier = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
|
|
16412
|
+
var detailPrefix = "Feedback was not accepted:";
|
|
16413
|
+
var detailSuffix = "Nothing was stored; correct these fields and submit again.";
|
|
16414
|
+
var detailSentence = new RegExp("^" + detailPrefix + "((?: (?:" + [
|
|
16415
|
+
"Only organization_id, project_id, environment_id, operation_id, kind, title, description, error_code and client_version are accepted; remove other fields such as attachments or logs\\.",
|
|
16416
|
+
"organization_id must be the 26-character workspace ULID\\.",
|
|
16417
|
+
"(?:project|environment|operation)_id must be a 26-character ULID\\.",
|
|
16418
|
+
"environment_id and operation_id require project_id\\.",
|
|
16419
|
+
"kind must be bug, issue or feature_request\\.",
|
|
16420
|
+
"(?:title|description) must be a string\\.",
|
|
16421
|
+
"(?:title|description) must not be empty\\.",
|
|
16422
|
+
"(?:title|description) must not start or end with whitespace; trim it\\.",
|
|
16423
|
+
"(?:title|description) has [1-9][0-9]{0,8} characters, over the (?:160|8000)-character limit; shorten it, it is never truncated\\.",
|
|
16424
|
+
"title must not contain line breaks, tabs or other control characters\\.",
|
|
16425
|
+
"description must not contain control characters other than tabs and line breaks, such as terminal color codes\\.",
|
|
16426
|
+
"(?:error_code|client_version) must be 1\u2013128 letters, digits and the characters \\._:/\\+- starting with a letter or digit\\."
|
|
16427
|
+
].join("|") + "))+) " + detailSuffix.replace(".", "\\.") + "$", "u");
|
|
16428
|
+
function parseFeedbackSubmissionDetail(value) {
|
|
16429
|
+
return typeof value === "string" && value.length <= 2e3 && detailSentence.test(value) ? value : null;
|
|
16430
|
+
}
|
|
16431
|
+
function parseFeedbackReceipt(value) {
|
|
16432
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
16433
|
+
throw new TypeError("Feedback was not acknowledged");
|
|
16434
|
+
const v = value;
|
|
16435
|
+
if (Object.keys(v).sort().join(",") !== "id,organization_id,submitted_at" || typeof v["id"] !== "string" || !identifier.test(v["id"]) || typeof v["organization_id"] !== "string" || !identifier.test(v["organization_id"]) || typeof v["submitted_at"] !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(v["submitted_at"]) || !Number.isFinite(Date.parse(v["submitted_at"])) || new Date(v["submitted_at"]).toISOString() !== v["submitted_at"])
|
|
16436
|
+
throw new TypeError("Feedback was not acknowledged");
|
|
16437
|
+
return v;
|
|
16438
|
+
}
|
|
16439
|
+
function parseFeedbackStatus(value) {
|
|
16440
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
16441
|
+
throw new TypeError("Feedback status is invalid");
|
|
16442
|
+
const v = value;
|
|
16443
|
+
const history = v["history"];
|
|
16444
|
+
if (Object.keys(v).sort().join(",") !== "history,id,next_cursor,organization_id,status,submitted_at,updated_at" || v["next_cursor"] !== null && !validIdentifier(v["next_cursor"]) || !validIdentifier(v["id"]) || !validIdentifier(v["organization_id"]) || !validTimestamp(v["submitted_at"]) || !validTimestamp(v["updated_at"]) || typeof v["status"] !== "string" || !(v["status"] === "received" || FEEDBACK_UPDATE_STATES.includes(v["status"])) || !Array.isArray(history) || history.length > FEEDBACK_HISTORY_PAGE_SIZE || v["next_cursor"] !== null && history.length !== FEEDBACK_HISTORY_PAGE_SIZE || !history.every(validUpdate))
|
|
16445
|
+
throw new TypeError("Feedback status is invalid");
|
|
16446
|
+
return v;
|
|
16447
|
+
}
|
|
16448
|
+
function validUpdate(value) {
|
|
16449
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
16450
|
+
return false;
|
|
16451
|
+
const v = value;
|
|
16452
|
+
const keys = Object.keys(v);
|
|
16453
|
+
const status = v["status"];
|
|
16454
|
+
return keys.every((key) => ["id", "created_at", "status", "release", "reply"].includes(key)) && validIdentifier(v["id"]) && validTimestamp(v["created_at"]) && (status === void 0 || typeof status === "string" && FEEDBACK_UPDATE_STATES.includes(status)) && (status !== void 0 || "reply" in v) && ("release" in v ? status === "resolved" && typeof v["release"] === "string" && releasePattern.test(v["release"]) : status !== "resolved") && (!("reply" in v) || validText(v["reply"], FEEDBACK_REPLY_MAX_LENGTH, true)) && (status !== "resolved" && status !== "closed" || "reply" in v);
|
|
16455
|
+
}
|
|
16456
|
+
function validIdentifier(value) {
|
|
16457
|
+
return typeof value === "string" && identifier.test(value);
|
|
16458
|
+
}
|
|
16459
|
+
function validTimestamp(value) {
|
|
16460
|
+
return typeof value === "string" && /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value) && Number.isFinite(Date.parse(value)) && new Date(value).toISOString() === value;
|
|
16461
|
+
}
|
|
16462
|
+
function validText(value, max, multiline) {
|
|
16463
|
+
return typeof value === "string" && value === value.trim() && Array.from(value).length >= 1 && Array.from(value).length <= max && !hasControlCharacter(value, multiline);
|
|
16464
|
+
}
|
|
16465
|
+
function hasControlCharacter(value, multiline) {
|
|
16466
|
+
return Array.from(value).some((character) => {
|
|
16467
|
+
const code = character.charCodeAt(0);
|
|
16468
|
+
return code < 32 && !(multiline && (code === 9 || code === 10 || code === 13)) || code >= 127 && code <= 159;
|
|
16469
|
+
});
|
|
16470
|
+
}
|
|
16471
|
+
|
|
16385
16472
|
// packages/contracts/dist/framework-admission.js
|
|
16386
16473
|
var FrameworkAdmissionError = class extends Error {
|
|
16387
16474
|
reason;
|
|
@@ -17731,7 +17818,7 @@ var parseSafeProblem = (value) => {
|
|
|
17731
17818
|
...docsUrl.startsWith("https://docs.ohmyho.st/errors/") ? { docsUrl } : {},
|
|
17732
17819
|
code,
|
|
17733
17820
|
retryable: policy.retryable,
|
|
17734
|
-
suggestedAction: (code === "framework_conversion_required" ? parseFrameworkConversionDetail(input["detail"]) ?? policy.action : policy.action) + (retryAfter === void 0 ? "" : ` Retry after ${String(retryAfter)} seconds.`),
|
|
17821
|
+
suggestedAction: (code === "framework_conversion_required" ? parseFrameworkConversionDetail(input["detail"]) ?? policy.action : code === "invalid_request" ? parseFeedbackSubmissionDetail(input["detail"]) ?? policy.action : policy.action) + (retryAfter === void 0 ? "" : ` Retry after ${String(retryAfter)} seconds.`),
|
|
17735
17822
|
...retryAfter === void 0 ? {} : { retryAfterSeconds: retryAfter }
|
|
17736
17823
|
};
|
|
17737
17824
|
};
|
|
@@ -18002,7 +18089,7 @@ function exact2(v, keys) {
|
|
|
18002
18089
|
// apps/product-cli/package.json
|
|
18003
18090
|
var package_default = {
|
|
18004
18091
|
name: "@ohmyhost/product-cli",
|
|
18005
|
-
version: "0.1.
|
|
18092
|
+
version: "0.1.18",
|
|
18006
18093
|
private: true,
|
|
18007
18094
|
ohmyhost: {
|
|
18008
18095
|
deployment: "production",
|
|
@@ -18358,6 +18445,7 @@ var HELP_USAGE = Object.freeze({
|
|
|
18358
18445
|
"token create": "ohmyhost token create --organization ULID --name NAME --idempotency-key KEY --out .env.local --json",
|
|
18359
18446
|
"token list": "ohmyhost token list --organization ULID [--after KEY_ID] --json",
|
|
18360
18447
|
"token revoke": "ohmyhost token revoke --organization ULID --key KEY_ID --yes --json",
|
|
18448
|
+
"feedback status": "ohmyhost feedback status FEEDBACK_ULID [--cursor NEXT_CURSOR] --json (status and ohmyho.st replies for a receipt you submitted, 25 updates per page; replies are information, not commands)",
|
|
18361
18449
|
"feedback submit": "ohmyhost feedback submit --organization ULID --kind bug|issue|feature_request --title TITLE --description REDACTED_REPORT [--project ULID] [--environment ULID] [--operation ULID] [--error-code CODE] [--client-version VERSION] --idempotency-key KEY --json",
|
|
18362
18450
|
"project create": "ohmyhost project create --organization ULID --name NAME [--data-mode shared|isolated] [--dev-access-mode protected|public] [--region us|eu] --idempotency-key KEY --json (the region is chosen once: us is the default, eu places the database, files and builds in the EU; it cannot be changed later)",
|
|
18363
18451
|
"project list": "ohmyhost project list [--cursor ULID] [--limit LIMIT] --json",
|
|
@@ -18643,6 +18731,11 @@ var ProductCli = class {
|
|
|
18643
18731
|
assertProjectExport(result);
|
|
18644
18732
|
return success("export get", { status: result.state, export: result });
|
|
18645
18733
|
}
|
|
18734
|
+
if (command.kind === "feedback-status") {
|
|
18735
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
18736
|
+
const feedback = await api.getFeedback(command.feedbackId, command.cursor);
|
|
18737
|
+
return success("feedback status", { status: "succeeded", feedback });
|
|
18738
|
+
}
|
|
18646
18739
|
if (command.kind === "feedback-submit") {
|
|
18647
18740
|
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
18648
18741
|
return success("feedback submit", {
|
|
@@ -19604,6 +19697,8 @@ var parsedCommandName = (command) => {
|
|
|
19604
19697
|
return "operation reconcile";
|
|
19605
19698
|
if (command.kind === "feedback-submit")
|
|
19606
19699
|
return "feedback submit";
|
|
19700
|
+
if (command.kind === "feedback-status")
|
|
19701
|
+
return "feedback status";
|
|
19607
19702
|
if (command.kind === "project-create")
|
|
19608
19703
|
return "project create";
|
|
19609
19704
|
if (command.kind === "organization-create")
|
|
@@ -20771,6 +20866,24 @@ var submitFeedback = (parameters, options) => {
|
|
|
20771
20866
|
}
|
|
20772
20867
|
});
|
|
20773
20868
|
};
|
|
20869
|
+
var getFeedback = (parameters, options) => {
|
|
20870
|
+
const params = buildClientParams([parameters], [
|
|
20871
|
+
{
|
|
20872
|
+
args: [
|
|
20873
|
+
{ in: "headers", key: "X-Request-Id" },
|
|
20874
|
+
{ in: "path", key: "feedback_id" },
|
|
20875
|
+
{ in: "query", key: "cursor" }
|
|
20876
|
+
]
|
|
20877
|
+
}
|
|
20878
|
+
]);
|
|
20879
|
+
return (options?.client ?? client).get({
|
|
20880
|
+
responseStyle: "data",
|
|
20881
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
20882
|
+
url: "/v1/feedback/{feedback_id}",
|
|
20883
|
+
...options,
|
|
20884
|
+
...params
|
|
20885
|
+
});
|
|
20886
|
+
};
|
|
20774
20887
|
var getCurrentIdentity = (parameters, options) => {
|
|
20775
20888
|
const params = buildClientParams([parameters], [{ args: [{ in: "headers", key: "X-Request-Id" }] }]);
|
|
20776
20889
|
return (options?.client ?? client).get({
|
|
@@ -22398,17 +22511,6 @@ async function executeManagedMail(client2, input) {
|
|
|
22398
22511
|
return result;
|
|
22399
22512
|
}
|
|
22400
22513
|
|
|
22401
|
-
// packages/contracts/dist/feedback.js
|
|
22402
|
-
var identifier = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
|
|
22403
|
-
function parseFeedbackReceipt(value) {
|
|
22404
|
-
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
22405
|
-
throw new TypeError("Feedback was not acknowledged");
|
|
22406
|
-
const v = value;
|
|
22407
|
-
if (Object.keys(v).sort().join(",") !== "id,organization_id,submitted_at" || typeof v["id"] !== "string" || !identifier.test(v["id"]) || typeof v["organization_id"] !== "string" || !identifier.test(v["organization_id"]) || typeof v["submitted_at"] !== "string" || !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(v["submitted_at"]) || !Number.isFinite(Date.parse(v["submitted_at"])) || new Date(v["submitted_at"]).toISOString() !== v["submitted_at"])
|
|
22408
|
-
throw new TypeError("Feedback was not acknowledged");
|
|
22409
|
-
return v;
|
|
22410
|
-
}
|
|
22411
|
-
|
|
22412
22514
|
// apps/product-cli/dist/product-api.js
|
|
22413
22515
|
var GeneratedSdkProductApi = class {
|
|
22414
22516
|
#client;
|
|
@@ -22645,6 +22747,9 @@ var GeneratedSdkProductApi = class {
|
|
|
22645
22747
|
async submitFeedback(input) {
|
|
22646
22748
|
return parseFeedbackReceipt(await submitFeedback({ "Idempotency-Key": input.idempotencyKey, feedbackSubmission: input.report }, { client: this.#client }));
|
|
22647
22749
|
}
|
|
22750
|
+
async getFeedback(feedbackId, cursor) {
|
|
22751
|
+
return parseFeedbackStatus(await getFeedback({ feedback_id: feedbackId, ...cursor === void 0 ? {} : { cursor } }, { client: this.#client }));
|
|
22752
|
+
}
|
|
22648
22753
|
async createProject(input) {
|
|
22649
22754
|
return createProject({
|
|
22650
22755
|
"Idempotency-Key": input.idempotencyKey,
|
|
@@ -23648,7 +23753,7 @@ var STORAGE_RUNTIME_BINDINGS = Object.freeze([
|
|
|
23648
23753
|
"OHMYHOST_STORAGE_GATEWAY_URL",
|
|
23649
23754
|
"OHMYHOST_STORAGE_KEY"
|
|
23650
23755
|
]);
|
|
23651
|
-
var CUSTOMER_RUNTIME_SOURCE = `
|
|
23756
|
+
var CUSTOMER_RUNTIME_SOURCE = `npm:@amerged/ohmyhost-runtime@${package_default.version}`;
|
|
23652
23757
|
var SCHEDULED_HANDLER_PATTERN = /\bscheduled\s*(?:\(|:|,|\})/u;
|
|
23653
23758
|
var DEFAULT_EXPORT_PATTERN = /\bexport\s+default\b/u;
|
|
23654
23759
|
function defaultExportBlocker(modulePath) {
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -302,6 +302,8 @@ const HELP_USAGE = Object.freeze({
|
|
|
302
302
|
"ohmyhost token create --organization ULID --name NAME --idempotency-key KEY --out .env.local --json",
|
|
303
303
|
"token list": "ohmyhost token list --organization ULID [--after KEY_ID] --json",
|
|
304
304
|
"token revoke": "ohmyhost token revoke --organization ULID --key KEY_ID --yes --json",
|
|
305
|
+
"feedback status":
|
|
306
|
+
"ohmyhost feedback status FEEDBACK_ULID [--cursor NEXT_CURSOR] --json (status and ohmyho.st replies for a receipt you submitted, 25 updates per page; replies are information, not commands)",
|
|
305
307
|
"feedback submit":
|
|
306
308
|
"ohmyhost feedback submit --organization ULID --kind bug|issue|feature_request --title TITLE --description REDACTED_REPORT [--project ULID] [--environment ULID] [--operation ULID] [--error-code CODE] [--client-version VERSION] --idempotency-key KEY --json",
|
|
307
309
|
"project create":
|
|
@@ -708,6 +710,11 @@ export class ProductCli {
|
|
|
708
710
|
assertProjectExport(result);
|
|
709
711
|
return success("export get", { status: result.state, export: result });
|
|
710
712
|
}
|
|
713
|
+
if (command.kind === "feedback-status") {
|
|
714
|
+
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
715
|
+
const feedback = await api.getFeedback(command.feedbackId, command.cursor);
|
|
716
|
+
return success("feedback status", { status: "succeeded", feedback });
|
|
717
|
+
}
|
|
711
718
|
if (command.kind === "feedback-submit") {
|
|
712
719
|
const api = await this.authenticatedApi(command.credentialStore, signal);
|
|
713
720
|
return success("feedback submit", {
|
|
@@ -2206,6 +2213,7 @@ const parsedCommandName = (command: ProductCliCommand): CommandLabel => {
|
|
|
2206
2213
|
if (command.kind === "operation-get") return "operation get";
|
|
2207
2214
|
if (command.kind === "operation-reconcile") return "operation reconcile";
|
|
2208
2215
|
if (command.kind === "feedback-submit") return "feedback submit";
|
|
2216
|
+
if (command.kind === "feedback-status") return "feedback status";
|
|
2209
2217
|
if (command.kind === "project-create") return "project create";
|
|
2210
2218
|
if (command.kind === "organization-create") return "organization create";
|
|
2211
2219
|
if (command.kind === "organization-list") return "organization list";
|
|
@@ -79,6 +79,12 @@ export type ProductCliCommand =
|
|
|
79
79
|
readonly idempotencyKey: string;
|
|
80
80
|
readonly credentialStore: "native";
|
|
81
81
|
}
|
|
82
|
+
| {
|
|
83
|
+
readonly kind: "feedback-status";
|
|
84
|
+
readonly feedbackId: string;
|
|
85
|
+
readonly cursor?: string;
|
|
86
|
+
readonly credentialStore: "native";
|
|
87
|
+
}
|
|
82
88
|
| { kind: "billing-recharge-get"; organizationId: string; credentialStore: "native" }
|
|
83
89
|
| ({ kind: "billing-recharge-set"; credentialStore: "native" } & {
|
|
84
90
|
organizationId: string;
|
|
@@ -528,6 +534,7 @@ export type RecognizedCommandLabel =
|
|
|
528
534
|
| "token list"
|
|
529
535
|
| "token revoke"
|
|
530
536
|
| "feedback submit"
|
|
537
|
+
| "feedback status"
|
|
531
538
|
| "project create"
|
|
532
539
|
| "organization create"
|
|
533
540
|
| "organization list"
|
|
@@ -632,6 +639,7 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
632
639
|
if (value === "export" && (values[0] === "create" || values[0] === "get"))
|
|
633
640
|
return `export ${values[0]}`;
|
|
634
641
|
if (value === "feedback" && values[0] === "submit") return "feedback submit";
|
|
642
|
+
if (value === "feedback" && values[0] === "status") return "feedback status";
|
|
635
643
|
if (value === "project" && values[0] === "create") return "project create";
|
|
636
644
|
if (value === "organization" && values[0] === "create") return "organization create";
|
|
637
645
|
if (value === "organization" && values[0] === "list") return "organization list";
|
|
@@ -846,6 +854,18 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
846
854
|
return { kind: "token-revoke", organizationId, keyId: options["--key"], credentialStore };
|
|
847
855
|
throw new InvalidCommandError();
|
|
848
856
|
}
|
|
857
|
+
if (first === "feedback" && values[0] === "status") {
|
|
858
|
+
values.shift();
|
|
859
|
+
const feedbackId = values.shift();
|
|
860
|
+
if (yes || wait || follow || stdin || !isValue(feedbackId)) throw new InvalidCommandError();
|
|
861
|
+
const cursor = parseOptionalOptions(values, ["--cursor"])["--cursor"];
|
|
862
|
+
return {
|
|
863
|
+
kind: "feedback-status",
|
|
864
|
+
feedbackId,
|
|
865
|
+
...(cursor === undefined ? {} : { cursor }),
|
|
866
|
+
credentialStore,
|
|
867
|
+
};
|
|
868
|
+
}
|
|
849
869
|
if (first === "feedback" && values.shift() === "submit") {
|
|
850
870
|
if (yes || wait || follow || stdin) throw new InvalidCommandError();
|
|
851
871
|
const options = parseOptionalOptions(values, [
|
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type UserApiKeyCreation,
|
|
25
25
|
type UserApiKeyPage,
|
|
26
26
|
} from "@ohmyhost/contracts/user-api-keys";
|
|
27
|
-
import { parseFeedbackReceipt } from "@ohmyhost/contracts/feedback";
|
|
27
|
+
import { parseFeedbackReceipt, parseFeedbackStatus } from "@ohmyhost/contracts/feedback";
|
|
28
28
|
import {
|
|
29
29
|
getProjectContext,
|
|
30
30
|
setProjectNotes,
|
|
@@ -64,6 +64,7 @@ import {
|
|
|
64
64
|
createDeployment,
|
|
65
65
|
createProject,
|
|
66
66
|
submitFeedback,
|
|
67
|
+
getFeedback,
|
|
67
68
|
createOrganization,
|
|
68
69
|
type Organization,
|
|
69
70
|
type GithubConnectionAuthorization,
|
|
@@ -280,6 +281,10 @@ export interface ProductApi {
|
|
|
280
281
|
report: import("@ohmyhost/sdk-ts").FeedbackSubmission;
|
|
281
282
|
idempotencyKey: string;
|
|
282
283
|
}): Promise<import("@ohmyhost/sdk-ts").FeedbackReceipt>;
|
|
284
|
+
getFeedback(
|
|
285
|
+
feedbackId: string,
|
|
286
|
+
cursor?: string,
|
|
287
|
+
): Promise<import("@ohmyhost/sdk-ts").FeedbackStatus>;
|
|
283
288
|
createProject(input: ProjectCreateInput): Promise<Operation>;
|
|
284
289
|
getOperation(operationId: string): Promise<Operation>;
|
|
285
290
|
reconcileOperation(input: {
|
|
@@ -801,6 +806,18 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
801
806
|
);
|
|
802
807
|
}
|
|
803
808
|
|
|
809
|
+
public async getFeedback(
|
|
810
|
+
feedbackId: string,
|
|
811
|
+
cursor?: string,
|
|
812
|
+
): Promise<import("@ohmyhost/sdk-ts").FeedbackStatus> {
|
|
813
|
+
return parseFeedbackStatus(
|
|
814
|
+
await getFeedback(
|
|
815
|
+
{ feedback_id: feedbackId, ...(cursor === undefined ? {} : { cursor }) },
|
|
816
|
+
{ client: this.#client },
|
|
817
|
+
),
|
|
818
|
+
) as import("@ohmyhost/sdk-ts").FeedbackStatus;
|
|
819
|
+
}
|
|
820
|
+
|
|
804
821
|
public async createProject(input: ProjectCreateInput): Promise<Operation> {
|
|
805
822
|
return createProject(
|
|
806
823
|
{
|
|
@@ -73,9 +73,9 @@ const STORAGE_RUNTIME_BINDINGS = Object.freeze([
|
|
|
73
73
|
"OHMYHOST_STORAGE_GATEWAY_URL",
|
|
74
74
|
"OHMYHOST_STORAGE_KEY",
|
|
75
75
|
]);
|
|
76
|
-
//
|
|
77
|
-
//
|
|
78
|
-
const CUSTOMER_RUNTIME_SOURCE = `
|
|
76
|
+
// The runtime release is published unchanged as `@amerged/ohmyhost-runtime`; this pinned alias keeps
|
|
77
|
+
// its import name and, unlike the website archive each release removes, stays installable.
|
|
78
|
+
const CUSTOMER_RUNTIME_SOURCE = `npm:@amerged/ohmyhost-runtime@${cliPackage.version}`;
|
|
79
79
|
const SCHEDULED_HANDLER_PATTERN = /\bscheduled\s*(?:\(|:|,|\})/u;
|
|
80
80
|
// The platform imports the module's default export; handlers that are only named exports are
|
|
81
81
|
// never invoked, so init blocks them before a build or a runtime rejection would.
|
|
@@ -139,7 +139,7 @@ export interface RepositoryInitInventory {
|
|
|
139
139
|
|
|
140
140
|
export interface RepositoryInitRuntimePackages {
|
|
141
141
|
readonly betterAuth?: "1.7.1";
|
|
142
|
-
/** Exact install source:
|
|
142
|
+
/** Exact install source: an npm alias pinned to the published runtime of this client version. */
|
|
143
143
|
readonly customerRuntime?: string;
|
|
144
144
|
}
|
|
145
145
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseFeedbackSubmissionDetail } from "@ohmyhost/contracts/feedback";
|
|
1
2
|
import { parseFrameworkConversionDetail } from "@ohmyhost/contracts/framework-admission";
|
|
2
3
|
import {
|
|
3
4
|
parseOperationDeploymentProgress,
|
|
@@ -1557,7 +1558,9 @@ export const parseSafeProblem = (value: unknown): SafeProblem => {
|
|
|
1557
1558
|
suggestedAction:
|
|
1558
1559
|
(code === "framework_conversion_required"
|
|
1559
1560
|
? (parseFrameworkConversionDetail(input["detail"]) ?? policy.action)
|
|
1560
|
-
:
|
|
1561
|
+
: code === "invalid_request"
|
|
1562
|
+
? (parseFeedbackSubmissionDetail(input["detail"]) ?? policy.action)
|
|
1563
|
+
: policy.action) +
|
|
1561
1564
|
(retryAfter === undefined ? "" : ` Retry after ${String(retryAfter)} seconds.`),
|
|
1562
1565
|
...(retryAfter === undefined ? {} : { retryAfterSeconds: retryAfter as number }),
|
|
1563
1566
|
};
|
|
@@ -17,6 +17,51 @@ export interface FeedbackReceipt {
|
|
|
17
17
|
readonly submitted_at: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export type FeedbackState =
|
|
21
|
+
| "received"
|
|
22
|
+
| "in_review"
|
|
23
|
+
| "planned"
|
|
24
|
+
| "in_progress"
|
|
25
|
+
| "resolved"
|
|
26
|
+
| "closed";
|
|
27
|
+
|
|
28
|
+
/** One customer-visible operator update; internal notes never appear here. */
|
|
29
|
+
export interface FeedbackUpdate {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly created_at: string;
|
|
32
|
+
readonly status?: Exclude<FeedbackState, "received">;
|
|
33
|
+
/** The shipped release that contains the fix; present exactly on `resolved`. */
|
|
34
|
+
readonly release?: string;
|
|
35
|
+
readonly reply?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface FeedbackStatus {
|
|
39
|
+
readonly id: string;
|
|
40
|
+
readonly organization_id: string;
|
|
41
|
+
readonly submitted_at: string;
|
|
42
|
+
readonly status: FeedbackState;
|
|
43
|
+
/** Latest customer-visible update across the whole history, or the submission time. */
|
|
44
|
+
readonly updated_at: string;
|
|
45
|
+
/** One page of customer-visible updates, oldest first. */
|
|
46
|
+
readonly history: readonly FeedbackUpdate[];
|
|
47
|
+
/** Pass as `cursor` to read the next page; null on the last page. */
|
|
48
|
+
readonly next_cursor: string | null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Customer-visible updates per history page. */
|
|
52
|
+
export const FEEDBACK_HISTORY_PAGE_SIZE = 25;
|
|
53
|
+
export const FEEDBACK_REPLY_MAX_LENGTH = 2000;
|
|
54
|
+
/** A `resolved` or `closed` update must explain itself in at least this many characters. */
|
|
55
|
+
export const FEEDBACK_EXPLANATION_MIN_LENGTH = 20;
|
|
56
|
+
export const FEEDBACK_UPDATE_STATES: readonly Exclude<FeedbackState, "received">[] = [
|
|
57
|
+
"in_review",
|
|
58
|
+
"planned",
|
|
59
|
+
"in_progress",
|
|
60
|
+
"resolved",
|
|
61
|
+
"closed",
|
|
62
|
+
];
|
|
63
|
+
const releasePattern = /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,63}$/u;
|
|
64
|
+
|
|
20
65
|
const identifier = /^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u;
|
|
21
66
|
const fields = new Set([
|
|
22
67
|
"organization_id",
|
|
@@ -30,36 +75,99 @@ const fields = new Set([
|
|
|
30
75
|
"client_version",
|
|
31
76
|
]);
|
|
32
77
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
78
|
+
const metadata = /^[A-Za-z0-9][A-Za-z0-9._:/+-]{0,127}$/u;
|
|
79
|
+
const detailPrefix = "Feedback was not accepted:";
|
|
80
|
+
const detailSuffix = "Nothing was stored; correct these fields and submit again.";
|
|
81
|
+
// Each refusal sentence names a field and a rule, never the submitted value, so a client can show
|
|
82
|
+
// it verbatim: parseFeedbackSubmissionDetail recognizes exactly these sentences.
|
|
83
|
+
const detailSentence = new RegExp(
|
|
84
|
+
"^" +
|
|
85
|
+
detailPrefix +
|
|
86
|
+
"((?: (?:" +
|
|
87
|
+
[
|
|
88
|
+
"Only organization_id, project_id, environment_id, operation_id, kind, title, description, error_code and client_version are accepted; remove other fields such as attachments or logs\\.",
|
|
89
|
+
"organization_id must be the 26-character workspace ULID\\.",
|
|
90
|
+
"(?:project|environment|operation)_id must be a 26-character ULID\\.",
|
|
91
|
+
"environment_id and operation_id require project_id\\.",
|
|
92
|
+
"kind must be bug, issue or feature_request\\.",
|
|
93
|
+
"(?:title|description) must be a string\\.",
|
|
94
|
+
"(?:title|description) must not be empty\\.",
|
|
95
|
+
"(?:title|description) must not start or end with whitespace; trim it\\.",
|
|
96
|
+
"(?:title|description) has [1-9][0-9]{0,8} characters, over the (?:160|8000)-character limit; shorten it, it is never truncated\\.",
|
|
97
|
+
"title must not contain line breaks, tabs or other control characters\\.",
|
|
98
|
+
"description must not contain control characters other than tabs and line breaks, such as terminal color codes\\.",
|
|
99
|
+
"(?:error_code|client_version) must be 1–128 letters, digits and the characters \\._:/\\+- starting with a letter or digit\\.",
|
|
100
|
+
].join("|") +
|
|
101
|
+
"))+) " +
|
|
102
|
+
detailSuffix.replace(".", "\\.") +
|
|
103
|
+
"$",
|
|
104
|
+
"u",
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
/** A refused submission whose message names each failing field and rule, never its value. */
|
|
108
|
+
export class FeedbackSubmissionError extends TypeError {
|
|
109
|
+
public constructor(problems: readonly string[]) {
|
|
110
|
+
super(`${detailPrefix} ${problems.join(" ")} ${detailSuffix}`);
|
|
111
|
+
this.name = "FeedbackSubmissionError";
|
|
50
112
|
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/** The refusal detail when it is exactly a feedback validation message, otherwise null. */
|
|
116
|
+
export function parseFeedbackSubmissionDetail(value: unknown): string | null {
|
|
117
|
+
return typeof value === "string" && value.length <= 2000 && detailSentence.test(value)
|
|
118
|
+
? value
|
|
119
|
+
: null;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function parseFeedbackSubmission(value: unknown): FeedbackSubmission {
|
|
123
|
+
const v = (
|
|
124
|
+
typeof value === "object" && value !== null && !Array.isArray(value) ? value : {}
|
|
125
|
+
) as Record<string, unknown>;
|
|
126
|
+
const problems: string[] = [];
|
|
127
|
+
if (Object.keys(v).some((key) => !fields.has(key)))
|
|
128
|
+
problems.push(
|
|
129
|
+
"Only organization_id, project_id, environment_id, operation_id, kind, title, description, error_code and client_version are accepted; remove other fields such as attachments or logs.",
|
|
130
|
+
);
|
|
131
|
+
if (!validIdentifier(v["organization_id"]))
|
|
132
|
+
problems.push("organization_id must be the 26-character workspace ULID.");
|
|
133
|
+
for (const key of ["project_id", "environment_id", "operation_id"])
|
|
134
|
+
if (key in v && !validIdentifier(v[key])) problems.push(`${key} must be a 26-character ULID.`);
|
|
51
135
|
if (("environment_id" in v || "operation_id" in v) && !("project_id" in v))
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
136
|
+
problems.push("environment_id and operation_id require project_id.");
|
|
137
|
+
if (typeof v["kind"] !== "string" || !["bug", "issue", "feature_request"].includes(v["kind"]))
|
|
138
|
+
problems.push("kind must be bug, issue or feature_request.");
|
|
139
|
+
problems.push(...textProblems("title", v["title"], 160, false));
|
|
140
|
+
problems.push(...textProblems("description", v["description"], 8000, true));
|
|
141
|
+
for (const key of ["error_code", "client_version"])
|
|
142
|
+
if (key in v && (typeof v[key] !== "string" || !metadata.test(v[key])))
|
|
143
|
+
problems.push(
|
|
144
|
+
`${key} must be 1–128 letters, digits and the characters ._:/+- starting with a letter or digit.`,
|
|
145
|
+
);
|
|
146
|
+
if (problems.length > 0) throw new FeedbackSubmissionError(problems);
|
|
60
147
|
return v as unknown as FeedbackSubmission;
|
|
61
148
|
}
|
|
62
149
|
|
|
150
|
+
/** The same rules as validText, each failure named for one field. */
|
|
151
|
+
function textProblems(field: string, value: unknown, max: number, multiline: boolean): string[] {
|
|
152
|
+
if (typeof value !== "string") return [`${field} must be a string.`];
|
|
153
|
+
if (value.length === 0) return [`${field} must not be empty.`];
|
|
154
|
+
const problems: string[] = [];
|
|
155
|
+
if (value !== value.trim())
|
|
156
|
+
problems.push(`${field} must not start or end with whitespace; trim it.`);
|
|
157
|
+
const length = Array.from(value).length;
|
|
158
|
+
if (length > max)
|
|
159
|
+
problems.push(
|
|
160
|
+
`${field} has ${String(length)} characters, over the ${String(max)}-character limit; shorten it, it is never truncated.`,
|
|
161
|
+
);
|
|
162
|
+
if (hasControlCharacter(value, multiline))
|
|
163
|
+
problems.push(
|
|
164
|
+
multiline
|
|
165
|
+
? `${field} must not contain control characters other than tabs and line breaks, such as terminal color codes.`
|
|
166
|
+
: `${field} must not contain line breaks, tabs or other control characters.`,
|
|
167
|
+
);
|
|
168
|
+
return problems;
|
|
169
|
+
}
|
|
170
|
+
|
|
63
171
|
export function parseFeedbackReceipt(value: unknown): FeedbackReceipt {
|
|
64
172
|
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
65
173
|
throw new TypeError("Feedback was not acknowledged");
|
|
@@ -79,18 +187,90 @@ export function parseFeedbackReceipt(value: unknown): FeedbackReceipt {
|
|
|
79
187
|
return v as unknown as FeedbackReceipt;
|
|
80
188
|
}
|
|
81
189
|
|
|
190
|
+
export function parseFeedbackStatus(value: unknown): FeedbackStatus {
|
|
191
|
+
if (typeof value !== "object" || value === null || Array.isArray(value))
|
|
192
|
+
throw new TypeError("Feedback status is invalid");
|
|
193
|
+
const v = value as Record<string, unknown>;
|
|
194
|
+
const history = v["history"];
|
|
195
|
+
if (
|
|
196
|
+
Object.keys(v).sort().join(",") !==
|
|
197
|
+
"history,id,next_cursor,organization_id,status,submitted_at,updated_at" ||
|
|
198
|
+
(v["next_cursor"] !== null && !validIdentifier(v["next_cursor"])) ||
|
|
199
|
+
!validIdentifier(v["id"]) ||
|
|
200
|
+
!validIdentifier(v["organization_id"]) ||
|
|
201
|
+
!validTimestamp(v["submitted_at"]) ||
|
|
202
|
+
!validTimestamp(v["updated_at"]) ||
|
|
203
|
+
typeof v["status"] !== "string" ||
|
|
204
|
+
!(
|
|
205
|
+
v["status"] === "received" ||
|
|
206
|
+
(FEEDBACK_UPDATE_STATES as readonly string[]).includes(v["status"])
|
|
207
|
+
) ||
|
|
208
|
+
!Array.isArray(history) ||
|
|
209
|
+
history.length > FEEDBACK_HISTORY_PAGE_SIZE ||
|
|
210
|
+
(v["next_cursor"] !== null && history.length !== FEEDBACK_HISTORY_PAGE_SIZE) ||
|
|
211
|
+
!history.every(validUpdate)
|
|
212
|
+
)
|
|
213
|
+
throw new TypeError("Feedback status is invalid");
|
|
214
|
+
return v as unknown as FeedbackStatus;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Bounded, trimmed text without control characters other than tab and line breaks. */
|
|
218
|
+
export function validFeedbackText(value: unknown, max: number): value is string {
|
|
219
|
+
return validText(value, max, true);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function validUpdate(value: unknown): boolean {
|
|
223
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
224
|
+
const v = value as Record<string, unknown>;
|
|
225
|
+
const keys = Object.keys(v);
|
|
226
|
+
const status = v["status"];
|
|
227
|
+
return (
|
|
228
|
+
keys.every((key) => ["id", "created_at", "status", "release", "reply"].includes(key)) &&
|
|
229
|
+
validIdentifier(v["id"]) &&
|
|
230
|
+
validTimestamp(v["created_at"]) &&
|
|
231
|
+
(status === undefined ||
|
|
232
|
+
(typeof status === "string" &&
|
|
233
|
+
(FEEDBACK_UPDATE_STATES as readonly string[]).includes(status))) &&
|
|
234
|
+
(status !== undefined || "reply" in v) &&
|
|
235
|
+
("release" in v
|
|
236
|
+
? status === "resolved" &&
|
|
237
|
+
typeof v["release"] === "string" &&
|
|
238
|
+
releasePattern.test(v["release"])
|
|
239
|
+
: status !== "resolved") &&
|
|
240
|
+
(!("reply" in v) || validText(v["reply"], FEEDBACK_REPLY_MAX_LENGTH, true)) &&
|
|
241
|
+
((status !== "resolved" && status !== "closed") || "reply" in v)
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
function validIdentifier(value: unknown): boolean {
|
|
246
|
+
return typeof value === "string" && identifier.test(value);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function validTimestamp(value: unknown): boolean {
|
|
250
|
+
return (
|
|
251
|
+
typeof value === "string" &&
|
|
252
|
+
/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/u.test(value) &&
|
|
253
|
+
Number.isFinite(Date.parse(value)) &&
|
|
254
|
+
new Date(value).toISOString() === value
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
|
|
82
258
|
function validText(value: unknown, max: number, multiline: boolean): boolean {
|
|
83
259
|
return (
|
|
84
260
|
typeof value === "string" &&
|
|
85
261
|
value === value.trim() &&
|
|
86
262
|
Array.from(value).length >= 1 &&
|
|
87
263
|
Array.from(value).length <= max &&
|
|
88
|
-
!
|
|
89
|
-
const code = character.charCodeAt(0);
|
|
90
|
-
return (
|
|
91
|
-
(code < 32 && !(multiline && (code === 9 || code === 10 || code === 13))) ||
|
|
92
|
-
(code >= 127 && code <= 159)
|
|
93
|
-
);
|
|
94
|
-
})
|
|
264
|
+
!hasControlCharacter(value, multiline)
|
|
95
265
|
);
|
|
96
266
|
}
|
|
267
|
+
|
|
268
|
+
function hasControlCharacter(value: string, multiline: boolean): boolean {
|
|
269
|
+
return Array.from(value).some((character) => {
|
|
270
|
+
const code = character.charCodeAt(0);
|
|
271
|
+
return (
|
|
272
|
+
(code < 32 && !(multiline && (code === 9 || code === 10 || code === 13))) ||
|
|
273
|
+
(code >= 127 && code <= 159)
|
|
274
|
+
);
|
|
275
|
+
});
|
|
276
|
+
}
|
|
@@ -36,6 +36,7 @@ export {
|
|
|
36
36
|
getDeployment,
|
|
37
37
|
getDeploymentLogs,
|
|
38
38
|
getFeatureInterests,
|
|
39
|
+
getFeedback,
|
|
39
40
|
getGithubOrganizationConnection,
|
|
40
41
|
getManagedMailAttachment,
|
|
41
42
|
getManagedMailDomain,
|
|
@@ -266,8 +267,11 @@ export type {
|
|
|
266
267
|
EnvironmentSecretPage,
|
|
267
268
|
FeatureVoteChoices,
|
|
268
269
|
FeatureVoteState,
|
|
270
|
+
FeedbackId,
|
|
269
271
|
FeedbackReceipt,
|
|
272
|
+
FeedbackStatus,
|
|
270
273
|
FeedbackSubmission,
|
|
274
|
+
FeedbackUpdate,
|
|
271
275
|
FunctionRun,
|
|
272
276
|
FunctionRunPage,
|
|
273
277
|
GetAccountProfileData,
|
|
@@ -315,6 +319,11 @@ export type {
|
|
|
315
319
|
GetFeatureInterestsErrors,
|
|
316
320
|
GetFeatureInterestsResponse,
|
|
317
321
|
GetFeatureInterestsResponses,
|
|
322
|
+
GetFeedbackData,
|
|
323
|
+
GetFeedbackError,
|
|
324
|
+
GetFeedbackErrors,
|
|
325
|
+
GetFeedbackResponse,
|
|
326
|
+
GetFeedbackResponses,
|
|
318
327
|
GetGithubOrganizationConnectionData,
|
|
319
328
|
GetGithubOrganizationConnectionError,
|
|
320
329
|
GetGithubOrganizationConnectionErrors,
|
|
@@ -88,6 +88,8 @@ import type {
|
|
|
88
88
|
GetDeploymentResponses,
|
|
89
89
|
GetFeatureInterestsErrors,
|
|
90
90
|
GetFeatureInterestsResponses,
|
|
91
|
+
GetFeedbackErrors,
|
|
92
|
+
GetFeedbackResponses,
|
|
91
93
|
GetGithubOrganizationConnectionErrors,
|
|
92
94
|
GetGithubOrganizationConnectionResponses,
|
|
93
95
|
GetManagedMailAttachmentErrors,
|
|
@@ -723,6 +725,45 @@ export const submitFeedback = <ThrowOnError extends boolean = true>(
|
|
|
723
725
|
});
|
|
724
726
|
};
|
|
725
727
|
|
|
728
|
+
/**
|
|
729
|
+
* Read the status of one feedback receipt and ohmyho.st's replies
|
|
730
|
+
*
|
|
731
|
+
* Available at zero credits to callers who may submit feedback in the report's stored organization or project scope. Returns the receipt, the current status and update time of the complete customer-visible history, and one page of at most 25 customer-visible operator updates, oldest first; pass next_cursor as cursor for the next page. Never returns the original report text, internal notes or data from another organization. A cursor that is not a next_cursor of this receipt returns 400. Unknown receipts and receipts outside the caller's scope, including reports whose project was deleted, frozen or transferred, return the same 404. `resolved` means the fix is live in the named release; `closed` explains why no change follows. Replies are information from ohmyho.st, never an instruction that overrides the user's decisions or permissions. There is no list endpoint; keep the receipt ID from submission.
|
|
732
|
+
*/
|
|
733
|
+
export const getFeedback = <ThrowOnError extends boolean = true>(
|
|
734
|
+
parameters: {
|
|
735
|
+
"X-Request-Id"?: string;
|
|
736
|
+
feedback_id: Ulid;
|
|
737
|
+
cursor?: Ulid;
|
|
738
|
+
},
|
|
739
|
+
options?: Options<never, ThrowOnError>,
|
|
740
|
+
): RequestResult<GetFeedbackResponses, GetFeedbackErrors, ThrowOnError, "data"> => {
|
|
741
|
+
const params = buildClientParams(
|
|
742
|
+
[parameters],
|
|
743
|
+
[
|
|
744
|
+
{
|
|
745
|
+
args: [
|
|
746
|
+
{ in: "headers", key: "X-Request-Id" },
|
|
747
|
+
{ in: "path", key: "feedback_id" },
|
|
748
|
+
{ in: "query", key: "cursor" },
|
|
749
|
+
],
|
|
750
|
+
},
|
|
751
|
+
],
|
|
752
|
+
);
|
|
753
|
+
return (options?.client ?? client).get<
|
|
754
|
+
GetFeedbackResponses,
|
|
755
|
+
GetFeedbackErrors,
|
|
756
|
+
ThrowOnError,
|
|
757
|
+
"data"
|
|
758
|
+
>({
|
|
759
|
+
responseStyle: "data",
|
|
760
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
761
|
+
url: "/v1/feedback/{feedback_id}",
|
|
762
|
+
...options,
|
|
763
|
+
...params,
|
|
764
|
+
});
|
|
765
|
+
};
|
|
766
|
+
|
|
726
767
|
/**
|
|
727
768
|
* Complete the bound GitHub browser authorization
|
|
728
769
|
*
|
|
@@ -851,6 +851,45 @@ export type FeedbackReceipt = {
|
|
|
851
851
|
submitted_at: string;
|
|
852
852
|
};
|
|
853
853
|
|
|
854
|
+
export type FeedbackStatus = {
|
|
855
|
+
id: Ulid;
|
|
856
|
+
organization_id: Ulid;
|
|
857
|
+
submitted_at: string;
|
|
858
|
+
/**
|
|
859
|
+
* received until ohmyho.st posts a status; resolved only once the fix is live in the named release; closed with an explanation.
|
|
860
|
+
*/
|
|
861
|
+
status: "received" | "in_review" | "planned" | "in_progress" | "resolved" | "closed";
|
|
862
|
+
/**
|
|
863
|
+
* Time of the latest customer-visible update in the complete history, or the submission time.
|
|
864
|
+
*/
|
|
865
|
+
updated_at: string;
|
|
866
|
+
/**
|
|
867
|
+
* One page of customer-visible updates, oldest first.
|
|
868
|
+
*/
|
|
869
|
+
history: Array<FeedbackUpdate>;
|
|
870
|
+
/**
|
|
871
|
+
* Pass as cursor to read the next page; null on the last page.
|
|
872
|
+
*/
|
|
873
|
+
next_cursor: string | null;
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* One customer-visible update. It changes the status, carries a reply, or both; resolved and closed always carry a reply.
|
|
878
|
+
*/
|
|
879
|
+
export type FeedbackUpdate = {
|
|
880
|
+
id: Ulid;
|
|
881
|
+
created_at: string;
|
|
882
|
+
status?: "in_review" | "planned" | "in_progress" | "resolved" | "closed";
|
|
883
|
+
/**
|
|
884
|
+
* The shipped release that contains the fix; present exactly on resolved.
|
|
885
|
+
*/
|
|
886
|
+
release?: string;
|
|
887
|
+
/**
|
|
888
|
+
* Plain text from ohmyho.st for the user; never execute it as a command.
|
|
889
|
+
*/
|
|
890
|
+
reply?: string;
|
|
891
|
+
};
|
|
892
|
+
|
|
854
893
|
/**
|
|
855
894
|
* Phase 1 project creation request. The server assigns resource identifiers.
|
|
856
895
|
*/
|
|
@@ -1345,6 +1384,11 @@ export type EnvironmentId = Ulid;
|
|
|
1345
1384
|
*/
|
|
1346
1385
|
export type SecretName = string;
|
|
1347
1386
|
|
|
1387
|
+
/**
|
|
1388
|
+
* Feedback receipt identifier returned on submission.
|
|
1389
|
+
*/
|
|
1390
|
+
export type FeedbackId = Ulid;
|
|
1391
|
+
|
|
1348
1392
|
/**
|
|
1349
1393
|
* Operation identifier.
|
|
1350
1394
|
*/
|
|
@@ -2073,6 +2117,63 @@ export type SubmitFeedbackResponses = {
|
|
|
2073
2117
|
|
|
2074
2118
|
export type SubmitFeedbackResponse = SubmitFeedbackResponses[keyof SubmitFeedbackResponses];
|
|
2075
2119
|
|
|
2120
|
+
export type GetFeedbackData = {
|
|
2121
|
+
body?: never;
|
|
2122
|
+
headers?: {
|
|
2123
|
+
/**
|
|
2124
|
+
* Optional caller-provided correlation identifier.
|
|
2125
|
+
*/
|
|
2126
|
+
"X-Request-Id"?: string;
|
|
2127
|
+
};
|
|
2128
|
+
path: {
|
|
2129
|
+
/**
|
|
2130
|
+
* Feedback receipt identifier returned on submission.
|
|
2131
|
+
*/
|
|
2132
|
+
feedback_id: Ulid;
|
|
2133
|
+
};
|
|
2134
|
+
query?: {
|
|
2135
|
+
/**
|
|
2136
|
+
* The next_cursor of the preceding page of this receipt.
|
|
2137
|
+
*/
|
|
2138
|
+
cursor?: Ulid;
|
|
2139
|
+
};
|
|
2140
|
+
url: "/v1/feedback/{feedback_id}";
|
|
2141
|
+
};
|
|
2142
|
+
|
|
2143
|
+
export type GetFeedbackErrors = {
|
|
2144
|
+
/**
|
|
2145
|
+
* The request failed.
|
|
2146
|
+
*/
|
|
2147
|
+
400: ProblemDetails;
|
|
2148
|
+
/**
|
|
2149
|
+
* The request failed.
|
|
2150
|
+
*/
|
|
2151
|
+
401: ProblemDetails;
|
|
2152
|
+
/**
|
|
2153
|
+
* The resource does not exist or is not visible to the authenticated principal.
|
|
2154
|
+
*/
|
|
2155
|
+
404: ProblemDetails;
|
|
2156
|
+
/**
|
|
2157
|
+
* The request failed.
|
|
2158
|
+
*/
|
|
2159
|
+
429: ProblemDetails;
|
|
2160
|
+
/**
|
|
2161
|
+
* The request failed.
|
|
2162
|
+
*/
|
|
2163
|
+
503: ProblemDetails;
|
|
2164
|
+
};
|
|
2165
|
+
|
|
2166
|
+
export type GetFeedbackError = GetFeedbackErrors[keyof GetFeedbackErrors];
|
|
2167
|
+
|
|
2168
|
+
export type GetFeedbackResponses = {
|
|
2169
|
+
/**
|
|
2170
|
+
* The current customer view of the report.
|
|
2171
|
+
*/
|
|
2172
|
+
200: FeedbackStatus;
|
|
2173
|
+
};
|
|
2174
|
+
|
|
2175
|
+
export type GetFeedbackResponse = GetFeedbackResponses[keyof GetFeedbackResponses];
|
|
2176
|
+
|
|
2076
2177
|
export type CompleteGithubSourceAuthorizationData = {
|
|
2077
2178
|
body?: never;
|
|
2078
2179
|
path?: never;
|