@sequenceholdings/artifact-studio 0.1.9 → 0.1.10
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 +1 -1
- package/dist/cli.js +6 -6
- package/dist/git-clone.js +10 -6
- package/dist/prepare-build.d.ts +9 -0
- package/dist/prepare-build.js +32 -7
- package/dist/source-resolver.js +2 -1
- package/package.json +3 -3
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
|
|
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
|
@@ -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 +
|
|
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:
|
|
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:
|
|
119
|
+
console.log('Next: seq-studio login && seq-studio artifact dev --env staging');
|
|
120
120
|
return 0;
|
|
121
121
|
}
|
|
122
122
|
async function loginCommand(args) {
|
|
@@ -382,7 +382,7 @@ async function planCommand(args) {
|
|
|
382
382
|
console.log(` source: ${result.sourceHash}`);
|
|
383
383
|
console.log(` bundle: ${result.bundleHash}`);
|
|
384
384
|
if (!token) {
|
|
385
|
-
console.log(' remote: skipped (run
|
|
385
|
+
console.log(' remote: skipped (run seq-studio login)');
|
|
386
386
|
return 0;
|
|
387
387
|
}
|
|
388
388
|
const remote = await fetchRemoteProject({ env: env.url, token, slug: result.manifest.artifact.project_id });
|
|
@@ -709,7 +709,7 @@ async function ensureRemoteProject({ env, token, result, linkConfigDir, allowCre
|
|
|
709
709
|
if (!allowCreate) {
|
|
710
710
|
throw new Error(`project "${result.manifest.artifact.project_id}" does not exist on ${env} and --no-create is set. ` +
|
|
711
711
|
`First-time provisioning is a human step: run \`seq-studio artifact deploy . -e <env>\` once with your ` +
|
|
712
|
-
`own login (
|
|
712
|
+
`own login (\`seq-studio login\`) to create the project, then the CI sweep keeps it updated.`);
|
|
713
713
|
}
|
|
714
714
|
const created = await postJson({
|
|
715
715
|
baseUrl: env,
|
|
@@ -787,7 +787,7 @@ export function setTokenProvider(provider) {
|
|
|
787
787
|
async function getRequiredToken(args) {
|
|
788
788
|
const token = await getOptionalToken(args);
|
|
789
789
|
if (!token)
|
|
790
|
-
throw new Error('Missing token. Run
|
|
790
|
+
throw new Error('Missing token. Run seq-studio login or pass --token.');
|
|
791
791
|
return token;
|
|
792
792
|
}
|
|
793
793
|
async function getOptionalToken(args) {
|
package/dist/git-clone.js
CHANGED
|
@@ -42,14 +42,18 @@ export function redactCloneUrl(url) {
|
|
|
42
42
|
}
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Abort
|
|
46
|
-
*
|
|
47
|
-
*
|
|
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 =
|
|
51
|
-
/** Backstop hard deadline:
|
|
52
|
-
const GIT_CLONE_TIMEOUT_MS =
|
|
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++) {
|
package/dist/prepare-build.d.ts
CHANGED
|
@@ -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. */
|
package/dist/prepare-build.js
CHANGED
|
@@ -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(
|
|
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
|
-
|
|
76
|
-
|
|
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
|
|
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(`
|
|
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 });
|
package/dist/source-resolver.js
CHANGED
|
@@ -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.
|
|
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
|
package/package.json
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"publishConfig": {
|
|
10
10
|
"access": "public"
|
|
11
11
|
},
|
|
12
|
-
"version": "0.1.
|
|
12
|
+
"version": "0.1.10",
|
|
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/
|
|
55
|
-
"@sequenceholdings/
|
|
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",
|