@sequenceholdings/studio-cli 0.1.22 → 0.1.25
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 +136 -12
- package/dist/app/commands.d.ts +16 -0
- package/dist/app/commands.js +227 -0
- package/dist/app/deploy.d.ts +49 -0
- package/dist/app/deploy.js +197 -0
- package/dist/app/kinds.d.ts +10 -0
- package/dist/app/kinds.js +36 -0
- package/dist/app/manifest.d.ts +94 -0
- package/dist/app/manifest.js +273 -0
- package/dist/app/scaffold.d.ts +28 -0
- package/dist/app/scaffold.js +263 -0
- package/dist/auth.d.ts +9 -5
- package/dist/auth.js +121 -8
- package/dist/config.d.ts +7 -3
- package/dist/config.js +77 -5
- package/dist/envs/commands.js +9 -6
- package/dist/functions/bundle.js +3 -3
- package/dist/functions/commands.js +18 -5
- package/dist/functions/manifest.d.ts +25 -0
- package/dist/functions/manifest.js +93 -0
- package/dist/login.js +3 -3
- package/dist/main.d.ts +3 -0
- package/dist/main.js +21 -0
- package/dist/pipeline/codegen.d.ts +2 -0
- package/dist/pipeline/codegen.js +118 -0
- package/dist/pipeline/commands.d.ts +7 -0
- package/dist/pipeline/commands.js +90 -12
- package/dist/pipeline/lifecycle.d.ts +1 -12
- package/dist/pipeline/lifecycle.js +228 -46
- package/dist/pipeline/templates.js +3 -1
- package/dist/preview.d.ts +4 -3
- package/dist/preview.js +5 -4
- package/dist/secrets/commands.d.ts +3 -1
- package/dist/secrets/commands.js +87 -26
- package/package.json +7 -7
- package/dist/pipeline/pinning.d.ts +0 -5
- package/dist/pipeline/pinning.js +0 -9
package/dist/secrets/commands.js
CHANGED
|
@@ -41,6 +41,25 @@ function parseCommaList(value) {
|
|
|
41
41
|
.map((part) => part.trim())
|
|
42
42
|
.filter(Boolean);
|
|
43
43
|
}
|
|
44
|
+
function orgFlag(args) {
|
|
45
|
+
return typeof args.flags.org === 'string' ? args.flags.org.trim().toLowerCase() : undefined;
|
|
46
|
+
}
|
|
47
|
+
/** First-party Sequence Atlas envs — their Lakebase registry is org=sequence. */
|
|
48
|
+
const SEQUENCE_OWNED_ENVS = new Set(['local', 'staging', 'production']);
|
|
49
|
+
/** Refuse --org that would write a tenant row into the wrong deployment registry. */
|
|
50
|
+
function orgDeploymentError(org, envName) {
|
|
51
|
+
if (SEQUENCE_OWNED_ENVS.has(envName) && org !== 'sequence') {
|
|
52
|
+
return `--org ${org} must be used with -e ${org} (${envName} only holds the sequence secret registry)`;
|
|
53
|
+
}
|
|
54
|
+
if (!SEQUENCE_OWNED_ENVS.has(envName) && org === 'sequence') {
|
|
55
|
+
return `--org sequence is not valid on -e ${envName} (that deployment does not own the Sequence registry)`;
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
/** Drop one trailing LF or CRLF so Windows-authored --from-file values match Unix. */
|
|
60
|
+
export function stripOneTrailingNewline(value) {
|
|
61
|
+
return value.replace(/\r?\n$/, '');
|
|
62
|
+
}
|
|
44
63
|
async function listSecrets(ctx) {
|
|
45
64
|
const { secrets } = await getJson({
|
|
46
65
|
...clientOptions(ctx),
|
|
@@ -48,11 +67,24 @@ async function listSecrets(ctx) {
|
|
|
48
67
|
});
|
|
49
68
|
return secrets;
|
|
50
69
|
}
|
|
51
|
-
async function resolveSecretByName({ ctx, name, }) {
|
|
70
|
+
async function resolveSecretByName({ ctx, name, org, }) {
|
|
52
71
|
const secrets = await listSecrets(ctx);
|
|
53
|
-
const
|
|
72
|
+
const matches = secrets.filter((row) => row.name === name);
|
|
73
|
+
const scoped = org ? matches.filter((row) => row.orgId === org) : matches;
|
|
74
|
+
if (scoped.length === 0) {
|
|
75
|
+
throw new Error(`Managed secret "${name}" not found on ${ctx.env.name}` +
|
|
76
|
+
(org ? ` org=${org}` : '') +
|
|
77
|
+
` (or you don't have access).`);
|
|
78
|
+
}
|
|
79
|
+
if (scoped.length > 1) {
|
|
80
|
+
const orgs = scoped.map((row) => row.orgId).join(', ');
|
|
81
|
+
throw new Error(`Managed secret "${name}" exists in multiple orgs (${orgs}). Pass --org <slug>.`);
|
|
82
|
+
}
|
|
83
|
+
const [secret] = scoped;
|
|
54
84
|
if (!secret) {
|
|
55
|
-
throw new Error(`Managed secret "${name}" not found on ${ctx.env.name}
|
|
85
|
+
throw new Error(`Managed secret "${name}" not found on ${ctx.env.name}` +
|
|
86
|
+
(org ? ` org=${org}` : '') +
|
|
87
|
+
` (or you don't have access).`);
|
|
56
88
|
}
|
|
57
89
|
return secret;
|
|
58
90
|
}
|
|
@@ -128,20 +160,29 @@ async function functionExistsBySlugEventually({ ctx, slug, attempts = 3, retryDe
|
|
|
128
160
|
export async function secretsCreateCommand(args) {
|
|
129
161
|
const name = args.positional[0];
|
|
130
162
|
if (!name) {
|
|
131
|
-
console.error('usage: seq-studio secrets create <NAME> -e <env> [--description text]');
|
|
163
|
+
console.error('usage: seq-studio secrets create <NAME> -e <env> [--org <slug>] [--description text]');
|
|
132
164
|
return 1;
|
|
133
165
|
}
|
|
134
166
|
const ctx = await buildContext(args);
|
|
135
167
|
const description = typeof args.flags.description === 'string' ? args.flags.description : undefined;
|
|
168
|
+
const org = orgFlag(args);
|
|
169
|
+
if (org) {
|
|
170
|
+
const mismatch = orgDeploymentError(org, ctx.env.name);
|
|
171
|
+
if (mismatch) {
|
|
172
|
+
console.error(`${LOG} ${mismatch}`);
|
|
173
|
+
return 1;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
136
176
|
const secret = await postJson({
|
|
137
177
|
...clientOptions(ctx),
|
|
138
178
|
path: '/api/managed-secrets',
|
|
139
179
|
body: {
|
|
140
180
|
name,
|
|
141
181
|
description: description ?? null,
|
|
182
|
+
...(org ? { orgId: org } : {}),
|
|
142
183
|
},
|
|
143
184
|
});
|
|
144
|
-
console.log(`${LOG} created managed secret "${secret.name}" (${secret.id}) on ${ctx.env.name}`);
|
|
185
|
+
console.log(`${LOG} created managed secret "${secret.name}" (${secret.id}) org=${secret.orgId} on ${ctx.env.name}`);
|
|
145
186
|
return 0;
|
|
146
187
|
}
|
|
147
188
|
// ---------------------------------------------------------------------------
|
|
@@ -150,12 +191,24 @@ export async function secretsCreateCommand(args) {
|
|
|
150
191
|
export async function secretsSetCommand(args) {
|
|
151
192
|
const name = args.positional[0];
|
|
152
193
|
if (!name) {
|
|
153
|
-
console.error('usage: seq-studio secrets set <NAME> -e <env>');
|
|
194
|
+
console.error('usage: seq-studio secrets set <NAME> -e <env> [--org <slug>] [--from-file <path>]');
|
|
154
195
|
return 1;
|
|
155
196
|
}
|
|
156
197
|
const ctx = await buildContext(args);
|
|
157
|
-
const secret = await resolveSecretByName({ ctx, name });
|
|
158
|
-
const
|
|
198
|
+
const secret = await resolveSecretByName({ ctx, name, org: orgFlag(args) });
|
|
199
|
+
const fromFile = typeof args.flags['from-file'] === 'string' ? args.flags['from-file'] : undefined;
|
|
200
|
+
let value;
|
|
201
|
+
if (fromFile) {
|
|
202
|
+
const filePath = isAbsolute(fromFile) ? fromFile : resolve(workDir(args), fromFile);
|
|
203
|
+
if (!existsSync(filePath)) {
|
|
204
|
+
console.error(`${LOG} --from-file not found: ${filePath}`);
|
|
205
|
+
return 1;
|
|
206
|
+
}
|
|
207
|
+
value = stripOneTrailingNewline(await readFile(filePath, 'utf8'));
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
value = await promptHidden(`Default value for ${name} (input hidden): `);
|
|
211
|
+
}
|
|
159
212
|
if (!value) {
|
|
160
213
|
console.error(`${LOG} empty value — aborted`);
|
|
161
214
|
return 1;
|
|
@@ -183,7 +236,7 @@ export async function secretsListCommand(args) {
|
|
|
183
236
|
for (const secret of secrets) {
|
|
184
237
|
const defaultState = secret.hasDefaultValue ? 'default set' : 'no default';
|
|
185
238
|
const access = secret.currentUserAccess ?? 'none';
|
|
186
|
-
console.log(` ${secret.name} [${defaultState}] access=${access} attachments=${secret.attachmentCount}`);
|
|
239
|
+
console.log(` ${secret.name} org=${secret.orgId} [${defaultState}] access=${access} attachments=${secret.attachmentCount}`);
|
|
187
240
|
}
|
|
188
241
|
return 0;
|
|
189
242
|
}
|
|
@@ -194,11 +247,11 @@ export async function secretsAttachCommand(args) {
|
|
|
194
247
|
const name = args.positional[0];
|
|
195
248
|
const fnSlug = typeof args.flags.fn === 'string' ? args.flags.fn : args.positional[1];
|
|
196
249
|
if (!name || !fnSlug) {
|
|
197
|
-
console.error('usage: seq-studio secrets attach <NAME> --fn <slug> -e <env> [--env-var NAME]');
|
|
250
|
+
console.error('usage: seq-studio secrets attach <NAME> --fn <slug> -e <env> [--org <slug>] [--env-var NAME]');
|
|
198
251
|
return 1;
|
|
199
252
|
}
|
|
200
253
|
const ctx = await buildContext(args);
|
|
201
|
-
const secret = await resolveSecretByName({ ctx, name });
|
|
254
|
+
const secret = await resolveSecretByName({ ctx, name, org: orgFlag(args) });
|
|
202
255
|
const fn = await resolveFunctionBySlug({ ctx, slug: fnSlug });
|
|
203
256
|
const envVarName = (typeof args.flags['env-var'] === 'string' ? args.flags['env-var'] : undefined) ?? name;
|
|
204
257
|
if (secret.hasDefaultValue) {
|
|
@@ -229,11 +282,11 @@ export async function secretsDetachCommand(args) {
|
|
|
229
282
|
const name = args.positional[0];
|
|
230
283
|
const fnSlug = typeof args.flags.fn === 'string' ? args.flags.fn : args.positional[1];
|
|
231
284
|
if (!name || !fnSlug) {
|
|
232
|
-
console.error('usage: seq-studio secrets detach <NAME> --fn <slug> -e <env>');
|
|
285
|
+
console.error('usage: seq-studio secrets detach <NAME> --fn <slug> -e <env> [--org <slug>]');
|
|
233
286
|
return 1;
|
|
234
287
|
}
|
|
235
288
|
const ctx = await buildContext(args);
|
|
236
|
-
const secret = await resolveSecretByName({ ctx, name });
|
|
289
|
+
const secret = await resolveSecretByName({ ctx, name, org: orgFlag(args) });
|
|
237
290
|
const fn = await resolveFunctionBySlug({ ctx, slug: fnSlug });
|
|
238
291
|
await deleteJson({
|
|
239
292
|
...clientOptions(ctx),
|
|
@@ -464,11 +517,11 @@ async function attachedFunctionSlugs(ctx, secretId) {
|
|
|
464
517
|
export async function secretsVersionsCommand(args) {
|
|
465
518
|
const name = args.positional[0];
|
|
466
519
|
if (!name) {
|
|
467
|
-
console.error('usage: seq-studio secrets versions <NAME> -e <env>');
|
|
520
|
+
console.error('usage: seq-studio secrets versions <NAME> -e <env> [--org <slug>]');
|
|
468
521
|
return 1;
|
|
469
522
|
}
|
|
470
523
|
const ctx = await buildContext(args);
|
|
471
|
-
const secret = await resolveSecretByName({ ctx, name });
|
|
524
|
+
const secret = await resolveSecretByName({ ctx, name, org: orgFlag(args) });
|
|
472
525
|
const versions = await listSecretVersions(ctx, secret.id);
|
|
473
526
|
if (versions.length === 0) {
|
|
474
527
|
console.log(`${LOG} no versions recorded for "${name}" on ${ctx.env.name}`);
|
|
@@ -492,11 +545,11 @@ export async function secretsVersionsCommand(args) {
|
|
|
492
545
|
export async function secretsSetDefaultCommand(args) {
|
|
493
546
|
const name = args.positional[0];
|
|
494
547
|
if (!name) {
|
|
495
|
-
console.error('usage: seq-studio secrets set-default <NAME> [version] -e <env> [--yes]');
|
|
548
|
+
console.error('usage: seq-studio secrets set-default <NAME> [version] -e <env> [--org <slug>] [--yes]');
|
|
496
549
|
return 1;
|
|
497
550
|
}
|
|
498
551
|
const ctx = await buildContext(args);
|
|
499
|
-
const secret = await resolveSecretByName({ ctx, name });
|
|
552
|
+
const secret = await resolveSecretByName({ ctx, name, org: orgFlag(args) });
|
|
500
553
|
const versions = await listSecretVersions(ctx, secret.id);
|
|
501
554
|
if (versions.length === 0) {
|
|
502
555
|
console.error(`${LOG} "${name}" has no recorded versions`);
|
|
@@ -558,11 +611,11 @@ export async function secretsPinCommand(args) {
|
|
|
558
611
|
const name = args.positional[0];
|
|
559
612
|
const fnSlug = typeof args.flags.fn === 'string' ? args.flags.fn : undefined;
|
|
560
613
|
if (!name || !fnSlug) {
|
|
561
|
-
console.error('usage: seq-studio secrets pin <NAME> --fn <slug> [version] [--unpin] -e <env> [--yes] [--no-redeploy]');
|
|
614
|
+
console.error('usage: seq-studio secrets pin <NAME> --fn <slug> [version] [--unpin] -e <env> [--org <slug>] [--yes] [--no-redeploy]');
|
|
562
615
|
return 1;
|
|
563
616
|
}
|
|
564
617
|
const ctx = await buildContext(args);
|
|
565
|
-
const secret = await resolveSecretByName({ ctx, name });
|
|
618
|
+
const secret = await resolveSecretByName({ ctx, name, org: orgFlag(args) });
|
|
566
619
|
const fn = await resolveFunctionBySlug({ ctx, slug: fnSlug });
|
|
567
620
|
if (flagBool(args.flags, 'unpin')) {
|
|
568
621
|
const preview = [
|
|
@@ -643,15 +696,21 @@ export async function secretsPinCommand(args) {
|
|
|
643
696
|
// dispatcher
|
|
644
697
|
// ---------------------------------------------------------------------------
|
|
645
698
|
export const SECRETS_USAGE = `usage:
|
|
646
|
-
seq-studio secrets create <NAME> -e <env> [--description text]
|
|
647
|
-
|
|
699
|
+
seq-studio secrets create <NAME> -e <env> [--org <slug>] [--description text]
|
|
700
|
+
register an org-owned secret
|
|
701
|
+
seq-studio secrets set <NAME> -e <env> [--org <slug>] [--from-file <path>]
|
|
702
|
+
set the shared default value (write-only)
|
|
648
703
|
seq-studio secrets list -e <env> secrets you can see (never values)
|
|
649
|
-
seq-studio secrets attach <NAME> --fn <slug> -e <env>
|
|
650
|
-
|
|
704
|
+
seq-studio secrets attach <NAME> --fn <slug> -e <env> [--org <slug>]
|
|
705
|
+
mount default on a function env var
|
|
706
|
+
seq-studio secrets detach <NAME> --fn <slug> -e <env> [--org <slug>]
|
|
707
|
+
remove attachment
|
|
651
708
|
seq-studio secrets apply --from-env-file .env -e <env> push defaults + attach (keys default from manifest)
|
|
652
|
-
seq-studio secrets versions <NAME> -e <env>
|
|
653
|
-
seq-studio secrets set-default <NAME> [version] -e <env>
|
|
654
|
-
|
|
709
|
+
seq-studio secrets versions <NAME> -e <env> [--org <slug>] value version history (never values)
|
|
710
|
+
seq-studio secrets set-default <NAME> [version] -e <env> [--org <slug>]
|
|
711
|
+
point the default at a prior version (editor)
|
|
712
|
+
seq-studio secrets pin <NAME> --fn <slug> [version] -e <env> [--org <slug>]
|
|
713
|
+
pin one function to a version (sticky; --unpin clears)
|
|
655
714
|
|
|
656
715
|
Note: for the common deploy loop, secrets declared in managed-function.yml are
|
|
657
716
|
reconciled automatically by \`seq-studio functions deploy\` using a local .env.
|
|
@@ -659,6 +718,8 @@ export const SECRETS_USAGE = `usage:
|
|
|
659
718
|
value changes without a redeploy.
|
|
660
719
|
|
|
661
720
|
Flags: -e/--env <env> (required; see: seq-studio envs list)
|
|
721
|
+
--org <slug> (target/select managed-scope org; required when a name exists in multiple orgs)
|
|
722
|
+
set: --from-file <path> (non-interactive; file is not echoed)
|
|
662
723
|
--fn <slug> · --env-var <NAME> · --yes
|
|
663
724
|
--functions <f1,f2> · --keys <K1,K2> · --all (override key selection)
|
|
664
725
|
set-default: [version|version-row-id] (defaults to the most recent non-default) · --yes
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sequenceholdings/studio-cli",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "Unified Sequence Studio CLI — `seq-studio
|
|
3
|
+
"version": "0.1.25",
|
|
4
|
+
"description": "Unified Sequence Studio CLI — `seq-studio init` / `add` / `deploy` (app monorepos), `seq-studio agents`, `seq-studio process` (Lattice), `seq-studio artifact`, `seq-studio functions` / `secrets`, `seq-studio repos`, and `seq-studio auth pat`. Includes Auth0 browser login shared with seqapi.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"README.md"
|
|
37
37
|
],
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"js-yaml": "^4.
|
|
39
|
+
"js-yaml": "^4.3.1",
|
|
40
40
|
"smol-toml": "^1.4.2",
|
|
41
41
|
"tsx": "^4.20.3",
|
|
42
42
|
"zod": "^4.1.13",
|
|
43
|
-
"@sequenceholdings/agent-spec": "0.1.
|
|
44
|
-
"@sequenceholdings/artifact-studio": "0.2.
|
|
43
|
+
"@sequenceholdings/agent-spec": "0.1.1",
|
|
44
|
+
"@sequenceholdings/artifact-studio": "0.2.2",
|
|
45
45
|
"@sequenceholdings/lattice": "0.1.2"
|
|
46
46
|
},
|
|
47
47
|
"peerDependencies": {
|
|
48
|
-
"@sequenceholdings/orm": "0.1.
|
|
48
|
+
"@sequenceholdings/orm": "0.1.3",
|
|
49
49
|
"@sequenceholdings/pipeline-spec": "0.1.0"
|
|
50
50
|
},
|
|
51
51
|
"peerDependenciesMeta": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"@types/node": "^22.0.0",
|
|
62
62
|
"typescript": "^5.6.0",
|
|
63
63
|
"vitest": "^4.1.5",
|
|
64
|
-
"@sequenceholdings/orm": "0.1.
|
|
64
|
+
"@sequenceholdings/orm": "0.1.3",
|
|
65
65
|
"@sequenceholdings/pipeline-spec": "0.1.0"
|
|
66
66
|
},
|
|
67
67
|
"engines": {
|
|
@@ -1,5 +0,0 @@
|
|
|
1
|
-
/** Client-side pin rules mirrored from the server deploy adapter (SEQ-2450). */
|
|
2
|
-
export declare const FULL_SHA_PATTERN: RegExp;
|
|
3
|
-
export declare const PINNED_ENVIRONMENTS: Set<string>;
|
|
4
|
-
export declare function isFullSha(ref: string): boolean;
|
|
5
|
-
export declare function requiresPinnedSha(environment: string): boolean;
|
package/dist/pipeline/pinning.js
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/** Client-side pin rules mirrored from the server deploy adapter (SEQ-2450). */
|
|
2
|
-
export const FULL_SHA_PATTERN = /^[0-9a-f]{40}$/i;
|
|
3
|
-
export const PINNED_ENVIRONMENTS = new Set(['production', 'banksouth']);
|
|
4
|
-
export function isFullSha(ref) {
|
|
5
|
-
return FULL_SHA_PATTERN.test(ref);
|
|
6
|
-
}
|
|
7
|
-
export function requiresPinnedSha(environment) {
|
|
8
|
-
return PINNED_ENVIRONMENTS.has(environment);
|
|
9
|
-
}
|