@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.
- package/README.md +258 -0
- package/dist/artifact/delegate.d.ts +25 -0
- package/dist/artifact/delegate.js +263 -0
- package/dist/atlas-client.d.ts +44 -0
- package/dist/atlas-client.js +173 -0
- package/dist/auth-cmds/commands.d.ts +15 -0
- package/dist/auth-cmds/commands.js +249 -0
- package/dist/auth.d.ts +26 -0
- package/dist/auth.js +171 -0
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +8 -0
- package/dist/cli-errors.d.ts +5 -0
- package/dist/cli-errors.js +78 -0
- package/dist/config.d.ts +44 -0
- package/dist/config.js +103 -0
- package/dist/env-flags.d.ts +8 -0
- package/dist/env-flags.js +47 -0
- package/dist/functions/bundle.d.ts +30 -0
- package/dist/functions/bundle.js +137 -0
- package/dist/functions/commands.d.ts +86 -0
- package/dist/functions/commands.js +999 -0
- package/dist/functions/egress-preview.d.ts +32 -0
- package/dist/functions/egress-preview.js +54 -0
- package/dist/functions/lockfile-origin.d.ts +16 -0
- package/dist/functions/lockfile-origin.js +45 -0
- package/dist/functions/manifest.d.ts +89 -0
- package/dist/functions/manifest.js +586 -0
- package/dist/functions/secret-reconcile.d.ts +79 -0
- package/dist/functions/secret-reconcile.js +86 -0
- package/dist/main.d.ts +14 -0
- package/dist/main.js +129 -0
- package/dist/orm/delegate.d.ts +8 -0
- package/dist/orm/delegate.js +61 -0
- package/dist/pat-hints.d.ts +17 -0
- package/dist/pat-hints.js +28 -0
- package/dist/preview.d.ts +89 -0
- package/dist/preview.js +291 -0
- package/dist/process/agent-loader.d.ts +24 -0
- package/dist/process/agent-loader.js +57 -0
- package/dist/process/build.d.ts +14 -0
- package/dist/process/build.js +368 -0
- package/dist/process/codegen.d.ts +18 -0
- package/dist/process/codegen.js +270 -0
- package/dist/process/commands.d.ts +47 -0
- package/dist/process/commands.js +786 -0
- package/dist/process/discover.d.ts +32 -0
- package/dist/process/discover.js +131 -0
- package/dist/process/lint.d.ts +39 -0
- package/dist/process/lint.js +485 -0
- package/dist/process/local-bundle.d.ts +17 -0
- package/dist/process/local-bundle.js +65 -0
- package/dist/process/plan-diff.d.ts +82 -0
- package/dist/process/plan-diff.js +333 -0
- package/dist/process/resolve-process-pin.d.ts +11 -0
- package/dist/process/resolve-process-pin.js +63 -0
- package/dist/process/simulate.d.ts +50 -0
- package/dist/process/simulate.js +328 -0
- package/dist/prompt.d.ts +35 -0
- package/dist/prompt.js +65 -0
- package/dist/repos/commands.d.ts +49 -0
- package/dist/repos/commands.js +548 -0
- package/dist/repos/git-clone.d.ts +10 -0
- package/dist/repos/git-clone.js +49 -0
- package/dist/secrets/commands.d.ts +24 -0
- package/dist/secrets/commands.js +704 -0
- package/dist/templates/process/example-process/process.ts +43 -0
- package/dist/templates/process/package.json +23 -0
- package/dist/templates/process/pnpm-workspace.yaml +21 -0
- package/dist/templates/process/tsconfig.json +17 -0
- package/package.json +78 -0
- package/templates/process/example-process/process.ts +43 -0
- package/templates/process/package.json +23 -0
- package/templates/process/pnpm-workspace.yaml +21 -0
- 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 {};
|