@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/VERSION-PIN.md
CHANGED
|
@@ -43,12 +43,15 @@ source (and older rows with null peer fields are rebuilt once to populate them).
|
|
|
43
43
|
`seq-studio artifact plan` reports the same decision, so its `action:` line
|
|
44
44
|
never claims `no-op` for a deploy that would actually rebuild.
|
|
45
45
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
`
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
46
|
+
## CSS entry points (sandbox previews)
|
|
47
|
+
|
|
48
|
+
`tokens.css` is the canonical stylesheet and includes semantic roles plus the
|
|
49
|
+
Tailwind utility bridge. The legacy `tokens.v2.css` and `theme.v2.css` paths are
|
|
50
|
+
temporary forwarding aliases; the compiler normalizes their source imports to one
|
|
51
|
+
canonical layer and reports a migration warning. An explicit
|
|
52
|
+
`seq-studio artifact dev --link <atlas-ui-package-dir>` preview additionally
|
|
53
|
+
reports retired numbered black/white opacity utilities and uncertain dynamic
|
|
54
|
+
class construction; ordinary released-CLI builds stay compatible until global
|
|
55
|
+
cutover. The runtime forwards brand variables independently of `data-tokens`.
|
|
56
|
+
Import `tokens.css` for standalone/`vite dev` compatibility. **Do not** import
|
|
57
|
+
`tokens.v2.tenants.css`.
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Official (active-channel) Artifact Studio deploys must come from
|
|
3
|
-
*
|
|
4
|
-
* git-service repo (`--repo`) or a monorepo / local checkout.
|
|
5
|
-
* may use feature branches, dirty trees, or other refs.
|
|
2
|
+
* Official (active-channel) Artifact Studio deploys must come from `main` with
|
|
3
|
+
* a resolved commit and a clean artifact source directory — whether the source
|
|
4
|
+
* is a platform git-service repo (`--repo`) or a monorepo / local checkout.
|
|
5
|
+
* Preview deploys may use feature branches, dirty trees, or other refs.
|
|
6
6
|
*
|
|
7
7
|
* This checks reported provenance (branch name + explicit clean flag + commit).
|
|
8
8
|
* It does **not** independently verify that `gitCommit` equals the remote
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Official (active-channel) Artifact Studio deploys must come from
|
|
3
|
-
*
|
|
4
|
-
* git-service repo (`--repo`) or a monorepo / local checkout.
|
|
5
|
-
* may use feature branches, dirty trees, or other refs.
|
|
2
|
+
* Official (active-channel) Artifact Studio deploys must come from `main` with
|
|
3
|
+
* a resolved commit and a clean artifact source directory — whether the source
|
|
4
|
+
* is a platform git-service repo (`--repo`) or a monorepo / local checkout.
|
|
5
|
+
* Preview deploys may use feature branches, dirty trees, or other refs.
|
|
6
6
|
*
|
|
7
7
|
* This checks reported provenance (branch name + explicit clean flag + commit).
|
|
8
8
|
* It does **not** independently verify that `gitCommit` equals the remote
|
|
@@ -41,9 +41,12 @@ export function isActiveDeployDefaultBranch(branch) {
|
|
|
41
41
|
* Preview callers must not invoke this.
|
|
42
42
|
*/
|
|
43
43
|
export function assertActiveDeployProvenance(provenance) {
|
|
44
|
-
// Require an explicit clean recording — omitting gitDirty
|
|
44
|
+
// Require an explicit clean source-directory recording — omitting gitDirty
|
|
45
|
+
// must not pass. The local source resolver scopes this value to the
|
|
46
|
+
// artifact directory, so unrelated changes elsewhere in a containing
|
|
47
|
+
// checkout do not block an official deploy.
|
|
45
48
|
if (provenance.gitDirty !== false) {
|
|
46
|
-
throw new ActiveDeployPolicyError('Active (official) artifact deploys require a clean
|
|
49
|
+
throw new ActiveDeployPolicyError('Active (official) artifact deploys require a clean artifact source tree ' +
|
|
47
50
|
'(`gitDirty: false`). Commit or stash your changes, or use ' +
|
|
48
51
|
'`seq-studio artifact dev` / a preview deploy for dirty feature-branch work.');
|
|
49
52
|
}
|
|
@@ -1,15 +1,36 @@
|
|
|
1
1
|
import type { Plugin } from 'vite';
|
|
2
|
+
export interface AtlasUiResolvedTokenSelection {
|
|
3
|
+
specifier: '@sequenceholdings/atlas-ui/tokens.css';
|
|
4
|
+
injectedBy: string | null;
|
|
5
|
+
}
|
|
2
6
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* imports them. Remove this inject at DES-227 cutover when `tokens.css` *is* v2.
|
|
7
|
+
* Every supported source shape becomes one canonical import before Tailwind
|
|
8
|
+
* expands it. The aliases remain package-compatible, but are no longer allowed
|
|
9
|
+
* to create duplicate token layers in an Artifact Studio bundle (DES-437).
|
|
7
10
|
*/
|
|
8
|
-
export declare function
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
export declare function normalizeAtlasUiTokenImports(code: string): {
|
|
12
|
+
code: string;
|
|
13
|
+
deprecatedImports: string[];
|
|
14
|
+
};
|
|
15
|
+
/**
|
|
16
|
+
* Evidence mirrors the CSS transform: aliases are normalized away, so the only
|
|
17
|
+
* selected sheet is the canonical token entry point.
|
|
18
|
+
*/
|
|
19
|
+
export declare function collectAtlasUiResolvedTokenSelections({ cssModules, }: {
|
|
20
|
+
cssModules: readonly {
|
|
21
|
+
id: string;
|
|
22
|
+
code: string;
|
|
23
|
+
}[];
|
|
24
|
+
atlasUiDir: string;
|
|
25
|
+
atlasUiDist: string;
|
|
26
|
+
formRendererDist: string | null;
|
|
27
|
+
}): AtlasUiResolvedTokenSelection[];
|
|
28
|
+
export declare function atlasUiCssResolverPlugin({ atlasUiDir: _atlasUiDir, atlasUiDist, formRendererDist, onTransform, }: {
|
|
12
29
|
atlasUiDir: string;
|
|
13
30
|
atlasUiDist: string;
|
|
14
31
|
formRendererDist: string | null;
|
|
32
|
+
onTransform?: (cssModule: {
|
|
33
|
+
id: string;
|
|
34
|
+
code: string;
|
|
35
|
+
}) => void;
|
|
15
36
|
}): Plugin;
|
|
@@ -17,36 +17,67 @@ function resolveTwAnimateCssPath() {
|
|
|
17
17
|
}
|
|
18
18
|
return resolved;
|
|
19
19
|
}
|
|
20
|
+
const CANONICAL_TOKEN_IMPORT = '@import "@sequenceholdings/atlas-ui/tokens.css";';
|
|
21
|
+
const ATLAS_UI_TOKEN_IMPORT = /@import\s+(?:url\(\s*)?(?:["'][^"']*?@sequenceholdings\/atlas-ui\/|[^"'()\s;]*@sequenceholdings\/atlas-ui\/)(tokens\.css|tokens\.v2\.css|theme\.v2\.css)(?:["'])?\s*\)?[^;\n]*;/gi;
|
|
22
|
+
function atlasUiTokenImportSheets(code) {
|
|
23
|
+
return [...code.matchAll(ATLAS_UI_TOKEN_IMPORT)]
|
|
24
|
+
.map((match) => match[1]?.toLowerCase())
|
|
25
|
+
.filter((sheet) => sheet !== undefined);
|
|
26
|
+
}
|
|
20
27
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* imports them. Remove this inject at DES-227 cutover when `tokens.css` *is* v2.
|
|
28
|
+
* Every supported source shape becomes one canonical import before Tailwind
|
|
29
|
+
* expands it. The aliases remain package-compatible, but are no longer allowed
|
|
30
|
+
* to create duplicate token layers in an Artifact Studio bundle (DES-437).
|
|
25
31
|
*/
|
|
26
|
-
export function
|
|
27
|
-
|
|
32
|
+
export function normalizeAtlasUiTokenImports(code) {
|
|
33
|
+
const deprecatedImports = new Set();
|
|
34
|
+
const withoutTokenImports = code.replace(ATLAS_UI_TOKEN_IMPORT, (importLine, sheet) => {
|
|
35
|
+
const normalizedSheet = String(sheet).toLowerCase();
|
|
36
|
+
if (normalizedSheet !== 'tokens.css')
|
|
37
|
+
deprecatedImports.add(normalizedSheet);
|
|
38
|
+
return '';
|
|
39
|
+
});
|
|
40
|
+
return {
|
|
41
|
+
code: insertAfterImports(withoutTokenImports, [CANONICAL_TOKEN_IMPORT]),
|
|
42
|
+
deprecatedImports: [...deprecatedImports],
|
|
43
|
+
};
|
|
28
44
|
}
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
45
|
+
/**
|
|
46
|
+
* Evidence mirrors the CSS transform: aliases are normalized away, so the only
|
|
47
|
+
* selected sheet is the canonical token entry point.
|
|
48
|
+
*/
|
|
49
|
+
export function collectAtlasUiResolvedTokenSelections({ cssModules, }) {
|
|
50
|
+
for (const { code } of cssModules) {
|
|
51
|
+
if (!code.includes('@import "tailwindcss"'))
|
|
52
|
+
continue;
|
|
53
|
+
return [{
|
|
54
|
+
specifier: '@sequenceholdings/atlas-ui/tokens.css',
|
|
55
|
+
injectedBy: atlasUiTokenImportSheets(code).includes('tokens.css') ? null : 'atlas-ui-css-resolver',
|
|
56
|
+
}];
|
|
37
57
|
}
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
/** Put Tailwind source directives after the import block but before style rules. */
|
|
61
|
+
function insertAfterImports(code, lines) {
|
|
62
|
+
const importPattern = /@import\s+[^;]+;/gi;
|
|
63
|
+
let lastImport = null;
|
|
64
|
+
for (let match = importPattern.exec(code); match; match = importPattern.exec(code)) {
|
|
65
|
+
lastImport = match;
|
|
40
66
|
}
|
|
41
|
-
|
|
67
|
+
const block = `\n${lines.join('\n')}`;
|
|
68
|
+
if (lastImport?.index === undefined)
|
|
69
|
+
return `${lines.join('\n')}\n${code}`;
|
|
70
|
+
const end = lastImport.index + lastImport[0].length;
|
|
71
|
+
return `${code.slice(0, end)}${block}${code.slice(end)}`;
|
|
42
72
|
}
|
|
43
|
-
export function atlasUiCssResolverPlugin({ atlasUiDir, atlasUiDist, formRendererDist, }) {
|
|
73
|
+
export function atlasUiCssResolverPlugin({ atlasUiDir: _atlasUiDir, atlasUiDist, formRendererDist, onTransform, }) {
|
|
44
74
|
return {
|
|
45
75
|
name: 'atlas-ui-css-resolver',
|
|
46
76
|
enforce: 'pre',
|
|
47
77
|
transform(code, id) {
|
|
48
78
|
if (!id.endsWith('.css'))
|
|
49
79
|
return undefined;
|
|
80
|
+
onTransform?.({ id, code });
|
|
50
81
|
let result = code;
|
|
51
82
|
if (result.includes('@import "tailwindcss"')) {
|
|
52
83
|
result = result.replace('@import "tailwindcss"', `@import ${JSON.stringify(tailwindCssPath)}`);
|
|
@@ -57,8 +88,9 @@ export function atlasUiCssResolverPlugin({ atlasUiDir, atlasUiDist, formRenderer
|
|
|
57
88
|
if (formRendererDist) {
|
|
58
89
|
sources.push(`@source ${JSON.stringify(formRendererDist)};`);
|
|
59
90
|
}
|
|
60
|
-
const
|
|
61
|
-
result =
|
|
91
|
+
const tokenImports = normalizeAtlasUiTokenImports(result);
|
|
92
|
+
result = tokenImports.code;
|
|
93
|
+
result = insertAfterImports(result, sources);
|
|
62
94
|
}
|
|
63
95
|
if (result.includes('@import "tw-animate-css"')) {
|
|
64
96
|
result = result.replace('@import "tw-animate-css"', `@import ${JSON.stringify(twAnimateCssPath)}`);
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ArtifactStudioPeerVersions } from './peer-versions.js';
|
|
2
|
+
import type { AtlasUiResolvedTokenSelection } from './atlas-ui-css-plugin.js';
|
|
3
|
+
export declare const ARTIFACT_BUILD_PROVENANCE_VERSION = 1;
|
|
4
|
+
export interface ArtifactSourceRevision {
|
|
5
|
+
commit: string | null;
|
|
6
|
+
branch: string | null;
|
|
7
|
+
dirty: boolean | null;
|
|
8
|
+
}
|
|
9
|
+
export interface ArtifactTokenResolution {
|
|
10
|
+
specifier: string;
|
|
11
|
+
resolvedPath: string;
|
|
12
|
+
contentHash: string;
|
|
13
|
+
cssLayers: string[];
|
|
14
|
+
injectedBy: string | null;
|
|
15
|
+
}
|
|
16
|
+
/** Observed local build evidence; values are not independently verified by Atlas. */
|
|
17
|
+
export interface ObservedBuildEvidence {
|
|
18
|
+
schemaVersion: typeof ARTIFACT_BUILD_PROVENANCE_VERSION;
|
|
19
|
+
channel: 'active' | 'preview';
|
|
20
|
+
toolchain: {
|
|
21
|
+
cliVersion: string;
|
|
22
|
+
selection: 'published' | 'linked';
|
|
23
|
+
};
|
|
24
|
+
atlasUi: {
|
|
25
|
+
version: string;
|
|
26
|
+
contentHash: string;
|
|
27
|
+
};
|
|
28
|
+
tokenResolution: {
|
|
29
|
+
modelVersion: 1;
|
|
30
|
+
entries: ArtifactTokenResolution[];
|
|
31
|
+
};
|
|
32
|
+
sourceRevision: ArtifactSourceRevision;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Evidence from the exact Atlas UI alias targets selected by this build. The
|
|
36
|
+
* format deliberately records paths and resolved layers, rather than naming a
|
|
37
|
+
* migration generation; future canonical token entry points need no schema
|
|
38
|
+
* change.
|
|
39
|
+
*/
|
|
40
|
+
export declare function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTokens, channel, linked, peerVersions, sourceRevision, tokenSelections }: {
|
|
41
|
+
atlasUiDir: string;
|
|
42
|
+
atlasUiDist: string;
|
|
43
|
+
/** Exact tokens.css alias target; releases and linked candidates use different layouts. */
|
|
44
|
+
atlasUiTokens: string;
|
|
45
|
+
channel: ObservedBuildEvidence['channel'];
|
|
46
|
+
linked: boolean;
|
|
47
|
+
peerVersions: ArtifactStudioPeerVersions;
|
|
48
|
+
sourceRevision: ArtifactSourceRevision;
|
|
49
|
+
tokenSelections: readonly AtlasUiResolvedTokenSelection[];
|
|
50
|
+
}): ObservedBuildEvidence;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { relative } from 'node:path';
|
|
3
|
+
import { sha256Hex } from './hash.js';
|
|
4
|
+
export const ARTIFACT_BUILD_PROVENANCE_VERSION = 1;
|
|
5
|
+
function cssLayers(content) {
|
|
6
|
+
return [...content.matchAll(/@layer\s+([\w-]+)/g)].map((match) => match[1]).filter((x) => x !== undefined);
|
|
7
|
+
}
|
|
8
|
+
function contentIdentity(entries) {
|
|
9
|
+
return sha256Hex(entries.slice().sort((a, b) => a.path.localeCompare(b.path)).map((entry) => `${entry.path}\0${entry.contentHash}`).join('\0'));
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Evidence from the exact Atlas UI alias targets selected by this build. The
|
|
13
|
+
* format deliberately records paths and resolved layers, rather than naming a
|
|
14
|
+
* migration generation; future canonical token entry points need no schema
|
|
15
|
+
* change.
|
|
16
|
+
*/
|
|
17
|
+
export function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTokens, channel, linked, peerVersions, sourceRevision, tokenSelections }) {
|
|
18
|
+
const knownTargets = new Map([
|
|
19
|
+
['@sequenceholdings/atlas-ui/tokens.css', { filePath: atlasUiTokens }],
|
|
20
|
+
['@sequenceholdings/atlas-ui/tokens.v2.css', { filePath: `${atlasUiDir}/tokens.v2.css` }],
|
|
21
|
+
['@sequenceholdings/atlas-ui/theme.v2.css', { filePath: `${atlasUiDir}/theme.v2.css` }],
|
|
22
|
+
]);
|
|
23
|
+
const entries = tokenSelections.map((target) => {
|
|
24
|
+
const resolved = knownTargets.get(target.specifier);
|
|
25
|
+
if (!resolved)
|
|
26
|
+
throw new Error(`Unknown Atlas UI token target: ${target.specifier}`);
|
|
27
|
+
const filePath = resolved.filePath;
|
|
28
|
+
const content = readFileSync(filePath, 'utf8');
|
|
29
|
+
return { specifier: target.specifier, resolvedPath: relative(atlasUiDir, filePath), contentHash: sha256Hex(content), cssLayers: cssLayers(content), injectedBy: target.injectedBy };
|
|
30
|
+
});
|
|
31
|
+
const identityEntries = [
|
|
32
|
+
...entries.map((entry) => ({ path: entry.resolvedPath, contentHash: entry.contentHash })),
|
|
33
|
+
{ path: 'dist/index.mjs', contentHash: sha256Hex(readFileSync(`${atlasUiDist}/index.mjs`)) },
|
|
34
|
+
{ path: 'dist/charts.mjs', contentHash: sha256Hex(readFileSync(`${atlasUiDist}/charts.mjs`)) },
|
|
35
|
+
];
|
|
36
|
+
return {
|
|
37
|
+
schemaVersion: ARTIFACT_BUILD_PROVENANCE_VERSION,
|
|
38
|
+
channel,
|
|
39
|
+
toolchain: { cliVersion: peerVersions.cliVersion, selection: linked ? 'linked' : 'published' },
|
|
40
|
+
atlasUi: { version: peerVersions.atlasUiVersion, contentHash: contentIdentity(identityEntries) },
|
|
41
|
+
tokenResolution: { modelVersion: 1, entries },
|
|
42
|
+
sourceRevision,
|
|
43
|
+
};
|
|
44
|
+
}
|
|
@@ -6,6 +6,8 @@ export interface IsolatedBuildOptions {
|
|
|
6
6
|
signal?: AbortSignal;
|
|
7
7
|
/** Parent environment override for deterministic security tests. */
|
|
8
8
|
sourceEnv?: NodeJS.ProcessEnv;
|
|
9
|
+
/** Explicitly report canonical-token compatibility for an Atlas UI --link preview. */
|
|
10
|
+
candidateTokenCompatibility?: boolean;
|
|
9
11
|
}
|
|
10
12
|
/**
|
|
11
13
|
* Run a single artifact build in a short-lived child process and return its
|
|
@@ -14,4 +16,4 @@ export interface IsolatedBuildOptions {
|
|
|
14
16
|
* long-running watch loops; one-shot commands can call
|
|
15
17
|
* `buildArtifactStudioProject` directly.
|
|
16
18
|
*/
|
|
17
|
-
export declare function buildArtifactStudioProjectIsolated(dir: string, { workerPath, signal, sourceEnv }?: IsolatedBuildOptions): Promise<ArtifactStudioBuildResult>;
|
|
19
|
+
export declare function buildArtifactStudioProjectIsolated(dir: string, { workerPath, signal, sourceEnv, candidateTokenCompatibility }?: IsolatedBuildOptions): Promise<ArtifactStudioBuildResult>;
|
package/dist/build-subprocess.js
CHANGED
|
@@ -17,7 +17,7 @@ const DEFAULT_WORKER_PATH = existsSync(BUILT_WORKER_PATH)
|
|
|
17
17
|
* long-running watch loops; one-shot commands can call
|
|
18
18
|
* `buildArtifactStudioProject` directly.
|
|
19
19
|
*/
|
|
20
|
-
export async function buildArtifactStudioProjectIsolated(dir, { workerPath = DEFAULT_WORKER_PATH, signal, sourceEnv } = {}) {
|
|
20
|
+
export async function buildArtifactStudioProjectIsolated(dir, { workerPath = DEFAULT_WORKER_PATH, signal, sourceEnv, candidateTokenCompatibility = false } = {}) {
|
|
21
21
|
const scratch = await mkdtemp(join(tmpdir(), 'artifact-build-'));
|
|
22
22
|
const outFile = join(scratch, 'result.json');
|
|
23
23
|
const homeDir = join(scratch, 'home');
|
|
@@ -29,6 +29,7 @@ export async function buildArtifactStudioProjectIsolated(dir, { workerPath = DEF
|
|
|
29
29
|
outFile,
|
|
30
30
|
homeDir,
|
|
31
31
|
signal,
|
|
32
|
+
candidateTokenCompatibility,
|
|
32
33
|
...(sourceEnv ? { sourceEnv } : {}),
|
|
33
34
|
});
|
|
34
35
|
return JSON.parse(await readFile(outFile, 'utf8'));
|
|
@@ -37,7 +38,7 @@ export async function buildArtifactStudioProjectIsolated(dir, { workerPath = DEF
|
|
|
37
38
|
await rm(scratch, { recursive: true, force: true });
|
|
38
39
|
}
|
|
39
40
|
}
|
|
40
|
-
function runWorker({ workerPath, dir, outFile, homeDir, signal, sourceEnv, }) {
|
|
41
|
+
function runWorker({ workerPath, dir, outFile, homeDir, signal, sourceEnv, candidateTokenCompatibility, }) {
|
|
41
42
|
return new Promise((resolve, reject) => {
|
|
42
43
|
if (signal?.aborted) {
|
|
43
44
|
reject(new Error('build aborted'));
|
|
@@ -46,6 +47,8 @@ function runWorker({ workerPath, dir, outFile, homeDir, signal, sourceEnv, }) {
|
|
|
46
47
|
const workerArgs = workerPath.endsWith('.ts')
|
|
47
48
|
? ['--import', 'tsx', workerPath, dir, outFile]
|
|
48
49
|
: [workerPath, dir, outFile];
|
|
50
|
+
if (candidateTokenCompatibility)
|
|
51
|
+
workerArgs.push('--candidate-token-compatibility');
|
|
49
52
|
const child = spawn(process.execPath, workerArgs, {
|
|
50
53
|
stdio: ['ignore', 'inherit', 'pipe'],
|
|
51
54
|
env: createScrubbedChildEnv({
|
package/dist/build-worker.js
CHANGED
|
@@ -13,13 +13,15 @@
|
|
|
13
13
|
import { writeFile } from 'node:fs/promises';
|
|
14
14
|
import { buildArtifactStudioProject } from './build.js';
|
|
15
15
|
async function main() {
|
|
16
|
-
const [dir, outFile] = process.argv.slice(2);
|
|
16
|
+
const [dir, outFile, candidateFlag] = process.argv.slice(2);
|
|
17
17
|
if (!dir || !outFile) {
|
|
18
18
|
process.stderr.write('usage: build-worker <projectDir> <outFile>\n');
|
|
19
19
|
process.exitCode = 2;
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
const result = await buildArtifactStudioProject(dir
|
|
22
|
+
const result = await buildArtifactStudioProject(dir, {
|
|
23
|
+
candidateTokenCompatibility: candidateFlag === '--candidate-token-compatibility',
|
|
24
|
+
});
|
|
23
25
|
await writeFile(outFile, JSON.stringify(result), 'utf8');
|
|
24
26
|
}
|
|
25
27
|
main().catch((error) => {
|
package/dist/build.d.ts
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
|
+
import { type ObservedBuildEvidence } from './build-provenance.js';
|
|
1
2
|
import { type ArtifactStudioSourceFile } from './project.js';
|
|
2
3
|
import type { ArtifactStudioManifest } from './manifest.js';
|
|
3
4
|
import { type ArtifactStudioPeerVersions } from './peer-versions.js';
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* react-query QueryClient context — so a duplicate copy can't silently break
|
|
10
|
-
* hooks or the provider/consumer link. Exported so it can be regression-tested
|
|
11
|
-
* (a missing/unresolvable peer must fail loudly).
|
|
12
|
-
*
|
|
13
|
-
* **CLI atlas-ui stamp (DES-254):** the atlas-ui a bundle gets is whatever
|
|
14
|
-
* *this* CLI package resolves — not a semver the artifact declares.
|
|
15
|
-
* See `VERSION-PIN.md`.
|
|
6
|
+
* Released Atlas UI 2.0.1 exports its canonical sheet from dist, while the
|
|
7
|
+
* candidate layout puts the generated entry at package root so it can be read
|
|
8
|
+
* before a workspace build. Accept both: ordinary CLI builds retain their
|
|
9
|
+
* released pin, and explicit linked previews prefer the synced root entry.
|
|
16
10
|
*/
|
|
17
|
-
|
|
11
|
+
export declare function resolveAtlasUiTokensPath(packageDir: string): string;
|
|
18
12
|
export declare const PEER_RESOLVE_ALIAS: Record<string, string>;
|
|
19
13
|
export interface ArtifactStudioRuntimeChunk {
|
|
20
14
|
/** Vite/Rollup output path, always under `chunks/`. */
|
|
@@ -39,8 +33,16 @@ export interface ArtifactStudioBuildResult {
|
|
|
39
33
|
bundleHash: string;
|
|
40
34
|
/** CLI + force-aliased atlas-ui versions that built this bundle (DES-254). */
|
|
41
35
|
peerVersions: ArtifactStudioPeerVersions;
|
|
36
|
+
/** Exact CLI-resolved UI/token evidence; deployment adds source revision + channel. */
|
|
37
|
+
observedBuildEvidence: Omit<ObservedBuildEvidence, 'channel' | 'sourceRevision'>;
|
|
38
|
+
/** Source diagnostics that do not prevent a build. */
|
|
39
|
+
diagnostics?: string[];
|
|
42
40
|
}
|
|
43
|
-
export declare function buildArtifactStudioProject(rootDir: string
|
|
41
|
+
export declare function buildArtifactStudioProject(rootDir: string, { candidateTokenCompatibility, atlasUiPackageDir, }?: {
|
|
42
|
+
candidateTokenCompatibility?: boolean;
|
|
43
|
+
/** Explicit linked Atlas UI package, used by candidate toolchain hosts. */
|
|
44
|
+
atlasUiPackageDir?: string;
|
|
45
|
+
}): Promise<ArtifactStudioBuildResult>;
|
|
44
46
|
export declare function writeBuildArtifact(result: ArtifactStudioBuildResult, outPath: string): Promise<void>;
|
|
45
47
|
export declare function hashBundleAndChunks({ bundle, chunks, }: {
|
|
46
48
|
bundle: string;
|
package/dist/build.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
1
2
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
2
3
|
import { createRequire } from 'node:module';
|
|
3
4
|
import { dirname, relative, resolve } from 'node:path';
|
|
4
5
|
import { build as viteBuild } from 'vite';
|
|
5
|
-
import { atlasUiCssResolverPlugin } from './atlas-ui-css-plugin.js';
|
|
6
|
+
import { atlasUiCssResolverPlugin, collectAtlasUiResolvedTokenSelections } from './atlas-ui-css-plugin.js';
|
|
7
|
+
import { createObservedBuildEvidence } from './build-provenance.js';
|
|
6
8
|
import { sha256Hex } from './hash.js';
|
|
7
9
|
import { isArtifactIgnoredPath } from './artifact-ignore.js';
|
|
8
10
|
import { readArtifactStudioSource } from './project.js';
|
|
9
11
|
import { normalizeArtifactPath } from './paths.js';
|
|
10
12
|
import { resolveArtifactStudioPeerVersions, } from './peer-versions.js';
|
|
11
13
|
import { ARTIFACT_RUNTIME_CHUNK_DIR, assertSafeRuntimeChunkFileName } from './runtime-chunks.js';
|
|
14
|
+
import { rejectOutsideSourcePlugin } from './source-boundary.js';
|
|
15
|
+
import { collectCandidateTokenCompatibilityDiagnostics, collectDeprecatedTokenImportDiagnostics, } from './token-contract.js';
|
|
12
16
|
/** Keep in sync with `atlas/src/server/services/artifact-studio/limits.ts`. */
|
|
13
17
|
const MAX_DEPLOY_SOURCEMAP_BYTES = 10 * 1024 * 1024;
|
|
14
18
|
const cliRequire = createRequire(import.meta.url);
|
|
@@ -25,6 +29,17 @@ const reactRouterDomDir = dirname(cliRequire.resolve('react-router-dom/package.j
|
|
|
25
29
|
const sonnerDir = dirname(cliRequire.resolve('sonner'));
|
|
26
30
|
const atlasUiDir = dirname(cliRequire.resolve('@sequenceholdings/atlas-ui/package.json'));
|
|
27
31
|
const atlasUiDist = resolve(atlasUiDir, 'dist');
|
|
32
|
+
/**
|
|
33
|
+
* Released Atlas UI 2.0.1 exports its canonical sheet from dist, while the
|
|
34
|
+
* candidate layout puts the generated entry at package root so it can be read
|
|
35
|
+
* before a workspace build. Accept both: ordinary CLI builds retain their
|
|
36
|
+
* released pin, and explicit linked previews prefer the synced root entry.
|
|
37
|
+
*/
|
|
38
|
+
export function resolveAtlasUiTokensPath(packageDir) {
|
|
39
|
+
const rootEntry = resolve(packageDir, 'tokens.css');
|
|
40
|
+
return existsSync(rootEntry) ? rootEntry : resolve(packageDir, 'dist', 'tokens.css');
|
|
41
|
+
}
|
|
42
|
+
const atlasUiTokens = resolveAtlasUiTokensPath(atlasUiDir);
|
|
28
43
|
// The shared Lattice form renderer is aliased the same way as atlas-ui (it is
|
|
29
44
|
// an atlas-ui-peer library, prebuilt to dist). Resolved via ./package.json —
|
|
30
45
|
// the package's exports map exposes it precisely so this resolve (and
|
|
@@ -45,53 +60,74 @@ const formRendererDist = resolve(formRendererDir, 'dist');
|
|
|
45
60
|
* See `VERSION-PIN.md`.
|
|
46
61
|
*/
|
|
47
62
|
/** Platform + common artifact runtime peers — always aliased over node_modules. */
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
63
|
+
function createPeerResolveAlias({ atlasUiPackageDir = atlasUiDir } = {}) {
|
|
64
|
+
const selectedAtlasUiDist = resolve(atlasUiPackageDir, 'dist');
|
|
65
|
+
const selectedAtlasUiTokens = resolveAtlasUiTokensPath(atlasUiPackageDir);
|
|
66
|
+
return {
|
|
67
|
+
'react/jsx-runtime': resolve(reactDir, 'jsx-runtime.js'),
|
|
68
|
+
'react/jsx-dev-runtime': resolve(reactDir, 'jsx-dev-runtime.js'),
|
|
69
|
+
react: reactDir,
|
|
70
|
+
'react-dom/client': resolve(reactDomDir, 'client'),
|
|
71
|
+
'react-dom': reactDomDir,
|
|
72
|
+
'@tanstack/react-query': reactQueryDir,
|
|
73
|
+
'react-router-dom': reactRouterDomDir,
|
|
74
|
+
sonner: sonnerDir,
|
|
75
|
+
// Candidate Atlas UI exposes canonical tokens at the package root; released
|
|
76
|
+
// 2.0.1 keeps them in dist. Preserve explicit alias resolution so legacy
|
|
77
|
+
// source reaches the normalizer instead of falling through to the bare alias.
|
|
78
|
+
'@sequenceholdings/atlas-ui/tokens.v2.css': resolve(atlasUiPackageDir, 'tokens.v2.css'),
|
|
79
|
+
'@sequenceholdings/atlas-ui/theme.v2.css': resolve(atlasUiPackageDir, 'theme.v2.css'),
|
|
80
|
+
'@sequenceholdings/atlas-ui/tokens.css': selectedAtlasUiTokens,
|
|
81
|
+
'@sequenceholdings/atlas-ui/charts': resolve(selectedAtlasUiDist, 'charts.mjs'),
|
|
82
|
+
'@sequenceholdings/atlas-ui': resolve(selectedAtlasUiDist, 'index.mjs'),
|
|
83
|
+
// Alias keys prefix-match, so subpath entries MUST precede their bare key
|
|
84
|
+
// (same ordering as tokens.css/charts above the bare atlas-ui key).
|
|
85
|
+
'@sequenceholdings/lattice-form-renderer/golden': resolve(formRendererDist, 'golden.mjs'),
|
|
86
|
+
'@sequenceholdings/lattice-form-renderer': resolve(formRendererDist, 'index.mjs'),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
export const PEER_RESOLVE_ALIAS = createPeerResolveAlias();
|
|
72
90
|
function isAssetOutput(value) {
|
|
73
91
|
return value.type === 'asset';
|
|
74
92
|
}
|
|
75
|
-
export async function buildArtifactStudioProject(rootDir) {
|
|
93
|
+
export async function buildArtifactStudioProject(rootDir, { candidateTokenCompatibility = false, atlasUiPackageDir, } = {}) {
|
|
94
|
+
const peerResolveAlias = atlasUiPackageDir
|
|
95
|
+
? createPeerResolveAlias({ atlasUiPackageDir })
|
|
96
|
+
: PEER_RESOLVE_ALIAS;
|
|
97
|
+
const selectedAtlasUiDir = atlasUiPackageDir ?? atlasUiDir;
|
|
98
|
+
const selectedAtlasUiDist = resolve(atlasUiPackageDir ?? atlasUiDir, 'dist');
|
|
99
|
+
const selectedAtlasUiTokens = resolveAtlasUiTokensPath(selectedAtlasUiDir);
|
|
76
100
|
const source = await readArtifactStudioSource(rootDir);
|
|
101
|
+
const diagnostics = [
|
|
102
|
+
...collectDeprecatedTokenImportDiagnostics({ files: source.files }),
|
|
103
|
+
...(candidateTokenCompatibility
|
|
104
|
+
? collectCandidateTokenCompatibilityDiagnostics({ files: source.files })
|
|
105
|
+
: []),
|
|
106
|
+
];
|
|
77
107
|
const root = source.root;
|
|
108
|
+
const transformedCssModules = new Map();
|
|
78
109
|
const result = await viteBuild({
|
|
79
110
|
root,
|
|
80
111
|
configFile: false,
|
|
81
112
|
logLevel: 'silent',
|
|
82
113
|
plugins: [
|
|
114
|
+
rejectOutsideSourcePlugin({
|
|
115
|
+
root,
|
|
116
|
+
trustedExternalRoots: Object.values(peerResolveAlias).map((target) => dirname(target)),
|
|
117
|
+
}),
|
|
83
118
|
rejectIgnoredSourcePlugin(root, source.ignorePatterns),
|
|
84
119
|
artifactSdkVirtualModule(),
|
|
85
120
|
atlasUiCssResolverPlugin({
|
|
86
|
-
atlasUiDir,
|
|
87
|
-
atlasUiDist,
|
|
121
|
+
atlasUiDir: selectedAtlasUiDir,
|
|
122
|
+
atlasUiDist: selectedAtlasUiDist,
|
|
88
123
|
formRendererDist,
|
|
124
|
+
onTransform: ({ id, code }) => transformedCssModules.set(id, code),
|
|
89
125
|
}),
|
|
90
126
|
nextStubPlugin(),
|
|
91
127
|
(await import('@tailwindcss/vite')).default(),
|
|
92
128
|
],
|
|
93
129
|
resolve: {
|
|
94
|
-
alias:
|
|
130
|
+
alias: peerResolveAlias,
|
|
95
131
|
},
|
|
96
132
|
build: {
|
|
97
133
|
write: false,
|
|
@@ -119,6 +155,22 @@ export async function buildArtifactStudioProject(rootDir) {
|
|
|
119
155
|
const bundle = css ? injectCssRuntime(css) + '\n' + entry.code : entry.code;
|
|
120
156
|
const chunks = collectRuntimeChunks(output);
|
|
121
157
|
const sourceMap = fitSourceMapForDeploy(collectSourceMap(output));
|
|
158
|
+
const peerVersions = resolveArtifactStudioPeerVersions();
|
|
159
|
+
const observedBuildEvidence = createObservedBuildEvidence({
|
|
160
|
+
atlasUiDir: selectedAtlasUiDir,
|
|
161
|
+
atlasUiDist: selectedAtlasUiDist,
|
|
162
|
+
atlasUiTokens: selectedAtlasUiTokens,
|
|
163
|
+
channel: 'active',
|
|
164
|
+
linked: atlasUiPackageDir !== undefined,
|
|
165
|
+
peerVersions,
|
|
166
|
+
sourceRevision: { commit: null, branch: null, dirty: null },
|
|
167
|
+
tokenSelections: collectAtlasUiResolvedTokenSelections({
|
|
168
|
+
cssModules: [...transformedCssModules].map(([id, code]) => ({ id, code })),
|
|
169
|
+
atlasUiDir: selectedAtlasUiDir,
|
|
170
|
+
atlasUiDist: selectedAtlasUiDist,
|
|
171
|
+
formRendererDist,
|
|
172
|
+
}),
|
|
173
|
+
});
|
|
122
174
|
return {
|
|
123
175
|
manifest: source.manifest,
|
|
124
176
|
files: source.files,
|
|
@@ -128,7 +180,14 @@ export async function buildArtifactStudioProject(rootDir) {
|
|
|
128
180
|
bundleFormat: 'esm',
|
|
129
181
|
sourceMap,
|
|
130
182
|
bundleHash: hashBundleAndChunks({ bundle, chunks }),
|
|
131
|
-
peerVersions
|
|
183
|
+
peerVersions,
|
|
184
|
+
diagnostics,
|
|
185
|
+
observedBuildEvidence: {
|
|
186
|
+
schemaVersion: observedBuildEvidence.schemaVersion,
|
|
187
|
+
toolchain: observedBuildEvidence.toolchain,
|
|
188
|
+
atlasUi: observedBuildEvidence.atlasUi,
|
|
189
|
+
tokenResolution: observedBuildEvidence.tokenResolution,
|
|
190
|
+
},
|
|
132
191
|
};
|
|
133
192
|
}
|
|
134
193
|
/**
|