@sequenceholdings/artifact-studio 0.1.9 → 0.1.11

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/dist/auth.js CHANGED
@@ -87,7 +87,7 @@ export async function getAccessToken() {
87
87
  }),
88
88
  });
89
89
  if (!response.ok) {
90
- throw new Error('Stored refresh token is no longer valid. Run seqapi login again.');
90
+ throw new Error('Stored refresh token is no longer valid. Run seq-studio login again.');
91
91
  }
92
92
  const refreshed = await response.json();
93
93
  await writeTokenConfig({
package/dist/cli.js CHANGED
@@ -3,7 +3,7 @@ import { existsSync } from 'node:fs';
3
3
  import { dirname, join, relative, resolve } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { getJson, getJsonOr404, postJson } from './api.js';
6
- import { readLocalConfig, resolveEnvironment, writeLocalConfig, writeTokenConfig, } from './config.js';
6
+ import { ENV_URLS, readLocalConfig, resolveEnvironment, writeLocalConfig, writeTokenConfig, } from './config.js';
7
7
  import { buildArtifactStudioProject, writeBuildArtifact } from './build.js';
8
8
  import { buildArtifactStudioProjectIsolated } from './build-subprocess.js';
9
9
  import { prepareArtifactBuildRoot } from './prepare-build.js';
@@ -45,10 +45,10 @@ const USAGE = `usage:
45
45
  --repo clones over smart-HTTP and REQUIRES a git PAT in ATLAS_GIT_PAT
46
46
  (repo:read scope) — create one with \`seq-studio auth pat create --scopes
47
47
  repo:read\` or in Atlas → Settings → Tokens. It's the same token you clone the
48
- repo with. (--env + seqapi login are still needed to resolve the repo and
48
+ repo with. (--env + seq-studio login are still needed to resolve the repo and
49
49
  upload the deployment.)
50
50
 
51
- Authenticate with: seqapi login`;
51
+ Authenticate with: seq-studio login`;
52
52
  export async function runCli(argv = process.argv.slice(2)) {
53
53
  const parsed = parseArgs(argv);
54
54
  if ('error' in parsed) {
@@ -116,7 +116,7 @@ async function initCommand(args) {
116
116
  await replaceInFile(join(dir, 'artifact.bundle.yml'), /\{\{title\}\}/g, titleize(slug));
117
117
  await replaceInFile(join(dir, 'package.json'), /\{\{slug\}\}/g, slug);
118
118
  console.log(`[seq-studio] initialized ${dir}`);
119
- console.log('Next: seqapi login && seq-studio artifact dev --env staging');
119
+ console.log('Next: seq-studio login && seq-studio artifact dev --env <env> (see: seq-studio envs list)');
120
120
  return 0;
121
121
  }
122
122
  async function loginCommand(args) {
@@ -204,7 +204,9 @@ async function linkCommand(args) {
204
204
  async function envCommand(args) {
205
205
  const [subcommand, value] = args.positional;
206
206
  if (subcommand === 'list') {
207
- console.log('local\nstaging\nproduction\nbanksouth');
207
+ for (const name of Object.keys(ENV_URLS))
208
+ console.log(name);
209
+ console.log('(deployed environments are discovered per-identity — run: seq-studio envs list)');
208
210
  return 0;
209
211
  }
210
212
  if (subcommand === 'use') {
@@ -229,7 +231,7 @@ async function statusCommand(args) {
229
231
  if (env.name === 'local') {
230
232
  await fetch(`${env.url}/api/health`)
231
233
  .then((response) => console.log(`local Atlas: ${response.ok ? 'ready' : `HTTP ${response.status}`}`))
232
- .catch(() => console.log('local Atlas: not reachable (start Atlas on port 5001 or pass --env staging)'));
234
+ .catch(() => console.log('local Atlas: not reachable (start Atlas on port 5001 or pass --env <env>)'));
233
235
  }
234
236
  return 0;
235
237
  }
@@ -382,7 +384,7 @@ async function planCommand(args) {
382
384
  console.log(` source: ${result.sourceHash}`);
383
385
  console.log(` bundle: ${result.bundleHash}`);
384
386
  if (!token) {
385
- console.log(' remote: skipped (run seqapi login)');
387
+ console.log(' remote: skipped (run seq-studio login)');
386
388
  return 0;
387
389
  }
388
390
  const remote = await fetchRemoteProject({ env: env.url, token, slug: result.manifest.artifact.project_id });
@@ -709,7 +711,7 @@ async function ensureRemoteProject({ env, token, result, linkConfigDir, allowCre
709
711
  if (!allowCreate) {
710
712
  throw new Error(`project "${result.manifest.artifact.project_id}" does not exist on ${env} and --no-create is set. ` +
711
713
  `First-time provisioning is a human step: run \`seq-studio artifact deploy . -e <env>\` once with your ` +
712
- `own login (seqapi login) to create the project, then the CI sweep keeps it updated.`);
714
+ `own login (\`seq-studio login\`) to create the project, then the CI sweep keeps it updated.`);
713
715
  }
