@sequenceholdings/studio-cli 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/README.md +258 -0
  2. package/dist/artifact/delegate.d.ts +25 -0
  3. package/dist/artifact/delegate.js +263 -0
  4. package/dist/atlas-client.d.ts +44 -0
  5. package/dist/atlas-client.js +173 -0
  6. package/dist/auth-cmds/commands.d.ts +15 -0
  7. package/dist/auth-cmds/commands.js +249 -0
  8. package/dist/auth.d.ts +26 -0
  9. package/dist/auth.js +171 -0
  10. package/dist/bin.d.ts +2 -0
  11. package/dist/bin.js +8 -0
  12. package/dist/cli-errors.d.ts +5 -0
  13. package/dist/cli-errors.js +78 -0
  14. package/dist/config.d.ts +44 -0
  15. package/dist/config.js +103 -0
  16. package/dist/env-flags.d.ts +8 -0
  17. package/dist/env-flags.js +47 -0
  18. package/dist/functions/bundle.d.ts +30 -0
  19. package/dist/functions/bundle.js +137 -0
  20. package/dist/functions/commands.d.ts +86 -0
  21. package/dist/functions/commands.js +999 -0
  22. package/dist/functions/egress-preview.d.ts +32 -0
  23. package/dist/functions/egress-preview.js +54 -0
  24. package/dist/functions/lockfile-origin.d.ts +16 -0
  25. package/dist/functions/lockfile-origin.js +45 -0
  26. package/dist/functions/manifest.d.ts +89 -0
  27. package/dist/functions/manifest.js +586 -0
  28. package/dist/functions/secret-reconcile.d.ts +79 -0
  29. package/dist/functions/secret-reconcile.js +86 -0
  30. package/dist/main.d.ts +14 -0
  31. package/dist/main.js +129 -0
  32. package/dist/orm/delegate.d.ts +8 -0
  33. package/dist/orm/delegate.js +61 -0
  34. package/dist/pat-hints.d.ts +17 -0
  35. package/dist/pat-hints.js +28 -0
  36. package/dist/preview.d.ts +89 -0
  37. package/dist/preview.js +291 -0
  38. package/dist/process/agent-loader.d.ts +24 -0
  39. package/dist/process/agent-loader.js +57 -0
  40. package/dist/process/build.d.ts +14 -0
  41. package/dist/process/build.js +368 -0
  42. package/dist/process/codegen.d.ts +18 -0
  43. package/dist/process/codegen.js +270 -0
  44. package/dist/process/commands.d.ts +47 -0
  45. package/dist/process/commands.js +786 -0
  46. package/dist/process/discover.d.ts +32 -0
  47. package/dist/process/discover.js +131 -0
  48. package/dist/process/lint.d.ts +39 -0
  49. package/dist/process/lint.js +485 -0
  50. package/dist/process/local-bundle.d.ts +17 -0
  51. package/dist/process/local-bundle.js +65 -0
  52. package/dist/process/plan-diff.d.ts +82 -0
  53. package/dist/process/plan-diff.js +333 -0
  54. package/dist/process/resolve-process-pin.d.ts +11 -0
  55. package/dist/process/resolve-process-pin.js +63 -0
  56. package/dist/process/simulate.d.ts +50 -0
  57. package/dist/process/simulate.js +328 -0
  58. package/dist/prompt.d.ts +35 -0
  59. package/dist/prompt.js +65 -0
  60. package/dist/repos/commands.d.ts +49 -0
  61. package/dist/repos/commands.js +548 -0
  62. package/dist/repos/git-clone.d.ts +10 -0
  63. package/dist/repos/git-clone.js +49 -0
  64. package/dist/secrets/commands.d.ts +24 -0
  65. package/dist/secrets/commands.js +704 -0
  66. package/dist/templates/process/example-process/process.ts +43 -0
  67. package/dist/templates/process/package.json +23 -0
  68. package/dist/templates/process/pnpm-workspace.yaml +21 -0
  69. package/dist/templates/process/tsconfig.json +17 -0
  70. package/package.json +78 -0
  71. package/templates/process/example-process/process.ts +43 -0
  72. package/templates/process/package.json +23 -0
  73. package/templates/process/pnpm-workspace.yaml +21 -0
  74. package/templates/process/tsconfig.json +17 -0
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Pure secret classifier for deploy-time reconciliation.
3
+ *
4
+ * This module performs no IO. The caller fetches the inputs (manifest,
5
+ * parsed .env, server secret list, current function attachments) and
6
+ * passes them here to get a categorised view that drives the deploy
7
+ * preview and the reconcile step.
8
+ */
9
+ /**
10
+ * Categorise each manifest-declared secret into one of four states:
11
+ *
12
+ * UPLOAD_NEW — in .env, no server default → create + set + attach via /apply
13
+ * OVERWRITE — in .env, server default exists → overwrite + attach if missing via /apply
14
+ * USE_EXISTING — not in .env, server default exists → attach only (no value change)
15
+ * BLOCKED — not in .env, no server default → unsatisfiable; deploy must abort
16
+ */
17
+ export function classifySecrets({ manifestSecrets, envValues, serverInfoByName, attachedByEnvVar, }) {
18
+ const secrets = [];
19
+ const blocked = [];
20
+ const seen = new Set();
21
+ for (const name of manifestSecrets) {
22
+ if (seen.has(name))
23
+ continue;
24
+ seen.add(name);
25
+ const inEnv = Boolean(envValues[name]?.length);
26
+ const serverInfo = serverInfoByName.get(name);
27
+ const serverHasDefault = Boolean(serverInfo?.hasDefaultValue);
28
+ // Only count as attached when the env var is mapped to this exact secret.
29
+ const attachedToFn = serverInfo !== undefined && attachedByEnvVar.get(name) === serverInfo.id;
30
+ const secretId = serverInfo?.id ?? null;
31
+ const attachmentCount = serverInfo?.attachmentCount ?? 0;
32
+ let category;
33
+ if (inEnv && !serverHasDefault) {
34
+ category = 'UPLOAD_NEW';
35
+ }
36
+ else if (inEnv && serverHasDefault) {
37
+ category = 'OVERWRITE';
38
+ }
39
+ else if (!inEnv && serverHasDefault) {
40
+ category = 'USE_EXISTING';
41
+ }
42
+ else {
43
+ category = 'BLOCKED';
44
+ }
45
+ const entry = { name, category, attachedToFn, secretId, attachmentCount };
46
+ if (category === 'BLOCKED') {
47
+ blocked.push(entry);
48
+ }
49
+ else {
50
+ secrets.push(entry);
51
+ }
52
+ }
53
+ return { secrets, blocked };
54
+ }
55
+ /**
56
+ * Build the preview lines for the secrets section of the deploy preview.
57
+ *
58
+ * @param log The LOG prefix string (e.g. '[seq-studio]').
59
+ * @param secrets Non-BLOCKED classified secrets.
60
+ */
61
+ export function buildSecretPreviewLines(log, secrets) {
62
+ if (secrets.length === 0) {
63
+ return [`${log} secrets: (none declared)`];
64
+ }
65
+ const lines = [`${log} secrets:`];
66
+ for (const c of secrets) {
67
+ let desc;
68
+ if (c.category === 'UPLOAD_NEW') {
69
+ desc = 'new — will upload from .env and attach';
70
+ }
71
+ else if (c.category === 'OVERWRITE') {
72
+ // Compute how many OTHER functions the existing default serves.
73
+ const otherCount = c.attachmentCount - (c.attachedToFn ? 1 : 0);
74
+ const sharedNote = otherCount > 0 ? ` — also used by ${otherCount} other function(s)` : '';
75
+ desc = `will overwrite existing default${sharedNote}`;
76
+ }
77
+ else {
78
+ // USE_EXISTING
79
+ desc = c.attachedToFn
80
+ ? 'uses existing default, no change'
81
+ : 'uses existing default, linking to function (value unchanged)';
82
+ }
83
+ lines.push(`${log} - ${c.name}: ${desc}`);
84
+ }
85
+ return lines;
86
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ /**
2
+ * seq-studio argv router.
3
+ *
4
+ * Top-level commands:
5
+ * seq-studio process <sub> manage Lattice processes
6
+ * seq-studio artifact <sub> manage Artifact Studio apps
7
+ * seq-studio functions <sub> manage Managed Functions
8
+ * seq-studio secrets <sub> manage org-owned Managed Secrets
9
+ * seq-studio repos <sub> manage platform git-service repos
10
+ * seq-studio auth <sub> manage git-service PATs
11
+ * seq-studio doctor check token + env + writer gate
12
+ * seq-studio help show usage
13
+ */
14
+ export declare function run(argv?: string[]): Promise<number>;
package/dist/main.js ADDED
@@ -0,0 +1,129 @@
1
+ /**
2
+ * seq-studio argv router.
3
+ *
4
+ * Top-level commands:
5
+ * seq-studio process <sub> manage Lattice processes
6
+ * seq-studio artifact <sub> manage Artifact Studio apps
7
+ * seq-studio functions <sub> manage Managed Functions
8
+ * seq-studio secrets <sub> manage org-owned Managed Secrets
9
+ * seq-studio repos <sub> manage platform git-service repos
10
+ * seq-studio auth <sub> manage git-service PATs
11
+ * seq-studio doctor check token + env + writer gate
12
+ * seq-studio help show usage
13
+ */
14
+ import { parseArgs } from './process/commands.js';
15
+ import { applyCommand, bundleCommand, doctorCommand, initCommand, lintCommand, planCommand, promoteCommand, pullCommand, simulateCommand, testCommand, } from './process/commands.js';
16
+ // Lazy-load artifact delegate: published @sequenceholdings/artifact-studio/cli
17
+ // still auto-runs runCli() at module load; importing it here breaks doctor/process.
18
+ const TOP_LEVEL_USAGE = `usage:
19
+ seq-studio process <sub> [args] lint | plan | apply | test | simulate | bundle | init
20
+ seq-studio artifact <sub> [args] init | build | plan | deploy | dev | list | show | pull | promote | rollback
21
+ seq-studio functions <sub> [args] init | build | deploy | list | show | logs | promote | rollback | delete
22
+ seq-studio secrets <sub> [args] create | set | list | attach | detach | apply
23
+ seq-studio repos <sub> [args] list | namespaces | show | create | clone | pull | delete
24
+ seq-studio auth <sub> [args] pat create | pat list | pat revoke
25
+ seq-studio orm <sub> [args] init | validate | plan | apply
26
+ seq-studio doctor [-e <env>] diagnose config, auth, and writer gate
27
+ seq-studio help show this message
28
+
29
+ Authenticate with: seqapi login
30
+ Env URLs come from ~/.config/lattice/config.toml (built-ins: local, staging, production, banksouth).
31
+ `;
32
+ const PROCESS_USAGE = `usage:
33
+ seq-studio process init <dir>
34
+ seq-studio process lint
35
+ seq-studio process plan [-e <env>]
36
+ seq-studio process apply [-e <env>] [--only <id1,id2,...>]
37
+ seq-studio process promote <processId> --version <v> [-e <env>]
38
+ seq-studio process pull <processId> [--version <v>] [-e <env>] [--out <dir>] [--no-children]
39
+ seq-studio process test [-e <env>] [--offline]
40
+ seq-studio process simulate <process-id>
41
+ seq-studio process bundle build [-o file.json]
42
+ seq-studio process bundle pull <hash> [-o file.json] [-e <env>]
43
+ seq-studio process bundle archive <hash> [-e <env>]
44
+ seq-studio process bundle inspect <bundle.json>
45
+ seq-studio process bundle list [-e <env>] [--limit N] [--cursor <hash>]
46
+ seq-studio process bundle publish <hash|bundle.json> [-e <env>]
47
+ `;
48
+ export async function run(argv = process.argv.slice(2)) {
49
+ const [namespace, sub, ...rest] = argv;
50
+ if (!namespace || namespace === 'help' || namespace === '--help' || namespace === '-h') {
51
+ console.log(TOP_LEVEL_USAGE);
52
+ return namespace ? 0 : 1;
53
+ }
54
+ switch (namespace) {
55
+ case 'process':
56
+ return runProcessNamespace(sub, rest);
57
+ case 'artifact': {
58
+ const { runArtifactCommand } = await import('./artifact/delegate.js');
59
+ return runArtifactCommand(sub, rest);
60
+ }
61
+ case 'functions': {
62
+ const { runFunctionsCommand } = await import('./functions/commands.js');
63
+ return runFunctionsCommand(sub, parseArgs(rest));
64
+ }
65
+ case 'secrets': {
66
+ const { runSecretsCommand } = await import('./secrets/commands.js');
67
+ return runSecretsCommand(sub, parseArgs(rest));
68
+ }
69
+ case 'repos': {
70
+ const { runReposCommand } = await import('./repos/commands.js');
71
+ return runReposCommand(sub, parseArgs(rest));
72
+ }
73
+ case 'auth': {
74
+ const { runAuthCommand } = await import('./auth-cmds/commands.js');
75
+ return runAuthCommand(sub, parseArgs(rest));
76
+ }
77
+ case 'orm': {
78
+ const { runOrmCommand } = await import('./orm/delegate.js');
79
+ return runOrmCommand(sub, rest);
80
+ }
81
+ case 'doctor':
82
+ return doctorCommand(parseArgs([sub, ...rest].filter(Boolean)));
83
+ default:
84
+ console.error(`unknown command: ${namespace}`);
85
+ console.error(TOP_LEVEL_USAGE);
86
+ return 1;
87
+ }
88
+ }
89
+ async function runProcessNamespace(sub, rest) {
90
+ if (!sub) {
91
+ console.error(PROCESS_USAGE);
92
+ return 1;
93
+ }
94
+ const args = parseArgs(sub === 'bundle' ? [sub, ...rest] : rest);
95
+ switch (sub) {
96
+ case 'init':
97
+ return initCommand(args);
98
+ case 'lint':
99
+ return lintCommand(args);
100
+ case 'plan':
101
+ return planCommand(args);
102
+ case 'apply':
103
+ return applyCommand(args);
104
+ case 'promote':
105
+ return promoteCommand(args);
106
+ case 'pull':
107
+ return pullCommand(args);
108
+ case 'test':
109
+ return testCommand(args);
110
+ case 'simulate':
111
+ return simulateCommand(args);
112
+ case 'bundle':
113
+ // bundle command needs the subcommand (build/pull/inspect) in positional[0].
114
+ // parseArgs already includes it because we threaded sub into args.
115
+ return bundleCommand({
116
+ positional: args.positional.slice(1),
117
+ flags: args.flags,
118
+ });
119
+ case 'help':
120
+ case '--help':
121
+ case '-h':
122
+ console.log(PROCESS_USAGE);
123
+ return 0;
124
+ default:
125
+ console.error(`unknown process command: ${sub}`);
126
+ console.error(PROCESS_USAGE);
127
+ return 1;
128
+ }
129
+ }
@@ -0,0 +1,8 @@
1
+ /**
2
+ * `seq-studio orm <sub>` — the Sequence ORM (OpCo data layer).
3
+ *
4
+ * Delegates to `runCli` from `@sequenceholdings/orm/cli` (same pattern as
5
+ * `artifact`): resolve `--env` from `~/.config/lattice/config.toml`, set
6
+ * SEQUENCE_ORM_BASE_URL, share the seqapi token, forward argv.
7
+ */
8
+ export declare function runOrmCommand(sub: string | undefined, rest: string[]): Promise<number>;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * `seq-studio orm <sub>` — the Sequence ORM (OpCo data layer).
3
+ *
4
+ * Delegates to `runCli` from `@sequenceholdings/orm/cli` (same pattern as
5
+ * `artifact`): resolve `--env` from `~/.config/lattice/config.toml`, set
6
+ * SEQUENCE_ORM_BASE_URL, share the seqapi token, forward argv.
7
+ */
8
+ import { getAccessToken, tryGetAccessToken } from '../auth.js';
9
+ import { readConfig, resolveEnv } from '../config.js';
10
+ import { normalizeShortEnvFlag, readEnvFromArgv } from '../env-flags.js';
11
+ const ORM_USAGE = `usage:
12
+ seq-studio orm init <dir> scaffold a namespace directory
13
+ seq-studio orm validate [dir] parse + validate definitions, print the content hash
14
+ seq-studio orm plan [dir] -e <env> compile definitions and diff against the registry
15
+ seq-studio orm diff [dir] write the next committed migration (--check verifies, --allow-destructive consents)
16
+ seq-studio orm apply [dir] -e <env> register the definitions and apply them to the env
17
+
18
+ Built-in envs: local, staging, production, banksouth.
19
+ Authenticate with: seqapi login
20
+ `;
21
+ export async function runOrmCommand(sub, rest) {
22
+ if (!sub || sub === 'help' || sub === '--help' || sub === '-h') {
23
+ console.log(ORM_USAGE);
24
+ return sub ? 0 : 1;
25
+ }
26
+ const normalized = normalizeShortEnvFlag(rest);
27
+ const requested = readEnvFromArgv(normalized);
28
+ if (requested) {
29
+ const config = await readConfig();
30
+ const resolved = resolveEnv({ config, requested });
31
+ process.env['SEQUENCE_ORM_BASE_URL'] = resolved.url;
32
+ }
33
+ const token = await tryGetAccessToken({ failClosedForM2m: true });
34
+ if (token)
35
+ process.env['SEQUENCE_ORM_TOKEN'] = token;
36
+ // Lazy import keeps `process`/`doctor` from pulling in the orm package.
37
+ // @sequenceholdings/orm is a private, optional peer — absent in public
38
+ // installs of the CLI, present in the monorepo (dev dep) and internal setups.
39
+ let ormCli;
40
+ try {
41
+ ormCli = await import('@sequenceholdings/orm/cli');
42
+ }
43
+ catch (err) {
44
+ if (err.code === 'ERR_MODULE_NOT_FOUND') {
45
+ console.error('seq-studio orm requires @sequenceholdings/orm, which is not included ' +
46
+ 'in this installation. It is available to Sequence-internal setups only.');
47
+ return 1;
48
+ }
49
+ throw err;
50
+ }
51
+ const { runCli, setTokenProvider } = ormCli;
52
+ setTokenProvider(async () => {
53
+ try {
54
+ return await getAccessToken();
55
+ }
56
+ catch {
57
+ return null;
58
+ }
59
+ });
60
+ return runCli([sub, ...normalized]);
61
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * User-facing hints for minting a git-service PAT.
3
+ *
4
+ * Third-party / OpCo developers typically do not have `seqapi`. Their path is
5
+ * the Atlas UI at `/settings/tokens`. Sequence staff can also use
6
+ * `seq-studio auth pat create` after `seqapi login`.
7
+ */
8
+ export declare function settingsTokensUrl(envUrl: string): string;
9
+ /**
10
+ * Multi-line setup instructions. Prefer the Atlas UI first (works for anyone
11
+ * with Atlas access); mention the CLI mint path as a staff convenience.
12
+ */
13
+ export declare function formatPatSetupHint({ envUrl, envName, indent, }: {
14
+ envUrl: string;
15
+ envName: string;
16
+ indent?: string;
17
+ }): string[];
@@ -0,0 +1,28 @@
1
+ /**
2
+ * User-facing hints for minting a git-service PAT.
3
+ *
4
+ * Third-party / OpCo developers typically do not have `seqapi`. Their path is
5
+ * the Atlas UI at `/settings/tokens`. Sequence staff can also use
6
+ * `seq-studio auth pat create` after `seqapi login`.
7
+ */
8
+ export function settingsTokensUrl(envUrl) {
9
+ return `${envUrl.replace(/\/$/, '')}/settings/tokens`;
10
+ }
11
+ /**
12
+ * Multi-line setup instructions. Prefer the Atlas UI first (works for anyone
13
+ * with Atlas access); mention the CLI mint path as a staff convenience.
14
+ */
15
+ export function formatPatSetupHint({ envUrl, envName, indent = ' ', }) {
16
+ const tokensUrl = settingsTokensUrl(envUrl);
17
+ return [
18
+ `${indent}Get a PAT (no seqapi required):`,
19
+ `${indent} 1. Open ${tokensUrl} and sign in to Atlas`,
20
+ `${indent} 2. New token → scopes repo:read (add repo:write for push) → copy once`,
21
+ `${indent} 3. export ATLAS_GIT_PAT=<token>`,
22
+ `${indent} 4. Copy the clone URL from Repositories → Clone, then:`,
23
+ `${indent} ATLAS_GIT_PAT=<token> seq-studio repos clone --url <https://…/repos/<id>/git>`,
24
+ `${indent} (or with seqapi: seq-studio repos clone <ns>/<name>)`,
25
+ `${indent}Sequence staff with seqapi can instead:`,
26
+ `${indent} seqapi login && seq-studio auth pat create --name laptop --scopes repo:read,repo:write -e ${envName}`,
27
+ ];
28
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Per-PR preview-environment resolution for `seq-studio artifact`.
3
+ *
4
+ * Lets an engineer target a specific PR's Vercel preview deployment
5
+ * (`https://studio-atlas-git-<slug>.preview.seqholdings.com`) WITHOUT
6
+ * hand-editing `~/.config/lattice/config.toml`. See
7
+ * `docs/preview-environments.md` for the full lifecycle.
8
+ *
9
+ * SLUG ALGORITHM — must stay byte-for-byte in sync with the three other
10
+ * places that compute the same slug from a branch name:
11
+ * - `.github/workflows/preview-deploy.yml` (`sed 's/[^a-zA-Z0-9-]/-/g' | tr '[:upper:]' '[:lower:]'`)
12
+ * - `atlas/src/server/db.ts:sanitize` (`replace(/[^a-zA-Z0-9-]/g, '-').toLowerCase()`)
13
+ * - `databricks/lakebase/atlas-db/pr-lifecycle.ts:sanitize`
14
+ * Changing it here without changing those produces a host that does NOT
15
+ * match what `preview-deploy.yml` actually deployed.
16
+ */
17
+ /** Cloudflare zone the preview aliases live on. */
18
+ export declare const PREVIEW_DOMAIN = "preview.seqholdings.com";
19
+ /** Vercel project name → the `<project>-git-<slug>` alias label prefix. */
20
+ export declare const PREVIEW_PROJECT = "studio-atlas";
21
+ /**
22
+ * Mirror of `preview-deploy.yml`'s `PROTECTED` list. A branch whose slug
23
+ * matches one of these (or starts with `dev-`) never gets a preview — the
24
+ * workflow's guard refuses to deploy it — so the CLI refuses to target it.
25
+ */
26
+ export declare const PREVIEW_PROTECTED_SLUGS: readonly ["banksouth", "staging", "dev", "production", "main", "development", "master"];
27
+ /**
28
+ * Vercel deploys the preview under the DNS label `studio-atlas-git-<slug>`.
29
+ * A single DNS label can be at most 63 chars; Vercel hash-truncates labels
30
+ * past that, so the resulting host can NOT be computed from the slug and must
31
+ * be read back from the deployment. This is the SAME cutoff the workflow uses
32
+ * (`[ "${#LABEL}" -le 63 ]`).
33
+ */
34
+ export declare const MAX_LABEL_LENGTH = 63;
35
+ /** Apply the canonical branch → slug transform. */
36
+ export declare function previewSlug(branchOrSlug: string): string;
37
+ /** The `studio-atlas-git-<slug>` DNS label Vercel assigns the preview. */
38
+ export declare function previewLabel(slug: string): string;
39
+ /** True for protected slugs that never get a preview (mirror of the workflow). */
40
+ export declare function isProtectedPreviewSlug(slug: string): boolean;
41
+ /**
42
+ * True when the slug is short enough that its preview host can be computed
43
+ * without reading the real alias back from Vercel (label ≤ 63 chars).
44
+ */
45
+ export declare function isComputablePreviewSlug(slug: string): boolean;
46
+ /** The computed preview host for a (short) slug. */
47
+ export declare function previewHost(slug: string): string;
48
+ export interface ResolvedPreview {
49
+ /** The canonical branch slug. */
50
+ slug: string;
51
+ /** Fully-qualified `https://…preview.seqholdings.com` URL. */
52
+ url: string;
53
+ }
54
+ /**
55
+ * Resolve a `preview:<branch-or-slug>` target to its computed preview URL.
56
+ * Throws for protected slugs and for long slugs whose alias can't be computed
57
+ * (the caller should fall back to `--pr <number>` or `--env-url <url>`).
58
+ */
59
+ export declare function resolvePreviewFromSlug(branchOrSlug: string): ResolvedPreview;
60
+ /** Injectable `gh` runner so PR resolution is unit-testable. */
61
+ export type GhRunner = (args: string[]) => Promise<string>;
62
+ interface GhPrComment {
63
+ body?: string;
64
+ author?: {
65
+ login?: string;
66
+ } | null;
67
+ createdAt?: string;
68
+ }
69
+ /**
70
+ * Find the real `*.preview.seqholdings.com` alias the Vercel bot posts on the
71
+ * PR. This is the ONLY reliable way to get the host for a long (hash-truncated)
72
+ * slug — we can't compute it. Returns the newest trusted match or null.
73
+ */
74
+ export declare function findPreviewUrlInComments(comments: ReadonlyArray<GhPrComment> | undefined, { expectedHost, requireTrustedAuthor, }?: {
75
+ expectedHost?: string;
76
+ requireTrustedAuthor?: boolean;
77
+ }): string | null;
78
+ /**
79
+ * Resolve a preview env from a PR number. Reads the PR's head branch (and the
80
+ * Vercel bot's preview-URL comment) via `gh`. Prefers the real alias from the
81
+ * comment — which works even for long, hash-truncated slugs — and falls back to
82
+ * computing the host from the branch slug for short slugs. Throws for protected
83
+ * slugs and for long slugs with no resolvable alias.
84
+ */
85
+ export declare function resolvePreviewByPr({ pr, runGh, }: {
86
+ pr: number;
87
+ runGh?: GhRunner;
88
+ }): Promise<ResolvedPreview>;
89
+ export {};