@sequenceholdings/artifact-studio 0.2.1 → 0.2.3
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/VERSION-PIN.md +12 -9
- package/dist/active-deploy-policy.d.ts +4 -4
- package/dist/active-deploy-policy.js +9 -6
- package/dist/atlas-ui-css-plugin.d.ts +29 -8
- package/dist/atlas-ui-css-plugin.js +52 -20
- package/dist/build-provenance.d.ts +50 -0
- package/dist/build-provenance.js +44 -0
- package/dist/build-subprocess.d.ts +3 -1
- package/dist/build-subprocess.js +5 -2
- package/dist/build-worker.js +4 -2
- package/dist/build.d.ts +15 -13
- package/dist/build.js +89 -30
- package/dist/cli.js +42 -14
- package/dist/dev-link.d.ts +10 -6
- package/dist/dev-link.js +42 -8
- package/dist/manifest.d.ts +1 -1
- package/dist/reporting-limits.d.ts +17 -0
- package/dist/reporting-limits.js +17 -0
- package/dist/sdk.d.ts +2 -2
- package/dist/source-boundary.d.ts +13 -0
- package/dist/source-boundary.js +54 -0
- package/dist/source-resolver.js +6 -1
- package/dist/templates/react-vite/AGENTS.md +1 -1
- package/dist/templates/react-vite/package.json +3 -3
- package/dist/templates/react-vite/src/styles.css +0 -2
- package/dist/token-contract.d.ts +16 -0
- package/dist/token-contract.js +245 -0
- package/package.json +10 -6
- package/templates/react-vite/AGENTS.md +1 -1
- package/templates/react-vite/package.json +3 -3
- package/templates/react-vite/src/styles.css +0 -2
package/dist/cli.js
CHANGED
|
@@ -13,7 +13,7 @@ import { hasArtifactStudioManifest, readArtifactStudioSource } from './project.j
|
|
|
13
13
|
import { localGitMetadata, parseSourceSpec, resolveArtifactSource, } from './source-resolver.js';
|
|
14
14
|
import { acquireDevLock } from './dev-lock.js';
|
|
15
15
|
import { runWatchLoop } from './watch-loop.js';
|
|
16
|
-
import { hashLinkedSources, parseLinks, rebuildLinkedDep, resolveLinkedDeps, syncLinkedDist, } from './dev-link.js';
|
|
16
|
+
import { hashLinkedSources, hasLinkedAtlasUiToolchain, parseLinks, rebuildLinkedDep, resolveLinkedDeps, syncLinkedDist, } from './dev-link.js';
|
|
17
17
|
import { lintArtifactSandbox, reportSandboxWarnings } from './sandbox-lint.js';
|
|
18
18
|
import { assertActiveDeployProvenance } from './active-deploy-policy.js';
|
|
19
19
|
import yaml from 'js-yaml';
|
|
@@ -43,10 +43,12 @@ const USAGE = `usage:
|
|
|
43
43
|
repo's default branch). The target project comes from the source's
|
|
44
44
|
artifact.bundle.yml project_id.
|
|
45
45
|
|
|
46
|
-
Official (active) deploys
|
|
47
|
-
git commit
|
|
48
|
-
|
|
49
|
-
|
|
46
|
+
Official (active) deploys to deployed environments require \`main\` with a
|
|
47
|
+
resolved git commit and a clean artifact source directory. Dirty files
|
|
48
|
+
outside a nested local source directory are ignored with a warning.
|
|
49
|
+
Deployments targeting \`local\` may use any branch, including dirty working
|
|
50
|
+
trees; feature branches and dirty source trees remain preview-only for
|
|
51
|
+
deployed environments (\`artifact dev\` / preview channel).
|
|
50
52
|
|
|
51
53
|
Interactive --repo builds clone over smart-HTTP and require a repo:read git
|
|
52
54
|
PAT in ATLAS_GIT_PAT — create one with \`seq-studio auth pat create --scopes
|
|
@@ -327,6 +329,10 @@ function logActiveDeploymentDetails(deployment) {
|
|
|
327
329
|
if (deployment.cliVersion || deployment.atlasUiVersion) {
|
|
328
330
|
console.log(` peers: artifact-studio@${deployment.cliVersion ?? '?'} atlas-ui@${deployment.atlasUiVersion ?? '?'}`);
|
|
329
331
|
}
|
|
332
|
+
if (deployment.observedBuildEvidence) {
|
|
333
|
+
console.log(` observed: ${deployment.observedBuildEvidence.channel} / ${deployment.observedBuildEvidence.integrityReceipt?.status ?? 'no receipt'} / ${deployment.observedBuildEvidence.toolchain.selection} toolchain ` +
|
|
334
|
+
`(atlas-ui ${deployment.observedBuildEvidence.atlasUi.contentHash.slice(0, 12)})`);
|
|
335
|
+
}
|
|
330
336
|
if (deployment.deployMessage)
|
|
331
337
|
console.log(` message: ${deployment.deployMessage}`);
|
|
332
338
|
if (deployment.createdAt)
|
|
@@ -347,6 +353,11 @@ async function buildPreparedArtifactSource(source) {
|
|
|
347
353
|
await prepareArtifactBuildRoot(source.dir, { remote: source.remote });
|
|
348
354
|
return buildArtifactStudioProjectIsolated(source.dir);
|
|
349
355
|
}
|
|
356
|
+
function reportBuildDiagnostics(result) {
|
|
357
|
+
for (const diagnostic of result.diagnostics ?? []) {
|
|
358
|
+
console.warn(`[seq-studio] warning: ${diagnostic}`);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
350
361
|
async function buildCommand(args) {
|
|
351
362
|
const spec = parseSourceSpec(args);
|
|
352
363
|
// Local builds stay offline. Public git URLs need no bearer token, but an
|
|
@@ -365,6 +376,7 @@ async function buildCommand(args) {
|
|
|
365
376
|
const source = await resolveArtifactSource(spec, remote);
|
|
366
377
|
try {
|
|
367
378
|
const result = await buildPreparedArtifactSource(source);
|
|
379
|
+
reportBuildDiagnostics(result);
|
|
368
380
|
// A remote source's dir is a temp dir we tear down below, so default the
|
|
369
381
|
// bundle output next to the cwd rather than into the throwaway tree.
|
|
370
382
|
const defaultOutDir = spec.kind === 'local' ? source.dir : process.cwd();
|
|
@@ -394,6 +406,7 @@ async function planCommand(args) {
|
|
|
394
406
|
});
|
|
395
407
|
try {
|
|
396
408
|
const result = await buildPreparedArtifactSource(source);
|
|
409
|
+
reportBuildDiagnostics(result);
|
|
397
410
|
console.log(`[seq-studio] plan ${result.manifest.artifact.project_id} -> ${env.name}`);
|
|
398
411
|
console.log(` source: ${result.sourceHash}`);
|
|
399
412
|
console.log(` bundle: ${result.bundleHash}`);
|
|
@@ -443,9 +456,11 @@ async function deployCommand(args) {
|
|
|
443
456
|
token,
|
|
444
457
|
});
|
|
445
458
|
try {
|
|
446
|
-
// Fail closed before build / project create
|
|
447
|
-
//
|
|
448
|
-
|
|
459
|
+
// Fail closed before build / project create for deployed environments.
|
|
460
|
+
// Local Atlas is a developer's own target, so it intentionally accepts
|
|
461
|
+
// the current branch and dirty working tree.
|
|
462
|
+
if (env.name !== 'local')
|
|
463
|
+
assertActiveDeployProvenance(source.provenance);
|
|
449
464
|
// --skip-unchanged: hash the raw source (no build needed) and no-op when
|
|
450
465
|
// the remote active deployment already matches *and* was built with the
|
|
451
466
|
// same CLI/atlas-ui pin. Lets CI redeploy every artifact on every release
|
|
@@ -472,6 +487,7 @@ async function deployCommand(args) {
|
|
|
472
487
|
}
|
|
473
488
|
}
|
|
474
489
|
const result = await buildPreparedArtifactSource(source);
|
|
490
|
+
reportBuildDiagnostics(result);
|
|
475
491
|
warnIfInlinedManifestDriftsFromBundle(result);
|
|
476
492
|
// Local sources read/write the project link in their working dir; remote
|
|
477
493
|
// sources resolve strictly by manifest project_id (never a stray config.json
|
|
@@ -517,6 +533,7 @@ async function devCommand(args) {
|
|
|
517
533
|
// refresh the artifact automatically, instead of the rebuild/reinstall/restart
|
|
518
534
|
// dance. Resolved once up front so a bad path fails fast.
|
|
519
535
|
const linkedDeps = resolveLinkedDeps({ links: parseLinks(args.flags.link) });
|
|
536
|
+
const candidateTokenCompatibility = hasLinkedAtlasUiToolchain({ deps: linkedDeps });
|
|
520
537
|
for (const dep of linkedDeps) {
|
|
521
538
|
const where = relative(process.cwd(), dep.dir) || '.';
|
|
522
539
|
console.log(`[seq-studio] linked ${dep.name} ← ${where}${dep.inPlace ? ' (in place)' : ''}`);
|
|
@@ -555,7 +572,11 @@ async function devCommand(args) {
|
|
|
555
572
|
// Vite builds leak memory that GC can't reclaim and eventually OOM a
|
|
556
573
|
// long-running watch. Isolating each build keeps memory flat. The signal
|
|
557
574
|
// reaps the child if the watcher is shutting down mid-build.
|
|
558
|
-
const result = await buildArtifactStudioProjectIsolated(dir, {
|
|
575
|
+
const result = await buildArtifactStudioProjectIsolated(dir, {
|
|
576
|
+
signal: controller.signal,
|
|
577
|
+
candidateTokenCompatibility,
|
|
578
|
+
});
|
|
579
|
+
reportBuildDiagnostics(result);
|
|
559
580
|
warnIfInlinedManifestDriftsFromBundle(result);
|
|
560
581
|
// The tree can change between the cheap pre-read above and the build
|
|
561
582
|
// finishing — including reverting back to the last-shipped snapshot. Trust
|
|
@@ -580,6 +601,7 @@ async function devCommand(args) {
|
|
|
580
601
|
result,
|
|
581
602
|
channel: 'preview',
|
|
582
603
|
previewKey,
|
|
604
|
+
linked: linkedDeps.length > 0,
|
|
583
605
|
provenance: localGitMetadata(dir),
|
|
584
606
|
message: `Preview push ${buildCount}`,
|
|
585
607
|
});
|
|
@@ -811,7 +833,7 @@ async function ensureRemoteProject({ env, token, result, linkConfigDir, allowCre
|
|
|
811
833
|
await writeLocalConfig({ ...config, projectId: created.project.id, projectSlug: created.project.slug }, linkConfigDir);
|
|
812
834
|
return created.project;
|
|
813
835
|
}
|
|
814
|
-
async function uploadDeployment({ env, token, projectId, result, channel, previewKey, provenance, message, }) {
|
|
836
|
+
async function uploadDeployment({ env, token, projectId, result, channel, previewKey, linked = false, provenance, message, }) {
|
|
815
837
|
// Active deploys omit `version`: the server derives it from its own
|
|
816
838
|
// sourceHash and de-dupes against the (projectId, version) unique
|
|
817
839
|
// constraint. Pinning it client-side is how a stale CLI-side hash turned
|
|
@@ -834,12 +856,18 @@ async function uploadDeployment({ env, token, projectId, result, channel, previe
|
|
|
834
856
|
bundleFormat: result.bundleFormat,
|
|
835
857
|
sourceMap: result.sourceMap,
|
|
836
858
|
...(version ? { version } : {}),
|
|
837
|
-
channel,
|
|
838
859
|
previewKey,
|
|
839
860
|
deployMessage: message ?? null,
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
861
|
+
observedBuildEvidence: {
|
|
862
|
+
...result.observedBuildEvidence,
|
|
863
|
+
toolchain: { ...result.observedBuildEvidence.toolchain, selection: linked ? 'linked' : 'published' },
|
|
864
|
+
channel,
|
|
865
|
+
sourceRevision: {
|
|
866
|
+
commit: provenance.gitCommit,
|
|
867
|
+
branch: provenance.gitBranch,
|
|
868
|
+
dirty: provenance.gitDirty,
|
|
869
|
+
},
|
|
870
|
+
},
|
|
843
871
|
},
|
|
844
872
|
});
|
|
845
873
|
return data.deployment;
|
package/dist/dev-link.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
*
|
|
10
10
|
* `--link` collapses that to zero steps: the watcher hashes the linked package's
|
|
11
11
|
* source each tick, and on change rebuilds the package and copies its fresh `dist`
|
|
12
|
-
* into the exact location the artifact build resolves
|
|
13
|
-
* artifact rebuild + preview push.
|
|
12
|
+
* plus package-root CSS entries into the exact location the artifact build resolves
|
|
13
|
+
* them from — then forces an artifact rebuild + preview push.
|
|
14
14
|
*/
|
|
15
15
|
export interface LinkedDep {
|
|
16
16
|
/** Absolute path to the linked source package (e.g. shared/services/atlas-ui). */
|
|
@@ -26,6 +26,10 @@ export interface LinkedDep {
|
|
|
26
26
|
/** True when the artifact resolves the package straight from `dir` (symlinked file: dep). */
|
|
27
27
|
inPlace: boolean;
|
|
28
28
|
}
|
|
29
|
+
/** True only when this dev session explicitly selected a local Atlas UI package. */
|
|
30
|
+
export declare function hasLinkedAtlasUiToolchain({ deps }: {
|
|
31
|
+
deps: readonly LinkedDep[];
|
|
32
|
+
}): boolean;
|
|
29
33
|
/** Resolve `${name}/package.json` the same way the build worker does. */
|
|
30
34
|
type PackageResolver = (request: string) => string;
|
|
31
35
|
/** Split a `--link a,b` flag value into trimmed, non-empty paths. */
|
|
@@ -51,10 +55,10 @@ export declare function hashLinkedSources(deps: readonly LinkedDep[]): Promise<s
|
|
|
51
55
|
/** Run a linked package's `build` script in a child process. */
|
|
52
56
|
export declare function rebuildLinkedDep(dep: LinkedDep, signal?: AbortSignal): Promise<void>;
|
|
53
57
|
/**
|
|
54
|
-
* Copy a linked package's freshly built `dist`
|
|
55
|
-
* build resolves
|
|
56
|
-
* in place (symlinked file: dep), where the rebuild
|
|
57
|
-
* build will read.
|
|
58
|
+
* Copy a linked package's freshly built `dist` and root CSS entries into the
|
|
59
|
+
* location the artifact build resolves them from. A no-op when the artifact
|
|
60
|
+
* already resolves the package in place (symlinked file: dep), where the rebuild
|
|
61
|
+
* already wrote the bytes the build will read.
|
|
58
62
|
*/
|
|
59
63
|
export declare function syncLinkedDist(dep: LinkedDep): Promise<void>;
|
|
60
64
|
export {};
|
package/dist/dev-link.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { createHash } from 'node:crypto';
|
|
3
3
|
import { existsSync, readFileSync, realpathSync } from 'node:fs';
|
|
4
|
-
import { cp, readFile, readdir, rm } from 'node:fs/promises';
|
|
4
|
+
import { cp, mkdir, readFile, readdir, rm } from 'node:fs/promises';
|
|
5
5
|
import { createRequire } from 'node:module';
|
|
6
6
|
import { dirname, join, relative, resolve } from 'node:path';
|
|
7
|
+
/** True only when this dev session explicitly selected a local Atlas UI package. */
|
|
8
|
+
export function hasLinkedAtlasUiToolchain({ deps }) {
|
|
9
|
+
return deps.some((dep) => dep.name === '@sequenceholdings/atlas-ui');
|
|
10
|
+
}
|
|
7
11
|
const WATCHED_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.css', '.json']);
|
|
8
12
|
const IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', '.next', '.turbo', '.cache']);
|
|
9
13
|
const defaultResolver = createRequire(import.meta.url).resolve;
|
|
@@ -72,9 +76,26 @@ export async function hashLinkedSources(deps) {
|
|
|
72
76
|
for (const dep of deps) {
|
|
73
77
|
hash.update(dep.name);
|
|
74
78
|
hash.update('\0');
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
79
|
+
const sourceRoots = [
|
|
80
|
+
{ label: 'src', dir: dep.srcDir },
|
|
81
|
+
// Atlas UI emits its package-root token entries from this generator.
|
|
82
|
+
// Watching scripts means edits to that generator trigger a linked preview.
|
|
83
|
+
{ label: 'scripts', dir: join(dep.dir, 'scripts') },
|
|
84
|
+
];
|
|
85
|
+
for (const sourceRoot of sourceRoots) {
|
|
86
|
+
const files = (await collectFiles(sourceRoot.dir)).sort((a, b) => a.localeCompare(b));
|
|
87
|
+
for (const file of files) {
|
|
88
|
+
hash.update(sourceRoot.label);
|
|
89
|
+
hash.update('/');
|
|
90
|
+
hash.update(relative(sourceRoot.dir, file));
|
|
91
|
+
hash.update('\0');
|
|
92
|
+
hash.update(await readFile(file));
|
|
93
|
+
hash.update('\0');
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
for (const file of await packageRootCssFiles(dep.dir)) {
|
|
97
|
+
hash.update('root-css/');
|
|
98
|
+
hash.update(relative(dep.dir, file));
|
|
78
99
|
hash.update('\0');
|
|
79
100
|
hash.update(await readFile(file));
|
|
80
101
|
hash.update('\0');
|
|
@@ -82,6 +103,14 @@ export async function hashLinkedSources(deps) {
|
|
|
82
103
|
}
|
|
83
104
|
return hash.digest('hex');
|
|
84
105
|
}
|
|
106
|
+
async function packageRootCssFiles(pkgDir) {
|
|
107
|
+
if (!existsSync(pkgDir))
|
|
108
|
+
return [];
|
|
109
|
+
const entries = await readdir(pkgDir, { withFileTypes: true });
|
|
110
|
+
return entries
|
|
111
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith('.css'))
|
|
112
|
+
.map((entry) => join(pkgDir, entry.name));
|
|
113
|
+
}
|
|
85
114
|
async function collectFiles(root) {
|
|
86
115
|
if (!existsSync(root))
|
|
87
116
|
return [];
|
|
@@ -139,10 +168,10 @@ export function rebuildLinkedDep(dep, signal) {
|
|
|
139
168
|
});
|
|
140
169
|
}
|
|
141
170
|
/**
|
|
142
|
-
* Copy a linked package's freshly built `dist`
|
|
143
|
-
* build resolves
|
|
144
|
-
* in place (symlinked file: dep), where the rebuild
|
|
145
|
-
* build will read.
|
|
171
|
+
* Copy a linked package's freshly built `dist` and root CSS entries into the
|
|
172
|
+
* location the artifact build resolves them from. A no-op when the artifact
|
|
173
|
+
* already resolves the package in place (symlinked file: dep), where the rebuild
|
|
174
|
+
* already wrote the bytes the build will read.
|
|
146
175
|
*/
|
|
147
176
|
export async function syncLinkedDist(dep) {
|
|
148
177
|
if (dep.inPlace)
|
|
@@ -150,6 +179,11 @@ export async function syncLinkedDist(dep) {
|
|
|
150
179
|
if (!existsSync(dep.distDir)) {
|
|
151
180
|
throw new Error(`${dep.name} build produced no dist at ${dep.distDir}`);
|
|
152
181
|
}
|
|
182
|
+
const targetPkgDir = dirname(dep.targetDistDir);
|
|
183
|
+
await mkdir(targetPkgDir, { recursive: true });
|
|
153
184
|
await rm(dep.targetDistDir, { recursive: true, force: true });
|
|
154
185
|
await cp(dep.distDir, dep.targetDistDir, { recursive: true });
|
|
186
|
+
for (const file of await packageRootCssFiles(dep.dir)) {
|
|
187
|
+
await cp(file, join(targetPkgDir, relative(dep.dir, file)));
|
|
188
|
+
}
|
|
155
189
|
}
|
package/dist/manifest.d.ts
CHANGED
|
@@ -58,9 +58,9 @@ export declare const artifactStudioManifestSchema: z.ZodObject<{
|
|
|
58
58
|
targets: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
59
59
|
url: z.ZodString;
|
|
60
60
|
visibility: z.ZodDefault<z.ZodEnum<{
|
|
61
|
+
published: "published";
|
|
61
62
|
private: "private";
|
|
62
63
|
shared: "shared";
|
|
63
|
-
published: "published";
|
|
64
64
|
}>>;
|
|
65
65
|
}, z.core.$strip>>>;
|
|
66
66
|
sidebarBadge: z.ZodOptional<z.ZodObject<{
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Max length of user-authored SQL accepted by reporting routes. */
|
|
2
|
+
export declare const REPORTING_MAX_SQL_CHARS = 500000;
|
|
3
|
+
/** Max decoded file size for manual report uploads. */
|
|
4
|
+
export declare const UPLOAD_MAX_BYTES: number;
|
|
5
|
+
/** Max length of a base64-encoded upload before decoding. */
|
|
6
|
+
export declare const UPLOAD_MAX_BASE64_CHARS: number;
|
|
7
|
+
/** Formatted bundle transport and file boundaries. */
|
|
8
|
+
export declare const FORMATTED_BUNDLE_MAX_BYTES: number;
|
|
9
|
+
/** Reserve space for multipart boundaries and per-file headers in the artifact. */
|
|
10
|
+
export declare const FORMATTED_BUNDLE_MULTIPART_OVERHEAD_RESERVE_BYTES: number;
|
|
11
|
+
export declare const FORMATTED_BUNDLE_MAX_DEFINITION_BYTES: number;
|
|
12
|
+
export declare const FORMATTED_BUNDLE_MAX_SQL_FILES = 50;
|
|
13
|
+
export declare const FORMATTED_BUNDLE_MAX_SQL_BYTES: number;
|
|
14
|
+
export declare const FORMATTED_BUNDLE_MAX_SQL_CHARS = 500000;
|
|
15
|
+
export declare const FORMATTED_BUNDLE_MAX_TOTAL_SQL_CHARS: number;
|
|
16
|
+
/** The definition and multipart contracts must reject the same input count. */
|
|
17
|
+
export declare const MAX_FORMATTED_REPORT_INPUTS = 50;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Max length of user-authored SQL accepted by reporting routes. */
|
|
2
|
+
export const REPORTING_MAX_SQL_CHARS = 500_000;
|
|
3
|
+
/** Max decoded file size for manual report uploads. */
|
|
4
|
+
export const UPLOAD_MAX_BYTES = 20 * 1024 * 1024;
|
|
5
|
+
/** Max length of a base64-encoded upload before decoding. */
|
|
6
|
+
export const UPLOAD_MAX_BASE64_CHARS = Math.ceil(UPLOAD_MAX_BYTES / 3) * 4;
|
|
7
|
+
/** Formatted bundle transport and file boundaries. */
|
|
8
|
+
export const FORMATTED_BUNDLE_MAX_BYTES = 20 * 1024 * 1024;
|
|
9
|
+
/** Reserve space for multipart boundaries and per-file headers in the artifact. */
|
|
10
|
+
export const FORMATTED_BUNDLE_MULTIPART_OVERHEAD_RESERVE_BYTES = 64 * 1024;
|
|
11
|
+
export const FORMATTED_BUNDLE_MAX_DEFINITION_BYTES = 2 * 1024 * 1024;
|
|
12
|
+
export const FORMATTED_BUNDLE_MAX_SQL_FILES = 50;
|
|
13
|
+
export const FORMATTED_BUNDLE_MAX_SQL_BYTES = 2 * 1024 * 1024;
|
|
14
|
+
export const FORMATTED_BUNDLE_MAX_SQL_CHARS = REPORTING_MAX_SQL_CHARS;
|
|
15
|
+
export const FORMATTED_BUNDLE_MAX_TOTAL_SQL_CHARS = 10 * REPORTING_MAX_SQL_CHARS;
|
|
16
|
+
/** The definition and multipart contracts must reject the same input count. */
|
|
17
|
+
export const MAX_FORMATTED_REPORT_INPUTS = FORMATTED_BUNDLE_MAX_SQL_FILES;
|
package/dist/sdk.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface SequenceApiUploadOptions {
|
|
|
19
19
|
}
|
|
20
20
|
export interface SequenceApi {
|
|
21
21
|
fetch<TBody = unknown>(path: string, options?: SequenceApiFetchOptions): Promise<SequenceApiFetchResponse<TBody>>;
|
|
22
|
-
upload<TBody = unknown>(path: string, file: File, fields?: Record<string, string>, options?: SequenceApiUploadOptions): Promise<SequenceApiFetchResponse<TBody>>;
|
|
22
|
+
upload<TBody = unknown>(path: string, file: File | readonly File[], fields?: Record<string, string>, options?: SequenceApiUploadOptions): Promise<SequenceApiFetchResponse<TBody>>;
|
|
23
23
|
stream(path: string, options?: SequenceApiFetchOptions): Promise<ReadableStream<Uint8Array>>;
|
|
24
24
|
get<TBody = unknown>(path: string): Promise<TBody>;
|
|
25
25
|
post<TBody = unknown>(path: string, body?: unknown): Promise<TBody>;
|
|
@@ -88,7 +88,7 @@ export interface SequenceLattice {
|
|
|
88
88
|
export interface SequenceHost {
|
|
89
89
|
/**
|
|
90
90
|
* Navigate the parent Atlas shell to an in-app route — e.g.
|
|
91
|
-
* `seq.host.navigate('/
|
|
91
|
+
* `seq.host.navigate('/relationships#/customers/<uid>')`. Fire-and-forget: the artifact
|
|
92
92
|
* sandbox cannot navigate the top frame itself (no `allow-top-navigation`),
|
|
93
93
|
* so this posts the request to the host, which validates it and drives the
|
|
94
94
|
* shell router. The host ignores anything that isn't a same-app relative
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Plugin } from 'vite';
|
|
2
|
+
export interface ArtifactSourceBoundaryOptions {
|
|
3
|
+
root: string;
|
|
4
|
+
trustedExternalRoots: readonly string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Artifact source must be self-contained: allowing a relative import to escape
|
|
8
|
+
* the build root would let uncommitted files elsewhere in a containing
|
|
9
|
+
* checkout enter the official bundle without being covered by its dirty bit.
|
|
10
|
+
* Published dependencies and the CLI-pinned platform peers are intentionally
|
|
11
|
+
* allowed because they are external to the artifact by design.
|
|
12
|
+
*/
|
|
13
|
+
export declare function rejectOutsideSourcePlugin({ root, trustedExternalRoots, }: ArtifactSourceBoundaryOptions): Plugin;
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Artifact source must be self-contained: allowing a relative import to escape
|
|
4
|
+
* the build root would let uncommitted files elsewhere in a containing
|
|
5
|
+
* checkout enter the official bundle without being covered by its dirty bit.
|
|
6
|
+
* Published dependencies and the CLI-pinned platform peers are intentionally
|
|
7
|
+
* allowed because they are external to the artifact by design.
|
|
8
|
+
*/
|
|
9
|
+
export function rejectOutsideSourcePlugin({ root, trustedExternalRoots, }) {
|
|
10
|
+
const rootResolved = resolve(root);
|
|
11
|
+
const trustedRoots = trustedExternalRoots.map((trustedRoot) => resolve(trustedRoot));
|
|
12
|
+
return {
|
|
13
|
+
name: 'reject-artifact-outside-source',
|
|
14
|
+
enforce: 'pre',
|
|
15
|
+
async resolveId(source, importer, options) {
|
|
16
|
+
const resolved = await this.resolve(source, importer, { ...options, skipSelf: true });
|
|
17
|
+
if (!resolved || resolved.external || resolved.id.startsWith('\0'))
|
|
18
|
+
return resolved;
|
|
19
|
+
const filePath = resolved.id.split('?')[0];
|
|
20
|
+
if (!isAbsolute(filePath) || isPathWithin(rootResolved, filePath))
|
|
21
|
+
return resolved;
|
|
22
|
+
const importerPath = importer?.split('?')[0];
|
|
23
|
+
const importerIsTrustedExternal = importerPath != null &&
|
|
24
|
+
(isNodeModulesPath(importerPath) || isTrustedExternalPath(importerPath, trustedRoots));
|
|
25
|
+
if (isNodeModulesPath(filePath) && (isBareImport(source) || importerIsTrustedExternal)) {
|
|
26
|
+
return resolved;
|
|
27
|
+
}
|
|
28
|
+
if (isTrustedExternalPath(filePath, trustedRoots))
|
|
29
|
+
return resolved;
|
|
30
|
+
const from = importer
|
|
31
|
+
? ` (imported from ${relative(rootResolved, importer.split('?')[0])})`
|
|
32
|
+
: '';
|
|
33
|
+
throw new Error(`Artifact import "${filePath}" resolves outside artifact source directory ` +
|
|
34
|
+
`"${rootResolved}"${from}. Keep relative build inputs inside the artifact ` +
|
|
35
|
+
'source directory or publish shared code as a dependency.');
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function isPathWithin(root, candidate) {
|
|
40
|
+
const pathFromRoot = relative(root, candidate);
|
|
41
|
+
return (pathFromRoot === '' ||
|
|
42
|
+
(pathFromRoot !== '..' &&
|
|
43
|
+
!pathFromRoot.startsWith(`..${sep}`) &&
|
|
44
|
+
!isAbsolute(pathFromRoot)));
|
|
45
|
+
}
|
|
46
|
+
function isTrustedExternalPath(filePath, trustedRoots) {
|
|
47
|
+
return trustedRoots.some((trustedRoot) => isPathWithin(trustedRoot, filePath));
|
|
48
|
+
}
|
|
49
|
+
function isNodeModulesPath(filePath) {
|
|
50
|
+
return filePath.includes(`${sep}node_modules${sep}`);
|
|
51
|
+
}
|
|
52
|
+
function isBareImport(source) {
|
|
53
|
+
return !source.startsWith('.') && !isAbsolute(source);
|
|
54
|
+
}
|
package/dist/source-resolver.js
CHANGED
|
@@ -161,7 +161,12 @@ export function localGitMetadata(dir) {
|
|
|
161
161
|
if (!gitBranch) {
|
|
162
162
|
gitBranch = resolveDetachedMainBranch({ gitOpts, gitCommit }) ?? '';
|
|
163
163
|
}
|
|
164
|
-
const
|
|
164
|
+
const wholeTreeStatus = String(execFileSync('git', ['status', '--porcelain'], gitOpts)).trim();
|
|
165
|
+
const sourceStatus = String(execFileSync('git', ['status', '--porcelain', '--', '.'], gitOpts)).trim();
|
|
166
|
+
const gitDirty = sourceStatus.length > 0;
|
|
167
|
+
if (wholeTreeStatus && !gitDirty) {
|
|
168
|
+
console.warn(`[seq-studio] ignoring unrelated dirty files outside artifact source directory ${dir}`);
|
|
169
|
+
}
|
|
165
170
|
return { gitCommit, gitBranch: gitBranch || null, gitDirty };
|
|
166
171
|
}
|
|
167
172
|
catch {
|
|
@@ -36,7 +36,7 @@ Use **pnpm** for artifacts you will deploy from git — `seq-studio artifact bui
|
|
|
36
36
|
| `src/main.tsx` | Providers: `QueryClientProvider` → `PortalContainerProvider` → `App` |
|
|
37
37
|
| `src/App.tsx` | Entrypoint UI |
|
|
38
38
|
| `src/lib/api.ts` | `unwrap<T>` envelope helper + re-export of `seq` |
|
|
39
|
-
| `src/styles.css` | Tailwind v4 + `@sequenceholdings/atlas-ui/tokens.css`
|
|
39
|
+
| `src/styles.css` | Tailwind v4 + canonical `@sequenceholdings/atlas-ui/tokens.css` |
|
|
40
40
|
| `vite.config.ts` | Vite + React + Tailwind v4 plugin |
|
|
41
41
|
|
|
42
42
|
## Defaults
|
|
@@ -9,15 +9,15 @@
|
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {},
|
|
11
11
|
"devDependencies": {
|
|
12
|
-
"@sequenceholdings/artifact-studio": "^0.1
|
|
13
|
-
"@sequenceholdings/atlas-ui": "^0.1
|
|
12
|
+
"@sequenceholdings/artifact-studio": "^0.2.1",
|
|
13
|
+
"@sequenceholdings/atlas-ui": "^2.0.1",
|
|
14
14
|
"@tailwindcss/postcss": "^4.2.2",
|
|
15
15
|
"@tailwindcss/vite": "^4.2.2",
|
|
16
16
|
"@tanstack/react-query": "^4.36.1",
|
|
17
17
|
"@vitejs/plugin-react": "^5.0.0",
|
|
18
18
|
"react": "^18.2.0",
|
|
19
19
|
"react-dom": "^18.2.0",
|
|
20
|
-
"react-router-dom": "^
|
|
20
|
+
"react-router-dom": "^7.18.2",
|
|
21
21
|
"sonner": "^2.0.7",
|
|
22
22
|
"tailwindcss": "^4.2.2",
|
|
23
23
|
"tw-animate-css": "^1.4.0",
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@import "tw-animate-css";
|
|
3
3
|
@import "@sequenceholdings/atlas-ui/tokens.css";
|
|
4
|
-
@import "@sequenceholdings/atlas-ui/tokens.v2.css";
|
|
5
|
-
@import "@sequenceholdings/atlas-ui/theme.v2.css";
|
|
6
4
|
|
|
7
5
|
/* Tailwind v4 skips node_modules during automatic source detection, so the
|
|
8
6
|
utility classes compiled into atlas-ui's components are never generated under
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ArtifactStudioSourceFile } from './project.js';
|
|
2
|
+
export interface RetiredOpacityUtilityReference {
|
|
3
|
+
path: string;
|
|
4
|
+
utility: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function collectDeprecatedTokenImportDiagnostics({ files, }: {
|
|
7
|
+
files: readonly ArtifactStudioSourceFile[];
|
|
8
|
+
}): string[];
|
|
9
|
+
/**
|
|
10
|
+
* Candidate builds use a source report rather than a build-time ban. A normal
|
|
11
|
+
* CLI release must remain able to rebuild pre-cutover artifacts; an explicit
|
|
12
|
+
* `artifact dev --link <atlas-ui>` preview is the opt-in migration surface.
|
|
13
|
+
*/
|
|
14
|
+
export declare function collectCandidateTokenCompatibilityDiagnostics({ files, }: {
|
|
15
|
+
files: readonly ArtifactStudioSourceFile[];
|
|
16
|
+
}): string[];
|