@amerged/ohmyhost-mcp 0.1.27 → 0.1.29
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/THIRD_PARTY_NOTICES.md +1419 -0
- package/dist/{chunk-QF7MOXLY.js → chunk-BWVBJWRR.js} +69 -23
- package/dist/index.js +1 -1
- package/dist/stdio-main.js +24158 -2353
- package/package.json +1 -1
- package/src/apps/mcp/package.json +1 -1
- package/src/apps/mcp/src/local-client.ts +12 -2
- package/src/apps/mcp/src/local-server.ts +333 -4
- package/src/apps/product-cli/package.json +4 -2
- package/src/apps/product-cli/src/cli.ts +312 -2
- package/src/apps/product-cli/src/command.ts +261 -0
- package/src/apps/product-cli/src/composition.ts +74 -4
- package/src/apps/product-cli/src/index.ts +6 -0
- package/src/apps/product-cli/src/product-api.ts +5 -0
- package/src/apps/product-cli/src/project-link-store.ts +92 -1
- package/src/apps/product-cli/src/repository-init.ts +12 -0
- package/src/apps/product-cli/src/runtime-contract.ts +34 -1
- package/src/apps/product-cli/src/source-managed-client.ts +189 -0
- package/src/apps/product-cli/src/source-publication-store.ts +563 -0
- package/src/apps/product-cli/src/source-publisher.ts +195 -0
- package/src/apps/product-cli/src/source-workspace.ts +552 -0
- package/src/packages/agent-skills/src/generated-skill-resources.ts +20 -10
- package/src/packages/agent-skills/src/index.ts +1 -1
- package/src/packages/contracts/package.json +10 -0
- package/src/packages/contracts/src/index.ts +5 -0
- package/src/packages/contracts/src/managed-sources.ts +582 -0
- package/src/packages/contracts/src/mcp-source-codec.ts +150 -0
- package/src/packages/contracts/src/operation-failure.ts +22 -6
- package/src/packages/contracts/src/problem-policy.ts +68 -14
- package/src/packages/sdk-ts/package.json +1 -1
- package/src/packages/sdk-ts/src/generated/index.ts +92 -0
- package/src/packages/sdk-ts/src/generated/sdk.gen.ts +541 -0
- package/src/packages/sdk-ts/src/generated/types.gen.ts +912 -4
- package/src/packages/workos-auth-contracts/src/tokens.ts +61 -4
|
@@ -7,7 +7,63 @@ import type { ManagedMailCommand } from "@ohmyhost/sdk-ts";
|
|
|
7
7
|
import { USER_API_KEY_ID } from "@ohmyhost/contracts/user-api-keys";
|
|
8
8
|
import { WORKOS_USER_API_KEY_PATTERN } from "@ohmyhost/workos-auth-contracts/user-api-keys";
|
|
9
9
|
import { isProfileName, isProfileUserId } from "./profile-store.js";
|
|
10
|
+
import {
|
|
11
|
+
isManagedSourcePath,
|
|
12
|
+
parseManagedSourceRequest,
|
|
13
|
+
type ManagedSourceUploadRequest,
|
|
14
|
+
type ManagedSourceRestoreRequest,
|
|
15
|
+
type ManagedSourceInitializeRequest,
|
|
16
|
+
} from "@ohmyhost/contracts/managed-sources";
|
|
17
|
+
export type SourceCliCommand =
|
|
18
|
+
| { readonly kind: "source-inspect"; readonly directory: string }
|
|
19
|
+
| {
|
|
20
|
+
readonly kind: "source-read";
|
|
21
|
+
readonly action: "status" | "files" | "file" | "versions" | "diff" | "upload";
|
|
22
|
+
readonly projectId: string;
|
|
23
|
+
readonly commitSha?: string;
|
|
24
|
+
readonly path?: string;
|
|
25
|
+
readonly limit?: number;
|
|
26
|
+
readonly beforeCommitSha?: string;
|
|
27
|
+
readonly fromCommitSha?: string;
|
|
28
|
+
readonly toCommitSha?: string;
|
|
29
|
+
readonly operationId?: string;
|
|
30
|
+
readonly credentialStore: "native";
|
|
31
|
+
}
|
|
32
|
+
| {
|
|
33
|
+
readonly kind: "source-publish";
|
|
34
|
+
readonly projectId: string;
|
|
35
|
+
readonly directory: string;
|
|
36
|
+
readonly request: ManagedSourceUploadRequest;
|
|
37
|
+
readonly explicitExpectedCommit: boolean;
|
|
38
|
+
readonly idempotencyKey: string;
|
|
39
|
+
readonly wait: boolean;
|
|
40
|
+
readonly credentialStore: "native";
|
|
41
|
+
}
|
|
42
|
+
| {
|
|
43
|
+
readonly kind: "source-complete";
|
|
44
|
+
readonly projectId: string;
|
|
45
|
+
readonly directory: string;
|
|
46
|
+
readonly operationId?: string;
|
|
47
|
+
readonly credentialStore: "native";
|
|
48
|
+
}
|
|
49
|
+
| {
|
|
50
|
+
readonly kind: "source-initialize";
|
|
51
|
+
readonly projectId: string;
|
|
52
|
+
readonly request: ManagedSourceInitializeRequest;
|
|
53
|
+
readonly idempotencyKey: string;
|
|
54
|
+
readonly wait: boolean;
|
|
55
|
+
readonly credentialStore: "native";
|
|
56
|
+
}
|
|
57
|
+
| {
|
|
58
|
+
readonly kind: "source-restore";
|
|
59
|
+
readonly projectId: string;
|
|
60
|
+
readonly request: ManagedSourceRestoreRequest;
|
|
61
|
+
readonly idempotencyKey: string;
|
|
62
|
+
readonly wait: boolean;
|
|
63
|
+
readonly credentialStore: "native";
|
|
64
|
+
};
|
|
10
65
|
export type ProductCliCommand =
|
|
66
|
+
| SourceCliCommand
|
|
11
67
|
| {
|
|
12
68
|
readonly kind: "project-data-plan";
|
|
13
69
|
readonly projectId: string;
|
|
@@ -571,7 +627,190 @@ export class InvalidCommandError extends Error {
|
|
|
571
627
|
}
|
|
572
628
|
}
|
|
573
629
|
|
|
630
|
+
function parseSourceCommand(values: string[], wait: boolean): SourceCliCommand {
|
|
631
|
+
const action = values.shift();
|
|
632
|
+
if (action === "publish" && values[0] === "complete") {
|
|
633
|
+
values.shift();
|
|
634
|
+
if (wait) throw new InvalidCommandError();
|
|
635
|
+
const options = parseOptionalOptions(values, ["--project", "--directory", "--operation"]);
|
|
636
|
+
const projectId = options["--project"],
|
|
637
|
+
operationId = options["--operation"];
|
|
638
|
+
if (
|
|
639
|
+
projectId === undefined ||
|
|
640
|
+
!/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(projectId) ||
|
|
641
|
+
(operationId !== undefined && !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(operationId))
|
|
642
|
+
)
|
|
643
|
+
throw new InvalidCommandError();
|
|
644
|
+
return {
|
|
645
|
+
kind: "source-complete",
|
|
646
|
+
projectId,
|
|
647
|
+
directory: options["--directory"] ?? ".",
|
|
648
|
+
operationId,
|
|
649
|
+
credentialStore: "native",
|
|
650
|
+
};
|
|
651
|
+
}
|
|
652
|
+
if (action === "inspect") {
|
|
653
|
+
if (wait) throw new InvalidCommandError();
|
|
654
|
+
const options = parseOptionalOptions(values, ["--directory"]);
|
|
655
|
+
return { kind: "source-inspect", directory: options["--directory"] ?? "." };
|
|
656
|
+
}
|
|
657
|
+
const allowed =
|
|
658
|
+
action === "publish"
|
|
659
|
+
? [
|
|
660
|
+
"--project",
|
|
661
|
+
"--directory",
|
|
662
|
+
"--mode",
|
|
663
|
+
"--expected-source-generation",
|
|
664
|
+
"--expected-source-connection",
|
|
665
|
+
"--expected-commit",
|
|
666
|
+
"--message",
|
|
667
|
+
"--idempotency-key",
|
|
668
|
+
]
|
|
669
|
+
: action === "restore"
|
|
670
|
+
? [
|
|
671
|
+
"--project",
|
|
672
|
+
"--expected-source-generation",
|
|
673
|
+
"--expected-commit",
|
|
674
|
+
"--restore-commit",
|
|
675
|
+
"--message",
|
|
676
|
+
"--idempotency-key",
|
|
677
|
+
]
|
|
678
|
+
: action === "initialize"
|
|
679
|
+
? ["--project", "--template", "--expected-source-generation", "--idempotency-key"]
|
|
680
|
+
: [
|
|
681
|
+
"--project",
|
|
682
|
+
"--commit",
|
|
683
|
+
"--path",
|
|
684
|
+
"--limit",
|
|
685
|
+
"--before",
|
|
686
|
+
"--from",
|
|
687
|
+
"--to",
|
|
688
|
+
"--operation",
|
|
689
|
+
];
|
|
690
|
+
const options = parseOptionalOptions(values, allowed);
|
|
691
|
+
const projectId = options["--project"];
|
|
692
|
+
if (projectId === undefined || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(projectId))
|
|
693
|
+
throw new InvalidCommandError();
|
|
694
|
+
const credentialStore = "native" as const;
|
|
695
|
+
if (action === "publish" || action === "restore" || action === "initialize") {
|
|
696
|
+
const idempotencyKey = options["--idempotency-key"];
|
|
697
|
+
const rawGeneration = options["--expected-source-generation"];
|
|
698
|
+
if (
|
|
699
|
+
idempotencyKey === undefined ||
|
|
700
|
+
rawGeneration === undefined ||
|
|
701
|
+
!/^(?:0|[1-9][0-9]*)$/u.test(rawGeneration)
|
|
702
|
+
)
|
|
703
|
+
throw new InvalidCommandError();
|
|
704
|
+
const expected_source_generation = Number(rawGeneration);
|
|
705
|
+
try {
|
|
706
|
+
if (action === "initialize")
|
|
707
|
+
return {
|
|
708
|
+
kind: "source-initialize",
|
|
709
|
+
projectId,
|
|
710
|
+
request: parseManagedSourceRequest("initialize", {
|
|
711
|
+
expected_source_generation,
|
|
712
|
+
template: options["--template"] ?? "vite-react",
|
|
713
|
+
}),
|
|
714
|
+
idempotencyKey,
|
|
715
|
+
wait,
|
|
716
|
+
credentialStore,
|
|
717
|
+
};
|
|
718
|
+
const common = {
|
|
719
|
+
expected_source_generation,
|
|
720
|
+
expected_commit_sha: options["--expected-commit"] ?? null,
|
|
721
|
+
message: options["--message"],
|
|
722
|
+
};
|
|
723
|
+
if (action === "restore")
|
|
724
|
+
return {
|
|
725
|
+
kind: "source-restore",
|
|
726
|
+
projectId,
|
|
727
|
+
request: parseManagedSourceRequest("restore", {
|
|
728
|
+
...common,
|
|
729
|
+
restore_commit_sha: options["--restore-commit"],
|
|
730
|
+
}),
|
|
731
|
+
idempotencyKey,
|
|
732
|
+
wait,
|
|
733
|
+
credentialStore,
|
|
734
|
+
};
|
|
735
|
+
return {
|
|
736
|
+
kind: "source-publish",
|
|
737
|
+
projectId,
|
|
738
|
+
directory: options["--directory"] ?? ".",
|
|
739
|
+
request: parseManagedSourceRequest("upload", {
|
|
740
|
+
...common,
|
|
741
|
+
mode: options["--mode"],
|
|
742
|
+
expected_source_connection_id: options["--expected-source-connection"] ?? null,
|
|
743
|
+
}),
|
|
744
|
+
explicitExpectedCommit: options["--expected-commit"] !== undefined,
|
|
745
|
+
idempotencyKey,
|
|
746
|
+
wait,
|
|
747
|
+
credentialStore,
|
|
748
|
+
};
|
|
749
|
+
} catch {
|
|
750
|
+
throw new InvalidCommandError();
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
if (wait || !["status", "files", "file", "versions", "diff", "upload"].includes(action ?? ""))
|
|
754
|
+
throw new InvalidCommandError();
|
|
755
|
+
const commitSha = options["--commit"],
|
|
756
|
+
path = options["--path"],
|
|
757
|
+
beforeCommitSha = options["--before"],
|
|
758
|
+
fromCommitSha = options["--from"],
|
|
759
|
+
toCommitSha = options["--to"],
|
|
760
|
+
operationId = options["--operation"];
|
|
761
|
+
const permitted =
|
|
762
|
+
action === "status"
|
|
763
|
+
? ["--project"]
|
|
764
|
+
: action === "versions"
|
|
765
|
+
? ["--project", "--limit", "--before"]
|
|
766
|
+
: action === "diff"
|
|
767
|
+
? ["--project", "--from", "--to"]
|
|
768
|
+
: action === "upload"
|
|
769
|
+
? ["--project", "--operation"]
|
|
770
|
+
: action === "file"
|
|
771
|
+
? ["--project", "--commit", "--path"]
|
|
772
|
+
: ["--project", "--commit"];
|
|
773
|
+
if (
|
|
774
|
+
Object.keys(options).some((key) => !permitted.includes(key)) ||
|
|
775
|
+
[commitSha, beforeCommitSha, fromCommitSha, toCommitSha].some(
|
|
776
|
+
(value) => value !== undefined && !/^[a-f0-9]{40}$/u.test(value),
|
|
777
|
+
) ||
|
|
778
|
+
(action === "file" && !isManagedSourcePath(path)) ||
|
|
779
|
+
(action === "diff" && (fromCommitSha === undefined || toCommitSha === undefined)) ||
|
|
780
|
+
(action === "upload" &&
|
|
781
|
+
(operationId === undefined || !/^[0-7][0-9A-HJKMNP-TV-Z]{25}$/u.test(operationId)))
|
|
782
|
+
)
|
|
783
|
+
throw new InvalidCommandError();
|
|
784
|
+
const limit = options["--limit"] === undefined ? undefined : Number(options["--limit"]);
|
|
785
|
+
if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 1 || limit > 100))
|
|
786
|
+
throw new InvalidCommandError();
|
|
787
|
+
return {
|
|
788
|
+
kind: "source-read",
|
|
789
|
+
action: action as Extract<SourceCliCommand, { kind: "source-read" }>["action"],
|
|
790
|
+
projectId,
|
|
791
|
+
commitSha,
|
|
792
|
+
path,
|
|
793
|
+
beforeCommitSha,
|
|
794
|
+
fromCommitSha,
|
|
795
|
+
toCommitSha,
|
|
796
|
+
operationId,
|
|
797
|
+
limit,
|
|
798
|
+
credentialStore,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
574
802
|
export type RecognizedCommandLabel =
|
|
803
|
+
| "source inspect"
|
|
804
|
+
| "source status"
|
|
805
|
+
| "source files"
|
|
806
|
+
| "source file"
|
|
807
|
+
| "source versions"
|
|
808
|
+
| "source diff"
|
|
809
|
+
| "source upload"
|
|
810
|
+
| "source publish"
|
|
811
|
+
| "source publish complete"
|
|
812
|
+
| "source initialize"
|
|
813
|
+
| "source restore"
|
|
575
814
|
| "project data plan"
|
|
576
815
|
| "project data change"
|
|
577
816
|
| "database compute set"
|
|
@@ -768,6 +1007,24 @@ export const recognizedCommandLabel = (argv: readonly string[]): RecognizedComma
|
|
|
768
1007
|
if (value === "database" && values[0] === "psql") return "database psql";
|
|
769
1008
|
if (value === "link" || value === "plan" || value === "deploy" || value === "logs")
|
|
770
1009
|
return value;
|
|
1010
|
+
if (value === "source" && values[0] === "publish" && values[1] === "complete")
|
|
1011
|
+
return "source publish complete";
|
|
1012
|
+
if (
|
|
1013
|
+
value === "source" &&
|
|
1014
|
+
[
|
|
1015
|
+
"inspect",
|
|
1016
|
+
"status",
|
|
1017
|
+
"files",
|
|
1018
|
+
"file",
|
|
1019
|
+
"versions",
|
|
1020
|
+
"diff",
|
|
1021
|
+
"upload",
|
|
1022
|
+
"publish",
|
|
1023
|
+
"initialize",
|
|
1024
|
+
"restore",
|
|
1025
|
+
].includes(values[0] ?? "")
|
|
1026
|
+
)
|
|
1027
|
+
return `source ${values[0]}` as RecognizedCommandLabel;
|
|
771
1028
|
if (value === "source" && values[0] === "auto-deploy") {
|
|
772
1029
|
if (values[1] === "set") return "source auto-deploy set";
|
|
773
1030
|
if (values[1] === "status") return "source auto-deploy status";
|
|
@@ -842,6 +1099,10 @@ export const parseProductCliCommand = (argv: readonly string[]): ProductCliComma
|
|
|
842
1099
|
const follow = removeFlag(values, "--follow");
|
|
843
1100
|
const stdin = removeFlag(values, "--stdin");
|
|
844
1101
|
const first = values.shift();
|
|
1102
|
+
if (first === "source" && values[0] !== "auto-deploy") {
|
|
1103
|
+
if (dryRun || revoke || yes || follow || stdin) throw new InvalidCommandError();
|
|
1104
|
+
return parseSourceCommand(values, wait);
|
|
1105
|
+
}
|
|
845
1106
|
if (
|
|
846
1107
|
first === "whoami" &&
|
|
847
1108
|
!dryRun &&
|
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { join } from "node:path";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { inspectSourceWorkspace, SourceWorkspaceError } from "./source-workspace.js";
|
|
4
|
+
import {
|
|
5
|
+
inspectLocalSourcePublications,
|
|
6
|
+
LocalSourcePublicationError,
|
|
7
|
+
} from "./source-publication-store.js";
|
|
3
8
|
import {
|
|
4
9
|
createClient,
|
|
5
10
|
getGithubOrganizationConnection,
|
|
6
11
|
getProject,
|
|
12
|
+
getProjectSource,
|
|
7
13
|
revokeCurrentSession,
|
|
8
14
|
} from "@ohmyhost/sdk-ts";
|
|
9
15
|
import { WORKOS_USER_API_KEY_PATTERN } from "@ohmyhost/workos-auth-contracts/user-api-keys";
|
|
@@ -558,6 +564,14 @@ export const runProductionCli = async (
|
|
|
558
564
|
return written(invalidCommandFailure(argv, environment));
|
|
559
565
|
}
|
|
560
566
|
const workingDirectory = overrides.workingDirectory ?? process.cwd();
|
|
567
|
+
const directoryOption = args.indexOf("--directory");
|
|
568
|
+
const selectedSourceDirectory =
|
|
569
|
+
args[0] === "source" &&
|
|
570
|
+
args[1] === "publish" &&
|
|
571
|
+
directoryOption !== -1 &&
|
|
572
|
+
args[directoryOption + 1] !== undefined
|
|
573
|
+
? resolve(workingDirectory, args[directoryOption + 1] as string)
|
|
574
|
+
: workingDirectory;
|
|
561
575
|
let command: ProductCliCommand;
|
|
562
576
|
let linked: Extract<LinkedProject, { outcome: "linked" }> | null;
|
|
563
577
|
let links: DirectoryLinks = { links: [], legacy: null };
|
|
@@ -570,7 +584,7 @@ export const runProductionCli = async (
|
|
|
570
584
|
return { outcome: "none" };
|
|
571
585
|
}
|
|
572
586
|
links = await readProjectLinks(
|
|
573
|
-
join(
|
|
587
|
+
join(selectedSourceDirectory, platform.linkDirectory),
|
|
574
588
|
platform.apiOrigin,
|
|
575
589
|
);
|
|
576
590
|
let named = profileName;
|
|
@@ -649,6 +663,22 @@ export const runProductionCli = async (
|
|
|
649
663
|
);
|
|
650
664
|
return runOfflineInit(command, io, diagnostics);
|
|
651
665
|
}
|
|
666
|
+
if (command.kind === "source-inspect") {
|
|
667
|
+
try {
|
|
668
|
+
const workspace = await inspectSourceWorkspace(resolve(workingDirectory, command.directory));
|
|
669
|
+
return written({
|
|
670
|
+
exitCode: 0,
|
|
671
|
+
stdout: `${JSON.stringify({ version: 1, command: "source inspect", status: "inspected", workspace, pending_publications: await inspectLocalSourcePublications(workspace.directory) })}\n`,
|
|
672
|
+
stderr: "",
|
|
673
|
+
});
|
|
674
|
+
} catch (error) {
|
|
675
|
+
return written(
|
|
676
|
+
error instanceof SourceWorkspaceError || error instanceof LocalSourcePublicationError
|
|
677
|
+
? errorDocument("source inspect", 2, error.code, false, error.message)
|
|
678
|
+
: classifyCliFailure("source inspect", error, signal),
|
|
679
|
+
);
|
|
680
|
+
}
|
|
681
|
+
}
|
|
652
682
|
if (command.kind === "help" || command.kind === "version") {
|
|
653
683
|
return written(
|
|
654
684
|
cliMetadataResult(command.kind, command.kind === "help" ? command.topic : undefined),
|
|
@@ -726,7 +756,7 @@ export const runProductionCli = async (
|
|
|
726
756
|
);
|
|
727
757
|
const registry =
|
|
728
758
|
overrides.profiles ?? new NativeKeyringProfileRegistry(undefined, platform.credentialService);
|
|
729
|
-
const linkDirectory = join(
|
|
759
|
+
const linkDirectory = join(selectedSourceDirectory, platform.linkDirectory);
|
|
730
760
|
const projectId = commandProjectId(command);
|
|
731
761
|
// A command that named its project read no link yet, but the link still says whose project it is.
|
|
732
762
|
if (projectId !== undefined && linked === null)
|
|
@@ -843,6 +873,16 @@ export const runProductionCli = async (
|
|
|
843
873
|
return written(result);
|
|
844
874
|
}
|
|
845
875
|
const selected = profile;
|
|
876
|
+
if (command.kind === "whoami" || command.kind === "login")
|
|
877
|
+
links = await readProjectLinks(linkDirectory, platform.apiOrigin);
|
|
878
|
+
const sourceOrganizationId =
|
|
879
|
+
selected?.organizationId ?? (command.kind === "login" ? command.organizationId : undefined);
|
|
880
|
+
const boundSource =
|
|
881
|
+
sourceOrganizationId === null || sourceOrganizationId === undefined
|
|
882
|
+
? links.links.length === 1
|
|
883
|
+
? links.links[0]
|
|
884
|
+
: undefined
|
|
885
|
+
: links.links.find((link) => link.organization_id === sourceOrganizationId);
|
|
846
886
|
const productApiOrigin = platform.apiOrigin;
|
|
847
887
|
const lifecycle = new GeneratedSdkSessionRevoker({
|
|
848
888
|
environment,
|
|
@@ -850,6 +890,15 @@ export const runProductionCli = async (
|
|
|
850
890
|
...(selected === undefined ? {} : { request: { name: selected.name } }),
|
|
851
891
|
});
|
|
852
892
|
const cli = new ProductCli({
|
|
893
|
+
sourceDirectory: workingDirectory,
|
|
894
|
+
sourceMetadataDirectory: platform.linkDirectory,
|
|
895
|
+
sourceWorkspace: () => inspectSourceWorkspace(workingDirectory),
|
|
896
|
+
...(boundSource === undefined
|
|
897
|
+
? {}
|
|
898
|
+
: {
|
|
899
|
+
linkedSourceProvider: boundSource.version === 3 ? boundSource.source.provider : "github",
|
|
900
|
+
linkedProjectId: boundSource.project_id,
|
|
901
|
+
}),
|
|
853
902
|
...(apiToken === undefined ? {} : { apiToken }),
|
|
854
903
|
commandPrefix: platform.commandPrefix,
|
|
855
904
|
profiles: registry,
|
|
@@ -895,6 +944,19 @@ export const runProductionCli = async (
|
|
|
895
944
|
signal,
|
|
896
945
|
},
|
|
897
946
|
),
|
|
947
|
+
projectSourceStatus: (accessToken, projectId) =>
|
|
948
|
+
getProjectSource(
|
|
949
|
+
{ project_id: projectId, include_binding: true },
|
|
950
|
+
{
|
|
951
|
+
client: createClient({
|
|
952
|
+
baseUrl: productApiOrigin,
|
|
953
|
+
auth: accessToken,
|
|
954
|
+
throwOnError: true,
|
|
955
|
+
fetch: boundedFetch(),
|
|
956
|
+
}),
|
|
957
|
+
signal,
|
|
958
|
+
},
|
|
959
|
+
),
|
|
898
960
|
projectLinkStore: new OwnerOnlyProjectLinkStore(linkDirectory, workingDirectory),
|
|
899
961
|
productApiOrigin,
|
|
900
962
|
tokenRefresher: refresher,
|
|
@@ -1351,7 +1413,15 @@ export const createBoundedFetch =
|
|
|
1351
1413
|
if (isAbortOrTimeout(error)) throw error;
|
|
1352
1414
|
throw new NetworkTransportError();
|
|
1353
1415
|
}
|
|
1354
|
-
|
|
1416
|
+
const requestUrl = new URL(input instanceof Request ? input.url : String(input));
|
|
1417
|
+
const sourceRead =
|
|
1418
|
+
/^\/v1\/projects\/[0-7][0-9A-HJKMNP-TV-Z]{25}\/source\/(?:files|diff|versions)$/u.test(
|
|
1419
|
+
requestUrl.pathname,
|
|
1420
|
+
);
|
|
1421
|
+
const limit = sourceRead
|
|
1422
|
+
? Math.max(responseLimit, Math.ceil((2 * 1024 * 1024) / 3) * 4 + 64 * 1024)
|
|
1423
|
+
: responseLimit;
|
|
1424
|
+
return streaming ? response : bufferBoundedResponse(response, limit);
|
|
1355
1425
|
};
|
|
1356
1426
|
|
|
1357
1427
|
const bufferBoundedResponse = async (response: Response, limit: number): Promise<Response> => {
|
|
@@ -8,6 +8,10 @@ export * from "./profile-store.js";
|
|
|
8
8
|
export * from "./project-link-store.js";
|
|
9
9
|
export * from "./repository-init.js";
|
|
10
10
|
export * from "./workspace.js";
|
|
11
|
+
export * from "./source-workspace.js";
|
|
12
|
+
export * from "./source-publisher.js";
|
|
13
|
+
export * from "./source-managed-client.js";
|
|
14
|
+
export * from "./source-publication-store.js";
|
|
11
15
|
export type { PublicAccount } from "./runtime-contract.js";
|
|
12
16
|
export {
|
|
13
17
|
parseAccountProfile,
|
|
@@ -24,6 +28,8 @@ export {
|
|
|
24
28
|
parseOperationEvent,
|
|
25
29
|
parseSafeProblem,
|
|
26
30
|
parseProjectDataChangePlan,
|
|
31
|
+
parseProjectSource,
|
|
32
|
+
parseProjectSourceBinding,
|
|
27
33
|
} from "./runtime-contract.js";
|
|
28
34
|
|
|
29
35
|
export * from "./user-token-file.js";
|
|
@@ -143,6 +143,8 @@ import {
|
|
|
143
143
|
parseOperation,
|
|
144
144
|
type PublicAccount,
|
|
145
145
|
} from "./runtime-contract.js";
|
|
146
|
+
import { GeneratedSdkManagedSourceApi } from "./source-managed-client.js";
|
|
147
|
+
import type { ManagedSourceApi } from "./source-publisher.js";
|
|
146
148
|
|
|
147
149
|
export interface ProjectCreateInput {
|
|
148
150
|
readonly organizationId: string;
|
|
@@ -154,6 +156,7 @@ export interface ProjectCreateInput {
|
|
|
154
156
|
}
|
|
155
157
|
|
|
156
158
|
export interface ProductApi {
|
|
159
|
+
readonly managedSource?: ManagedSourceApi;
|
|
157
160
|
planProjectDataChange(input: {
|
|
158
161
|
projectId: string;
|
|
159
162
|
change: ProjectDataChangeKind;
|
|
@@ -436,6 +439,7 @@ export interface ProductApi {
|
|
|
436
439
|
|
|
437
440
|
export class GeneratedSdkProductApi implements ProductApi {
|
|
438
441
|
readonly #client: OhMyHostClient;
|
|
442
|
+
public readonly managedSource: ManagedSourceApi;
|
|
439
443
|
|
|
440
444
|
public constructor(
|
|
441
445
|
baseUrl: string,
|
|
@@ -443,6 +447,7 @@ export class GeneratedSdkProductApi implements ProductApi {
|
|
|
443
447
|
fetch: typeof globalThis.fetch,
|
|
444
448
|
) {
|
|
445
449
|
this.#client = createClient({ auth: accessToken, baseUrl, fetch, throwOnError: true });
|
|
450
|
+
this.managedSource = new GeneratedSdkManagedSourceApi(() => this.#client);
|
|
446
451
|
}
|
|
447
452
|
|
|
448
453
|
public async changeDatabaseCompute(input: {
|
|
@@ -15,7 +15,7 @@ import { join } from "node:path";
|
|
|
15
15
|
* Which project this working copy deploys to for one organization. The same checkout may be linked
|
|
16
16
|
* once per organization, so a second account never overwrites or inherits the first one's link.
|
|
17
17
|
*/
|
|
18
|
-
export interface
|
|
18
|
+
export interface LegacyScopedProjectLink {
|
|
19
19
|
readonly version: 2;
|
|
20
20
|
readonly api_origin: string;
|
|
21
21
|
readonly organization_id: string;
|
|
@@ -24,6 +24,33 @@ export interface ProjectLinkMetadata {
|
|
|
24
24
|
readonly repository_full_name: string;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
/** Provider binding follows the project; managed sources also cache this directory's confirmed base. */
|
|
28
|
+
export interface CurrentProjectLink {
|
|
29
|
+
readonly version: 3;
|
|
30
|
+
readonly api_origin: string;
|
|
31
|
+
readonly organization_id: string;
|
|
32
|
+
readonly project_id: string;
|
|
33
|
+
readonly source:
|
|
34
|
+
| {
|
|
35
|
+
readonly provider: "managed";
|
|
36
|
+
readonly source_connection_id: string;
|
|
37
|
+
readonly source_generation: number;
|
|
38
|
+
readonly namespace: string;
|
|
39
|
+
readonly repository_name: string;
|
|
40
|
+
readonly branch: "main";
|
|
41
|
+
readonly last_confirmed_commit_sha: string | null;
|
|
42
|
+
}
|
|
43
|
+
| {
|
|
44
|
+
readonly provider: "github";
|
|
45
|
+
readonly source_connection_id: string;
|
|
46
|
+
readonly source_generation: number;
|
|
47
|
+
readonly installation_id: string;
|
|
48
|
+
readonly repository_full_name: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export type ProjectLinkMetadata = LegacyScopedProjectLink | CurrentProjectLink;
|
|
53
|
+
|
|
27
54
|
/** A link written before links named their organization; it is verified before any use. */
|
|
28
55
|
export interface LegacyProjectLink {
|
|
29
56
|
readonly project_id: string;
|
|
@@ -60,6 +87,10 @@ export async function readProjectLinks(
|
|
|
60
87
|
const organizationId = /^([0-7][0-9A-HJKMNP-TV-Z]{25})\.json$/u.exec(name)?.[1];
|
|
61
88
|
if (organizationId === undefined) continue;
|
|
62
89
|
const link = await readJson(join(directory, LINKS, name));
|
|
90
|
+
if (isCurrentLink(link, apiOrigin, organizationId)) {
|
|
91
|
+
links.push(link);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
63
94
|
if (
|
|
64
95
|
isRecord(link, [
|
|
65
96
|
"version",
|
|
@@ -100,6 +131,66 @@ export async function readProjectLinks(
|
|
|
100
131
|
};
|
|
101
132
|
}
|
|
102
133
|
|
|
134
|
+
function isCurrentLink(
|
|
135
|
+
value: Record<string, unknown> | null,
|
|
136
|
+
apiOrigin: string,
|
|
137
|
+
organizationId: string,
|
|
138
|
+
): value is unknown & CurrentProjectLink & Record<string, unknown> {
|
|
139
|
+
if (
|
|
140
|
+
!isRecord(value, ["version", "api_origin", "organization_id", "project_id", "source"]) ||
|
|
141
|
+
value["version"] !== 3 ||
|
|
142
|
+
value["api_origin"] !== apiOrigin ||
|
|
143
|
+
value["organization_id"] !== organizationId ||
|
|
144
|
+
typeof value["project_id"] !== "string" ||
|
|
145
|
+
!ULID.test(value["project_id"])
|
|
146
|
+
)
|
|
147
|
+
return false;
|
|
148
|
+
const candidate = value["source"];
|
|
149
|
+
if (typeof candidate !== "object" || candidate === null || !("provider" in candidate))
|
|
150
|
+
return false;
|
|
151
|
+
const source = candidate as Record<string, unknown>;
|
|
152
|
+
const keys =
|
|
153
|
+
source["provider"] === "managed"
|
|
154
|
+
? [
|
|
155
|
+
"provider",
|
|
156
|
+
"source_connection_id",
|
|
157
|
+
"source_generation",
|
|
158
|
+
"namespace",
|
|
159
|
+
"repository_name",
|
|
160
|
+
"branch",
|
|
161
|
+
"last_confirmed_commit_sha",
|
|
162
|
+
]
|
|
163
|
+
: [
|
|
164
|
+
"provider",
|
|
165
|
+
"source_connection_id",
|
|
166
|
+
"source_generation",
|
|
167
|
+
"installation_id",
|
|
168
|
+
"repository_full_name",
|
|
169
|
+
];
|
|
170
|
+
if (
|
|
171
|
+
!isRecord(source, keys) ||
|
|
172
|
+
typeof source["source_connection_id"] !== "string" ||
|
|
173
|
+
!ULID.test(source["source_connection_id"]) ||
|
|
174
|
+
!Number.isSafeInteger(source["source_generation"]) ||
|
|
175
|
+
Number(source["source_generation"]) < 1
|
|
176
|
+
)
|
|
177
|
+
return false;
|
|
178
|
+
return source["provider"] === "managed"
|
|
179
|
+
? source["branch"] === "main" &&
|
|
180
|
+
(source["last_confirmed_commit_sha"] === null ||
|
|
181
|
+
(typeof source["last_confirmed_commit_sha"] === "string" &&
|
|
182
|
+
/^[a-f0-9]{40}$/u.test(source["last_confirmed_commit_sha"]))) &&
|
|
183
|
+
typeof source["namespace"] === "string" &&
|
|
184
|
+
typeof source["repository_name"] === "string" &&
|
|
185
|
+
/^[a-z0-9][a-z0-9_-]{0,127}$/u.test(source["namespace"]) &&
|
|
186
|
+
/^[a-z0-9][a-z0-9_-]{0,127}$/u.test(source["repository_name"])
|
|
187
|
+
: source["provider"] === "github" &&
|
|
188
|
+
typeof source["installation_id"] === "string" &&
|
|
189
|
+
/^[1-9][0-9]{0,19}$/u.test(source["installation_id"]) &&
|
|
190
|
+
typeof source["repository_full_name"] === "string" &&
|
|
191
|
+
/^[^/]+\/[^/]+$/u.test(source["repository_full_name"]);
|
|
192
|
+
}
|
|
193
|
+
|
|
103
194
|
export type LinkedProject =
|
|
104
195
|
| {
|
|
105
196
|
readonly outcome: "linked";
|
|
@@ -1163,6 +1163,18 @@ function repositoryBlockers(
|
|
|
1163
1163
|
existingConfiguration: OhmyhostConfig | null,
|
|
1164
1164
|
): RepositoryInitBlocker[] {
|
|
1165
1165
|
const blockers: RepositoryInitBlocker[] = [];
|
|
1166
|
+
if (
|
|
1167
|
+
framework !== "nextjs" &&
|
|
1168
|
+
existingConfiguration !== null &&
|
|
1169
|
+
"command" in existingConfiguration.build &&
|
|
1170
|
+
existingConfiguration.build.ssg_cache_max_mib !== undefined
|
|
1171
|
+
) {
|
|
1172
|
+
blockers.push({
|
|
1173
|
+
code: "framework_unsupported",
|
|
1174
|
+
message:
|
|
1175
|
+
"build.ssg_cache_max_mib is supported only by Next.js; remove it from this application's configuration.",
|
|
1176
|
+
});
|
|
1177
|
+
}
|
|
1166
1178
|
if (framework === "unknown") {
|
|
1167
1179
|
blockers.push({
|
|
1168
1180
|
code: "framework_unsupported",
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { parseFeedbackSubmissionDetail } from "@ohmyhost/contracts/feedback";
|
|
2
|
+
import {
|
|
3
|
+
assertGithubSourceResponse,
|
|
4
|
+
assertManagedSourceResponse,
|
|
5
|
+
} from "@ohmyhost/contracts/managed-sources";
|
|
2
6
|
import {
|
|
3
7
|
parseProjectDataChangePlan as readProjectDataChangePlan,
|
|
4
8
|
type ProjectDataChangePlan,
|
|
@@ -822,7 +826,20 @@ export const parseProjectStatus = (value: unknown): Readonly<Record<string, unkn
|
|
|
822
826
|
return input;
|
|
823
827
|
};
|
|
824
828
|
|
|
825
|
-
const parseProjectSource = (value: unknown): void => {
|
|
829
|
+
export const parseProjectSource = (value: unknown): void => {
|
|
830
|
+
if (
|
|
831
|
+
typeof value === "object" &&
|
|
832
|
+
value !== null &&
|
|
833
|
+
"provider" in value &&
|
|
834
|
+
value.provider === "managed"
|
|
835
|
+
) {
|
|
836
|
+
try {
|
|
837
|
+
assertManagedSourceResponse("source", value);
|
|
838
|
+
} catch {
|
|
839
|
+
throw new ResponseContractError();
|
|
840
|
+
}
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
826
843
|
const input = exactRecord(value, [
|
|
827
844
|
"provider",
|
|
828
845
|
"installation_id",
|
|
@@ -850,6 +867,22 @@ const parseProjectSource = (value: unknown): void => {
|
|
|
850
867
|
}
|
|
851
868
|
};
|
|
852
869
|
|
|
870
|
+
/** Binding-aware reads opt in explicitly; a legacy GitHub projection cannot supply a fence. */
|
|
871
|
+
export const parseProjectSourceBinding = (value: unknown): void => {
|
|
872
|
+
try {
|
|
873
|
+
if (
|
|
874
|
+
typeof value === "object" &&
|
|
875
|
+
value !== null &&
|
|
876
|
+
"provider" in value &&
|
|
877
|
+
value.provider === "managed"
|
|
878
|
+
)
|
|
879
|
+
assertManagedSourceResponse("source", value);
|
|
880
|
+
else assertGithubSourceResponse("binding", value);
|
|
881
|
+
} catch {
|
|
882
|
+
throw new ResponseContractError();
|
|
883
|
+
}
|
|
884
|
+
};
|
|
885
|
+
|
|
853
886
|
const parseProjectDeployment = (value: unknown): void => {
|
|
854
887
|
const input = exactRecord(value, [
|
|
855
888
|
"id",
|