714
716
  const created = await postJson({
715
717
  baseUrl: env,
@@ -787,7 +789,7 @@ export function setTokenProvider(provider) {
787
789
  async function getRequiredToken(args) {
788
790
  const token = await getOptionalToken(args);
789
791
  if (!token)
790
- throw new Error('Missing token. Run seqapi login or pass --token.');
792
+ throw new Error('Missing token. Run seq-studio login or pass --token.');
791
793
  return token;
792
794
  }
793
795
  async function getOptionalToken(args) {
package/dist/config.d.ts CHANGED
@@ -1,3 +1,9 @@
1
+ /**
2
+ * Only `local` ships in the published package. Deployed-environment URLs
3
+ * are resolved by the embedding CLI (seq-studio), which discovers the
4
+ * environments visible to the caller's identity and injects the resolved
5
+ * URL via ARTIFACT_STUDIO_BASE_URL (see resolveEnvironment below).
6
+ */
1
7
  export declare const ENV_URLS: Record<string, string>;
2
8
  export interface LocalProjectConfig {
3
9
  projectId?: string;
@@ -18,6 +24,7 @@ export declare function readLocalConfig(cwd?: string): Promise<LocalProjectConfi
18
24
  export declare function writeLocalConfig(config: LocalProjectConfig, cwd?: string): Promise<void>;
19
25
  export declare function readTokenConfig(): Promise<TokenConfig>;
20
26
  export declare function writeTokenConfig(config: TokenConfig): Promise<void>;
27
+ export declare function knownEnvUrls(): Record<string, string>;
21
28
  export declare function resolveEnvironment(name: string | undefined, fallback?: string): {
22
29
  name: string;
23
30
  url: string;
package/dist/config.js CHANGED
@@ -2,11 +2,14 @@ import { mkdir, readFile, writeFile } from 'node:fs/promises';
2
2
  import { existsSync } from 'node:fs';
3
3
  import { dirname, join, resolve } from 'node:path';
4
4
  import { homedir } from 'node:os';
5
+ /**
6
+ * Only `local` ships in the published package. Deployed-environment URLs
7
+ * are resolved by the embedding CLI (seq-studio), which discovers the
8
+ * environments visible to the caller's identity and injects the resolved
9
+ * URL via ARTIFACT_STUDIO_BASE_URL (see resolveEnvironment below).
10
+ */
5
11
  export const ENV_URLS = {
6
12
  local: 'http://localhost:5001',
7
- staging: 'https://staging.atlas.seqholdings.com',
8
- production: 'https://atlas.seqholdings.com',
9
- banksouth: 'https://banksouth.seqholdings.com',
10
13
  };
11
14
  export function globalConfigDir() {
12
15
  return join(homedir(), '.config', 'sequence-artifact-studio');
@@ -42,6 +45,35 @@ export async function writeTokenConfig(config) {
42
45
  await mkdir(dirname(path), { recursive: true });
43
46
  await writeFile(path, JSON.stringify(config, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
44
47
  }
48
+ /**
49
+ * Environment map injected by the embedding CLI (seq-studio sets
50
+ * ARTIFACT_STUDIO_ENV_URLS to the JSON name→url map of environments visible
51
+ * to the caller's identity). Lets commands that resolve an env *name* without
52
+ * an explicit --env flag — the stored `defaultEnv`, `artifact env set` —
53
+ * reach deployed environments, not just the built-in `local`.
54
+ */
55
+ function embedderEnvUrls() {
56
+ const raw = process.env['ARTIFACT_STUDIO_ENV_URLS']?.trim();
57
+ if (!raw)
58
+ return {};
59
+ try {
60
+ const parsed = JSON.parse(raw);
61
+ if (typeof parsed !== 'object' || parsed === null)
62
+ return {};
63
+ const out = {};
64
+ for (const [name, url] of Object.entries(parsed)) {
65
+ if (typeof url === 'string' && url)
66
+ out[name] = url;
67
+ }
68
+ return out;
69
+ }
70
+ catch {
71
+ return {};
72
+ }
73
+ }
74
+ export function knownEnvUrls() {
75
+ return { ...ENV_URLS, ...embedderEnvUrls() };
76
+ }
45
77
  export function resolveEnvironment(name, fallback) {
46
78
  const envName = name ?? fallback;
47
79
  // Allow `seq-studio artifact` (and any other embedder that wants to
@@ -52,11 +84,18 @@ export function resolveEnvironment(name, fallback) {
52
84
  const overrideUrl = process.env['ARTIFACT_STUDIO_BASE_URL']?.trim() || undefined;
53
85
  if (overrideUrl) {
54
86
  if (!envName)
55
- throw new Error('--env must be one of: local, staging, production, banksouth');
87
+ throw new Error(unknownEnvMessage(envName));
56
88
  return { name: envName, url: overrideUrl };
57
89
  }
58
- const url = envName ? ENV_URLS[envName] : undefined;
90
+ const envUrls = knownEnvUrls();
91
+ const url = envName ? envUrls[envName] : undefined;
59
92
  if (!envName || !url)
60
- throw new Error('--env must be one of: local, staging, production, banksouth');
93
+ throw new Error(unknownEnvMessage(envName));
61
94
  return { name: envName, url };
62
95
  }
96
+ function unknownEnvMessage(envName) {
97
+ const known = Object.keys(knownEnvUrls()).join(', ');
98
+ return (`--env ${envName ? `"${envName}" is not available here` : 'is required'} ` +
99
+ `(known: ${known}). Deployed environments are resolved through ` +
100
+ `seq-studio (run: seq-studio envs list), or set ARTIFACT_STUDIO_BASE_URL.`);
101
+ }
package/dist/git-clone.js CHANGED
@@ -42,14 +42,18 @@ export function redactCloneUrl(url) {
42
42
  }
43
43
  }
44
44
  /**
45
- * Abort an HTTP transfer that drops below ~1 KB/s for 30s the primary stall
46
- * guard, so a wedged smart-HTTP fetch fails cleanly in ~30s instead of hanging
47
- * a deploy/CI. Passed as `git -c` so it applies to the clone's fetch.
45
+ * Abort a smart-HTTP transfer that drops below ~1 KB/s for a sustained window,
46
+ * so a wedged fetch fails cleanly instead of hanging a deploy/CI. The window
47
+ * must clear the git-service's *server-side pack generation* pause: upload-pack
48
+ * enumerates + compresses objects before streaming a single byte, and for a
49
+ * real repo that silent gap is tens of seconds (observed ~55s for a 145-file
50
+ * artifact). A 30s threshold false-killed those legitimate clones, so allow a
51
+ * generous 180s of silence — the hard timeout below is the real backstop.
48
52
  */
49
53
  const GIT_LOW_SPEED_LIMIT_BYTES = 1000;
50
- const GIT_LOW_SPEED_TIME_SEC = 30;
51
- /** Backstop hard deadline: kill the child even if it trickles just above the low-speed floor. */
52
- const GIT_CLONE_TIMEOUT_MS = 5 * 60_000;
54
+ const GIT_LOW_SPEED_TIME_SEC = 180;
55
+ /** Backstop hard deadline: SIGKILL the child even if it trickles above the low-speed floor. */
56
+ const GIT_CLONE_TIMEOUT_MS = 6 * 60_000;
53
57
  /** First non-flag arg (the git subcommand), skipping `-c <value>` config pairs. */
54
58
  function gitVerb(args) {
55
59
  for (let i = 0; i < args.length; i++) {
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Pinned pnpm for remote artifact installs, run via `npx`. Kept in lockstep
3
+ * with the repo's root `packageManager`. Do not float to latest: pnpm 11+ adds
4
+ * an UNCONDITIONAL tarball-URL verification policy (no opt-out) that rejects
5
+ * Chainguard's upstream-proxied tarballs at install time
6
+ * (ERR_PNPM_TARBALL_URL_MISMATCH). We run it via npx — which ships with npm on
7
+ * every Node release — rather than corepack, which Node 25+ no longer bundles.
8
+ */
9
+ export declare const ARTIFACT_BUILD_PNPM = "pnpm@10.28.2";
1
10
  /** Chainguard registry routing for ephemeral remote builds. */
2
11
  export declare const ARTIFACT_BUILD_NPMRC = "; Ephemeral install config for seq-studio artifact builds.\nregistry=https://libraries.cgr.dev/javascript/\n//libraries.cgr.dev/javascript/:always-auth=true\n//libraries.cgr.dev/javascript-upstream/:always-auth=true\nignore-scripts=true\nmanage-package-manager-versions=false\n";
3
12
  /** Pull only Chainguard registry auth lines from the developer ~/.npmrc. */
@@ -5,6 +5,15 @@ import { homedir, tmpdir } from 'node:os';
5
5
  import { join } from 'node:path';
6
6
  import { stripInstallControlFiles } from './sanitize-remote-tree.js';
7
7
  import { assertTrustedArtifactInstallTree } from './trusted-install.js';
8
+ /**
9
+ * Pinned pnpm for remote artifact installs, run via `npx`. Kept in lockstep
10
+ * with the repo's root `packageManager`. Do not float to latest: pnpm 11+ adds
11
+ * an UNCONDITIONAL tarball-URL verification policy (no opt-out) that rejects
12
+ * Chainguard's upstream-proxied tarballs at install time
13
+ * (ERR_PNPM_TARBALL_URL_MISMATCH). We run it via npx — which ships with npm on
14
+ * every Node release — rather than corepack, which Node 25+ no longer bundles.
15
+ */
16
+ export const ARTIFACT_BUILD_PNPM = 'pnpm@10.28.2';
8
17
  /** Chainguard registry routing for ephemeral remote builds. */
9
18
  export const ARTIFACT_BUILD_NPMRC = `; Ephemeral install config for seq-studio artifact builds.
10
19
  registry=https://libraries.cgr.dev/javascript/
@@ -65,28 +74,44 @@ export async function prepareArtifactBuildRoot(dir, opts = {}) {
65
74
  console.warn(`[seq-studio] stripped install-control files from remote tree (not committed): ${stripped.join(', ')}`);
66
75
  }
67
76
  assertTrustedArtifactInstallTree(dir);
77
+ // Defense-in-depth: never trust a node_modules committed into the checkout.
78
+ // The trust walk skips node_modules, so a crafted tree could smuggle a
79
+ // malicious `node_modules/pnpm` bin. We both (a) remove any pre-existing
80
+ // node_modules and (b) run npx from a trusted cwd below so npx can't resolve
81
+ // the pinned CLI out of the untrusted tree.
82
+ await rm(join(dir, 'node_modules'), { recursive: true, force: true });
68
83
  const configDir = await mkdtemp(join(tmpdir(), 'artifact-pnpm-'));
69
84
  const controlledNpmrc = join(configDir, '.npmrc');
70
85
  const args = ['install', '--frozen-lockfile', '--ignore-scripts', '--prod'];
71
86
  const userNpmrcPath = process.env.npm_config_userconfig ?? join(homedir(), '.npmrc');
72
- console.log('[seq-studio] installing dependencies (frozen lockfile, prod only)…');
87
+ console.log(`[seq-studio] installing dependencies (frozen lockfile, prod only) via ${ARTIFACT_BUILD_PNPM}…`);
73
88
  try {
74
89
  await writeFile(controlledNpmrc, buildControlledArtifactNpmrc(userNpmrcPath), { mode: 0o600 });
75
- execFileSync('pnpm', args, {
76
- cwd: dir,
90
+ // Run a PINNED pnpm via `npx`, not the operator's ambient `pnpm`. The
91
+ // install must be reproducible across laptops and CI: in a bare temp clone
92
+ // dir the ambient pnpm resolves to latest (11.x), whose unconditional
93
+ // tarball-URL policy rejects Chainguard's upstream-proxied tarballs with
94
+ // ERR_PNPM_TARBALL_URL_MISMATCH. `npx` ships with npm on every Node release
95
+ // (including Node 25+, which dropped corepack), fetches the pinned pnpm, and
96
+ // caches it. `--yes` skips the install-confirmation prompt.
97
+ //
98
+ // SECURITY: run npx from the trusted `configDir` (not `dir`) so it can't
99
+ // resolve/execute a pnpm bin planted in the checkout's node_modules; pnpm
100
+ // still operates on the checkout via `--dir`.
101
+ execFileSync('npx', ['--yes', ARTIFACT_BUILD_PNPM, '--dir', dir, ...args], {
102
+ cwd: configDir,
77
103
  stdio: 'inherit',
78
104
  env: {
79
105
  ...process.env,
80
106
  npm_config_userconfig: controlledNpmrc,
81
- COREPACK_ENABLE_AUTO_PIN: '0',
82
- COREPACK_ENABLE_DOWNLOAD_PROMPT: '0',
83
107
  },
84
108
  });
85
109
  }
86
110
  catch (error) {
87
- const hint = 'Lockfile out of sync or registry auth missing run `pnpm install` locally and commit pnpm-lock.yaml.';
111
+ const hint = 'Lockfile out of sync, registry auth missing, or npx unavailable ' +
112
+ 'run `pnpm install` locally and commit pnpm-lock.yaml, and ensure Node ships npm/npx.';
88
113
  const detail = error instanceof Error ? error.message : String(error);
89
- throw new Error(`pnpm install failed for artifact build. ${hint} (${detail})`);
114
+ throw new Error(`artifact dependency install failed (${ARTIFACT_BUILD_PNPM}). ${hint} (${detail})`);
90
115
  }
91
116
  finally {
92
117
  await rm(configDir, { recursive: true, force: true });
@@ -82,7 +82,8 @@ export async function resolveArtifactSource(spec, opts = {}) {
82
82
  try {
83
83
  if (spec.kind === 'git-service') {
84
84
  if (!opts.baseUrl || !opts.token) {
85
- throw new Error('Deploying from --repo needs a target environment and auth. Pass --env and run `seqapi login`.');
85
+ throw new Error('Deploying from --repo needs a target environment and auth. ' +
86
+ 'Pass --env and run `seq-studio login`.');
86
87
  }
87
88
  // resolveRepo + resolveCommitSha use the Auth0/M2M token: the clone URL
88
89
  // is id-addressed (no by-path endpoint), and we pin the build to the
@@ -24,7 +24,9 @@ capabilities:
24
24
  functions:
25
25
  invoke: []
26
26
 
27
+ # Point each target at an Atlas deployment you can access.
28
+ # Run `seq-studio envs list` to see the environments visible to your identity.
27
29
  targets:
28
- staging:
29
- url: https://staging.atlas.seqholdings.com
30
+ local:
31
+ url: http://localhost:5001
30
32
  visibility: private
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "version": "0.1.9",
12
+ "version": "0.1.11",
13
13
  "description": "SDK types and CLI library for building Artifact Studio apps. Driven via `seq-studio artifact <sub>` (@sequenceholdings/studio-cli), which runs the `./cli` runCli export.",
14
14
  "type": "module",
15
15
  "exports": {
@@ -51,8 +51,8 @@
51
51
  "tw-animate-css": "^1.4.0",
52
52
  "vite": "8.0.16",
53
53
  "zod": "^4.1.13",
54
- "@sequenceholdings/atlas-ui": "0.1.3",
55
- "@sequenceholdings/lattice-form-renderer": "0.1.0"
54
+ "@sequenceholdings/lattice-form-renderer": "0.1.0",
55
+ "@sequenceholdings/atlas-ui": "0.1.3"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/js-yaml": "^4.0.9",
@@ -24,7 +24,9 @@ capabilities:
24
24
  functions:
25
25
  invoke: []
26
26
 
27
+ # Point each target at an Atlas deployment you can access.
28
+ # Run `seq-studio envs list` to see the environments visible to your identity.
27
29
  targets:
28
- staging:
29
- url: https://staging.atlas.seqholdings.com
30
+ local:
31
+ url: http://localhost:5001
30
32
  visibility: private