@sequenceholdings/studio-cli 0.1.18 → 0.1.22
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 +232 -11
- package/dist/agents/apply-chunks.d.ts +13 -0
- package/dist/agents/apply-chunks.js +43 -0
- package/dist/agents/commands.d.ts +10 -0
- package/dist/agents/commands.js +238 -0
- package/dist/agents/scaffold.d.ts +2 -0
- package/dist/agents/scaffold.js +77 -0
- package/dist/agents/source.d.ts +20 -0
- package/dist/agents/source.js +144 -0
- package/dist/atlas-client.js +29 -0
- package/dist/auth.d.ts +19 -17
- package/dist/auth.js +102 -33
- package/dist/functions/commands.d.ts +2 -10
- package/dist/functions/commands.js +18 -25
- package/dist/functions/manifest.d.ts +2 -0
- package/dist/functions/manifest.js +39 -9
- package/dist/functions/source-selection.d.ts +24 -0
- package/dist/functions/source-selection.js +67 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +6 -0
- package/dist/orm/delegate.js +11 -6
- package/dist/pipeline/commands.js +21 -0
- package/dist/pipeline/lifecycle.d.ts +2 -0
- package/dist/pipeline/lifecycle.js +107 -0
- package/dist/process/build.js +2 -1
- package/dist/process/lint.js +8 -0
- package/dist/repos/commands.d.ts +53 -1
- package/dist/repos/commands.js +258 -1
- package/package.json +8 -6
package/dist/repos/commands.js
CHANGED
|
@@ -16,7 +16,8 @@ import { existsSync, readdirSync, rmSync } from 'node:fs';
|
|
|
16
16
|
import { homedir } from 'node:os';
|
|
17
17
|
import { resolve, sep } from 'node:path';
|
|
18
18
|
import { materializeRepo, resolveCommitSha, resolveRepo, } from '@sequenceholdings/artifact-studio/git-service-client';
|
|
19
|
-
import {
|
|
19
|
+
import { z } from 'zod';
|
|
20
|
+
import { AtlasApiError, deleteNoContent, getJson, getJsonOr404, postJson, } from '../atlas-client.js';
|
|
20
21
|
import { printCliError } from '../cli-errors.js';
|
|
21
22
|
import { PREVIEW_DOMAIN } from '../preview.js';
|
|
22
23
|
import { confirmYes } from '../prompt.js';
|
|
@@ -468,6 +469,257 @@ function defaultOutFromCloneTarget({ url, id, }) {
|
|
|
468
469
|
const match = /\/repos\/([^/]+)\/git\/?$/.exec(new URL(url).pathname);
|
|
469
470
|
return match?.[1] ?? 'repo';
|
|
470
471
|
}
|
|
472
|
+
/**
|
|
473
|
+
* Mirror of atlas `ci-discover` Zod contract so `repos ci show` fails closed
|
|
474
|
+
* on the same invalid `.seq/ci.json` shapes the runner rejects (`ci/config`).
|
|
475
|
+
* Keep in sync with `atlas/src/server/services/git-service/ci-discover.ts`.
|
|
476
|
+
*/
|
|
477
|
+
const CiPhaseSchema = z.enum(['check', 'test']);
|
|
478
|
+
const CiJsonEntrySchema = z
|
|
479
|
+
.object({
|
|
480
|
+
phase: CiPhaseSchema,
|
|
481
|
+
name: z
|
|
482
|
+
.string()
|
|
483
|
+
.min(1)
|
|
484
|
+
.max(64)
|
|
485
|
+
.regex(/^[a-z][a-z0-9._/-]*$/)
|
|
486
|
+
.optional(),
|
|
487
|
+
script: z.string().min(1).max(128).optional(),
|
|
488
|
+
command: z.array(z.string().min(1).max(256)).min(1).max(32).optional(),
|
|
489
|
+
})
|
|
490
|
+
.superRefine((entry, ctx) => {
|
|
491
|
+
const hasScript = entry.script != null;
|
|
492
|
+
const hasCommand = entry.command != null;
|
|
493
|
+
if (hasScript === hasCommand) {
|
|
494
|
+
ctx.addIssue({
|
|
495
|
+
code: z.ZodIssueCode.custom,
|
|
496
|
+
message: 'each check must specify exactly one of script or command',
|
|
497
|
+
path: hasScript ? ['command'] : ['script'],
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
});
|
|
501
|
+
const CiJsonSchema = z.object({
|
|
502
|
+
checks: z.array(CiJsonEntrySchema).max(16),
|
|
503
|
+
});
|
|
504
|
+
/** Match atlas `ci-discover` defaultNameForPhase (per-phase index, not array index). */
|
|
505
|
+
function defaultNameForPhase(phase, indexInPhase) {
|
|
506
|
+
if (indexInPhase === 0)
|
|
507
|
+
return `ci/${phase}`;
|
|
508
|
+
return `ci/${phase}-${indexInPhase + 1}`;
|
|
509
|
+
}
|
|
510
|
+
/**
|
|
511
|
+
* Pure discovery shared by `repos ci show` / `import`. Mirrors atlas
|
|
512
|
+
* `discoverCiChecks` / `parseCiJson` so CLI preview matches execution.
|
|
513
|
+
*/
|
|
514
|
+
export function discoverCliChecks({ ciJson, packageScripts, }) {
|
|
515
|
+
if (ciJson !== undefined) {
|
|
516
|
+
const parsed = CiJsonSchema.safeParse(ciJson);
|
|
517
|
+
if (!parsed.success) {
|
|
518
|
+
const detail = parsed.error.issues
|
|
519
|
+
.map((i) => `${i.path.join('.') || '(root)'}: ${i.message}`)
|
|
520
|
+
.slice(0, 3)
|
|
521
|
+
.join('; ');
|
|
522
|
+
return { ok: false, error: `Invalid .seq/ci.json: ${detail}` };
|
|
523
|
+
}
|
|
524
|
+
const phaseCounts = new Map();
|
|
525
|
+
const seenNames = new Set();
|
|
526
|
+
const checks = [];
|
|
527
|
+
for (const entry of parsed.data.checks) {
|
|
528
|
+
const indexInPhase = phaseCounts.get(entry.phase) ?? 0;
|
|
529
|
+
phaseCounts.set(entry.phase, indexInPhase + 1);
|
|
530
|
+
const name = entry.name ?? defaultNameForPhase(entry.phase, indexInPhase);
|
|
531
|
+
if (seenNames.has(name)) {
|
|
532
|
+
return {
|
|
533
|
+
ok: false,
|
|
534
|
+
error: `Invalid .seq/ci.json: duplicate check name "${name}"`,
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
seenNames.add(name);
|
|
538
|
+
const check = {
|
|
539
|
+
name,
|
|
540
|
+
source: 'ci.json',
|
|
541
|
+
};
|
|
542
|
+
if (entry.script != null)
|
|
543
|
+
check.script = entry.script;
|
|
544
|
+
if (entry.command != null)
|
|
545
|
+
check.command = entry.command;
|
|
546
|
+
checks.push(check);
|
|
547
|
+
}
|
|
548
|
+
return { ok: true, checks };
|
|
549
|
+
}
|
|
550
|
+
const checks = [];
|
|
551
|
+
if (packageScripts) {
|
|
552
|
+
for (const candidate of ['lint', 'typecheck', 'check']) {
|
|
553
|
+
if (packageScripts[candidate]?.trim()) {
|
|
554
|
+
checks.push({ name: 'ci/check', script: candidate, source: 'autodiscover' });
|
|
555
|
+
break;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
if (packageScripts.test?.trim()) {
|
|
559
|
+
checks.push({ name: 'ci/test', script: 'test', source: 'autodiscover' });
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
return { ok: true, checks };
|
|
563
|
+
}
|
|
564
|
+
function decodeFileContent(file) {
|
|
565
|
+
return file.encoding === 'base64'
|
|
566
|
+
? Buffer.from(file.content, 'base64').toString('utf8')
|
|
567
|
+
: file.content;
|
|
568
|
+
}
|
|
569
|
+
/**
|
|
570
|
+
* Load `.seq/ci.json` / `package.json` for CI preview.
|
|
571
|
+
*
|
|
572
|
+
* Matches the runner: only a true 404 means "file absent". Invalid JSON and
|
|
573
|
+
* non-404 API errors fail closed (no silent package.json fallback over a bad
|
|
574
|
+
* `.seq/ci.json`).
|
|
575
|
+
*/
|
|
576
|
+
export async function loadRemoteCiSources({ ctx, repoId, ref, }) {
|
|
577
|
+
let ciJson;
|
|
578
|
+
try {
|
|
579
|
+
const file = await getJsonOr404({
|
|
580
|
+
...clientOptions(ctx),
|
|
581
|
+
path: `/api/git-service/repos/${repoId}/contents/.seq/ci.json?ref=${encodeURIComponent(ref)}`,
|
|
582
|
+
});
|
|
583
|
+
if (file != null) {
|
|
584
|
+
try {
|
|
585
|
+
ciJson = JSON.parse(decodeFileContent(file));
|
|
586
|
+
}
|
|
587
|
+
catch {
|
|
588
|
+
return { ok: false, error: 'invalid .seq/ci.json: not valid JSON' };
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
catch (err) {
|
|
593
|
+
if (err instanceof AtlasApiError) {
|
|
594
|
+
return { ok: false, error: `failed to fetch .seq/ci.json: ${err.message}` };
|
|
595
|
+
}
|
|
596
|
+
throw err;
|
|
597
|
+
}
|
|
598
|
+
// When ci.json is present (including empty checks), skip package.json —
|
|
599
|
+
// discovery never falls through to autodiscover in that case.
|
|
600
|
+
if (ciJson !== undefined) {
|
|
601
|
+
return { ok: true, ciJson };
|
|
602
|
+
}
|
|
603
|
+
let packageScripts;
|
|
604
|
+
try {
|
|
605
|
+
const file = await getJsonOr404({
|
|
606
|
+
...clientOptions(ctx),
|
|
607
|
+
path: `/api/git-service/repos/${repoId}/contents/package.json?ref=${encodeURIComponent(ref)}`,
|
|
608
|
+
});
|
|
609
|
+
if (file != null) {
|
|
610
|
+
let pkg;
|
|
611
|
+
try {
|
|
612
|
+
pkg = JSON.parse(decodeFileContent(file));
|
|
613
|
+
}
|
|
614
|
+
catch {
|
|
615
|
+
return { ok: false, error: 'invalid package.json: not valid JSON' };
|
|
616
|
+
}
|
|
617
|
+
if (pkg.scripts && typeof pkg.scripts === 'object') {
|
|
618
|
+
packageScripts = {};
|
|
619
|
+
for (const [key, value] of Object.entries(pkg.scripts)) {
|
|
620
|
+
if (typeof value === 'string')
|
|
621
|
+
packageScripts[key] = value;
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
catch (err) {
|
|
627
|
+
if (err instanceof AtlasApiError) {
|
|
628
|
+
return { ok: false, error: `failed to fetch package.json: ${err.message}` };
|
|
629
|
+
}
|
|
630
|
+
throw err;
|
|
631
|
+
}
|
|
632
|
+
return { ok: true, packageScripts };
|
|
633
|
+
}
|
|
634
|
+
/**
|
|
635
|
+
* Discover CI checks from a remote repo tip (pure preview — no Settings write).
|
|
636
|
+
* Uses the same autodiscovery rules as the platform runner (PLA-378).
|
|
637
|
+
*/
|
|
638
|
+
export async function reposCiShowCommand(args) {
|
|
639
|
+
const { namespace, name } = parseRepoPath(args.positional[0]);
|
|
640
|
+
const ctx = await reposContext(args);
|
|
641
|
+
const resolved = await resolveRepo({ ...clientOptions(ctx), namespace, name });
|
|
642
|
+
const repo = await getJson({
|
|
643
|
+
...clientOptions(ctx),
|
|
644
|
+
path: `/api/git-service/repos/${resolved.id}`,
|
|
645
|
+
});
|
|
646
|
+
const ref = stringFlag(args.flags, 'ref') ?? repo.defaultBranch;
|
|
647
|
+
const required = new Set(repo.reviewPolicy?.requiredChecks ?? []);
|
|
648
|
+
const sources = await loadRemoteCiSources({ ctx, repoId: resolved.id, ref });
|
|
649
|
+
if (!sources.ok) {
|
|
650
|
+
console.error(`${LOG} ${sources.error} on ${namespace}/${name}@${ref}`);
|
|
651
|
+
return 1;
|
|
652
|
+
}
|
|
653
|
+
const discovered = discoverCliChecks(sources);
|
|
654
|
+
if (!discovered.ok) {
|
|
655
|
+
console.error(`${LOG} ${discovered.error} on ${namespace}/${name}@${ref}`);
|
|
656
|
+
return 1;
|
|
657
|
+
}
|
|
658
|
+
console.log(`${LOG} CI for ${namespace}/${name}@${ref} on ${ctx.env.name}:`);
|
|
659
|
+
if (discovered.checks.length === 0) {
|
|
660
|
+
console.log(' (no checks discovered)');
|
|
661
|
+
return 0;
|
|
662
|
+
}
|
|
663
|
+
for (const check of discovered.checks) {
|
|
664
|
+
const how = check.script != null
|
|
665
|
+
? `script=${check.script}`
|
|
666
|
+
: check.command != null
|
|
667
|
+
? `command=${JSON.stringify(check.command)}`
|
|
668
|
+
: '';
|
|
669
|
+
console.log(` ${check.name} ${how} source=${check.source} required=${required.has(check.name)}`);
|
|
670
|
+
}
|
|
671
|
+
return 0;
|
|
672
|
+
}
|
|
673
|
+
const REQUIRED_CHECKS_WRITE_DISABLED = `${LOG} writing requiredChecks is disabled until the sandboxed CI runner is live ` +
|
|
674
|
+
`(discovery posts neutral check-runs that cannot satisfy the merge gate). ` +
|
|
675
|
+
`Use \`seq-studio repos ci show\` to preview discovered names.`;
|
|
676
|
+
/** Add a check-run name to Settings `requiredChecks` (merge-blocking). */
|
|
677
|
+
export async function reposCiRequireCommand(args) {
|
|
678
|
+
const check = stringFlag(args.flags, 'check');
|
|
679
|
+
if (!check) {
|
|
680
|
+
console.error('usage: seq-studio repos ci require <ns>/<name> --check <name> -e <env>');
|
|
681
|
+
return 1;
|
|
682
|
+
}
|
|
683
|
+
console.error(REQUIRED_CHECKS_WRITE_DISABLED);
|
|
684
|
+
return 1;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Set requiredChecks to every check discovered on the tip (opt-in bulk require).
|
|
688
|
+
*/
|
|
689
|
+
export async function reposCiImportCommand(args) {
|
|
690
|
+
// Still run show so authors can preview names, then refuse the write.
|
|
691
|
+
const code = await reposCiShowCommand(args);
|
|
692
|
+
if (code !== 0)
|
|
693
|
+
return code;
|
|
694
|
+
console.error(REQUIRED_CHECKS_WRITE_DISABLED);
|
|
695
|
+
return 1;
|
|
696
|
+
}
|
|
697
|
+
export async function reposCiCommand(args) {
|
|
698
|
+
const [action] = args.positional;
|
|
699
|
+
const rest = {
|
|
700
|
+
...args,
|
|
701
|
+
positional: args.positional.slice(1),
|
|
702
|
+
};
|
|
703
|
+
switch (action) {
|
|
704
|
+
case 'show':
|
|
705
|
+
return reposCiShowCommand(rest);
|
|
706
|
+
case 'require':
|
|
707
|
+
return reposCiRequireCommand(rest);
|
|
708
|
+
case 'import':
|
|
709
|
+
return reposCiImportCommand(rest);
|
|
710
|
+
case 'help':
|
|
711
|
+
case undefined:
|
|
712
|
+
console.log(`usage:
|
|
713
|
+
seq-studio repos ci show <ns>/<name> -e <env> [--ref r] preview discovered CI checks
|
|
714
|
+
seq-studio repos ci require <ns>/<name> --check <name> -e <env> reserved (refuses write until sandboxed runner)
|
|
715
|
+
seq-studio repos ci import <ns>/<name> -e <env> [--ref r] reserved (refuses write until sandboxed runner)
|
|
716
|
+
`);
|
|
717
|
+
return action ? 0 : 1;
|
|
718
|
+
default:
|
|
719
|
+
console.error(`unknown repos ci command: ${action}`);
|
|
720
|
+
return 1;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
471
723
|
export async function reposDeleteCommand(args) {
|
|
472
724
|
const { namespace, name } = parseRepoPath(args.positional[0]);
|
|
473
725
|
const ctx = await reposContext(args);
|
|
@@ -505,6 +757,9 @@ export const REPOS_USAGE = `usage:
|
|
|
505
757
|
seq-studio repos pull <ns>/<name> -e <env> [--ref r] [--out dir] [--force]
|
|
506
758
|
materialize the tree at a ref (JSON API)
|
|
507
759
|
seq-studio repos delete <ns>/<name> -e <env> [--yes] delete a repo (confirm prompt)
|
|
760
|
+
seq-studio repos ci show <ns>/<name> -e <env> [--ref r] preview discovered CI checks
|
|
761
|
+
seq-studio repos ci require <ns>/<name> --check <name> -e <env> reserved (refuses write until sandboxed runner)
|
|
762
|
+
seq-studio repos ci import <ns>/<name> -e <env> [--ref r] reserved (refuses write until sandboxed runner)
|
|
508
763
|
|
|
509
764
|
Flags: -e/--env <env> (required; see: seq-studio envs list)
|
|
510
765
|
|
|
@@ -534,6 +789,8 @@ export async function runReposCommand(sub, args) {
|
|
|
534
789
|
return await reposPullCommand(args);
|
|
535
790
|
case 'delete':
|
|
536
791
|
return await reposDeleteCommand(args);
|
|
792
|
+
case 'ci':
|
|
793
|
+
return await reposCiCommand(args);
|
|
537
794
|
case 'help':
|
|
538
795
|
case '--help':
|
|
539
796
|
case '-h':
|
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 process` (Lattice), `seq-studio artifact` (Artifact Studio), `seq-studio functions` / `secrets`, `seq-studio repos` (platform git-service), and `seq-studio auth pat` (git-service PATs). Includes Auth0 browser login shared with seqapi.",
|
|
3
|
+
"version": "0.1.22",
|
|
4
|
+
"description": "Unified Sequence Studio CLI — `seq-studio agents` (typed agent definitions), `seq-studio process` (Lattice), `seq-studio artifact` (Artifact Studio), `seq-studio functions` / `secrets`, `seq-studio repos` (platform git-service), and `seq-studio auth pat` (git-service PATs). Includes Auth0 browser login shared with seqapi.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
@@ -40,12 +40,13 @@
|
|
|
40
40
|
"smol-toml": "^1.4.2",
|
|
41
41
|
"tsx": "^4.20.3",
|
|
42
42
|
"zod": "^4.1.13",
|
|
43
|
-
"@sequenceholdings/
|
|
43
|
+
"@sequenceholdings/agent-spec": "0.1.0",
|
|
44
|
+
"@sequenceholdings/artifact-studio": "0.2.0",
|
|
44
45
|
"@sequenceholdings/lattice": "0.1.2"
|
|
45
46
|
},
|
|
46
47
|
"peerDependencies": {
|
|
47
|
-
"@sequenceholdings/
|
|
48
|
-
"@sequenceholdings/
|
|
48
|
+
"@sequenceholdings/orm": "0.1.2",
|
|
49
|
+
"@sequenceholdings/pipeline-spec": "0.1.0"
|
|
49
50
|
},
|
|
50
51
|
"peerDependenciesMeta": {
|
|
51
52
|
"@sequenceholdings/orm": {
|
|
@@ -60,7 +61,7 @@
|
|
|
60
61
|
"@types/node": "^22.0.0",
|
|
61
62
|
"typescript": "^5.6.0",
|
|
62
63
|
"vitest": "^4.1.5",
|
|
63
|
-
"@sequenceholdings/orm": "0.1.
|
|
64
|
+
"@sequenceholdings/orm": "0.1.2",
|
|
64
65
|
"@sequenceholdings/pipeline-spec": "0.1.0"
|
|
65
66
|
},
|
|
66
67
|
"engines": {
|
|
@@ -70,6 +71,7 @@
|
|
|
70
71
|
"studio",
|
|
71
72
|
"sequence",
|
|
72
73
|
"lattice",
|
|
74
|
+
"agents",
|
|
73
75
|
"process",
|
|
74
76
|
"artifact",
|
|
75
77
|
"cli"
|