@sequenceholdings/artifact-studio 0.2.2 → 0.2.4
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 +18 -8
- 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/atlas-ui-package-contract.d.ts +7 -0
- package/dist/atlas-ui-package-contract.js +60 -0
- package/dist/build-provenance.d.ts +55 -0
- package/dist/build-provenance.js +45 -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 +101 -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/resolved-stylesheets.d.ts +21 -0
- package/dist/resolved-stylesheets.js +33 -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
|
@@ -4,6 +4,12 @@ A (re)built Artifact Studio app gets whatever `@sequenceholdings/atlas-ui` the
|
|
|
4
4
|
**building CLI** carries — not a semver the artifact's own `package.json`
|
|
5
5
|
declares.
|
|
6
6
|
|
|
7
|
+
Artifact Studio 0.2.4 (Studio CLI 0.1.30) pins Atlas UI 3.1.1 exactly.
|
|
8
|
+
Ordinary builds now use its canonical, mode-aware `tokens.css`. Rebuilding an
|
|
9
|
+
existing artifact adopts this toolchain; previously uploaded immutable bundles
|
|
10
|
+
keep their original CSS. Migrate retired numbered black/white utilities to
|
|
11
|
+
semantic roles and verify a preview before activating a rebuilt artifact.
|
|
12
|
+
|
|
7
13
|
## What this means
|
|
8
14
|
|
|
9
15
|
`shared/services/artifact-studio/src/build.ts` force-aliases platform peers via
|
|
@@ -43,12 +49,16 @@ source (and older rows with null peer fields are rebuilt once to populate them).
|
|
|
43
49
|
`seq-studio artifact plan` reports the same decision, so its `action:` line
|
|
44
50
|
never claims `no-op` for a deploy that would actually rebuild.
|
|
45
51
|
|
|
46
|
-
##
|
|
47
|
-
|
|
48
|
-
`seq-studio artifact build` idempotently injects `tokens.v2.css` + `theme.v2.css`
|
|
49
|
-
when the artifact CSS entry lacks them (iframes do not inherit Atlas CSSOM; the
|
|
50
|
-
host only stamps `data-tokens` / brand vars). Keep listing `tokens.css` for the
|
|
51
|
-
ungated standalone/`vite dev` fallback. **Do not** import `tokens.v2.tenants.css`.
|
|
52
|
+
## CSS entry points (sandbox previews)
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
54
|
+
`tokens.css` is the canonical stylesheet and includes semantic roles plus the
|
|
55
|
+
Tailwind utility bridge. The legacy `tokens.v2.css` and `theme.v2.css` paths are
|
|
56
|
+
temporary forwarding aliases; the compiler normalizes their source imports to one
|
|
57
|
+
canonical layer and reports a migration warning. An explicit
|
|
58
|
+
`seq-studio artifact dev --link <atlas-ui-package-dir>` preview additionally
|
|
59
|
+
reports retired numbered black/white opacity utilities and uncertain dynamic
|
|
60
|
+
class construction. Legacy stylesheet aliases normalize to canonical tokens;
|
|
61
|
+
they do not restore retired utility scales. The runtime forwards brand variables
|
|
62
|
+
independently of `data-tokens`.
|
|
63
|
+
Import `tokens.css` for standalone/`vite dev` compatibility. **Do not** import
|
|
64
|
+
`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,7 @@
|
|
|
1
|
+
export declare class AtlasUiPackageContractError extends Error {
|
|
2
|
+
constructor(message: string);
|
|
3
|
+
}
|
|
4
|
+
export declare function validateAtlasUiTokenStylesheetContract({ packageDir, resolvedTokensCssPath, }: {
|
|
5
|
+
packageDir: string;
|
|
6
|
+
resolvedTokensCssPath: string;
|
|
7
|
+
}): string;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
const ATLAS_UI_PACKAGE = '@sequenceholdings/atlas-ui';
|
|
4
|
+
const TOKENS_CSS_EXPORT = './tokens.css';
|
|
5
|
+
export class AtlasUiPackageContractError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(`Atlas UI package contract error: ${message}`);
|
|
8
|
+
this.name = 'AtlasUiPackageContractError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function readExportTarget(value) {
|
|
15
|
+
if (typeof value === 'string')
|
|
16
|
+
return value;
|
|
17
|
+
if (isRecord(value)) {
|
|
18
|
+
for (const key of ['style', 'default', 'import']) {
|
|
19
|
+
const target = value[key];
|
|
20
|
+
if (typeof target === 'string')
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
export function validateAtlasUiTokenStylesheetContract({ packageDir, resolvedTokensCssPath, }) {
|
|
27
|
+
const packageJsonPath = resolve(packageDir, 'package.json');
|
|
28
|
+
let parsed;
|
|
29
|
+
try {
|
|
30
|
+
parsed = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
throw new AtlasUiPackageContractError(`unable to read ${packageJsonPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
34
|
+
}
|
|
35
|
+
if (!isRecord(parsed)) {
|
|
36
|
+
throw new AtlasUiPackageContractError(`${packageJsonPath} is not a JSON object`);
|
|
37
|
+
}
|
|
38
|
+
if (parsed.name !== ATLAS_UI_PACKAGE) {
|
|
39
|
+
throw new AtlasUiPackageContractError(`${packageJsonPath} must be ${ATLAS_UI_PACKAGE}, got ${String(parsed.name)}`);
|
|
40
|
+
}
|
|
41
|
+
const exportsMap = parsed.exports;
|
|
42
|
+
if (!isRecord(exportsMap)) {
|
|
43
|
+
throw new AtlasUiPackageContractError(`${ATLAS_UI_PACKAGE} package.json must declare an exports map`);
|
|
44
|
+
}
|
|
45
|
+
const exportTarget = readExportTarget(exportsMap[TOKENS_CSS_EXPORT]);
|
|
46
|
+
if (!exportTarget) {
|
|
47
|
+
throw new AtlasUiPackageContractError(`${ATLAS_UI_PACKAGE} must export ${TOKENS_CSS_EXPORT} to a CSS file included in the package`);
|
|
48
|
+
}
|
|
49
|
+
const declaredPath = resolve(packageDir, exportTarget);
|
|
50
|
+
if (!existsSync(declaredPath)) {
|
|
51
|
+
throw new AtlasUiPackageContractError(`${ATLAS_UI_PACKAGE}${TOKENS_CSS_EXPORT} points to ${exportTarget}, but ${declaredPath} does not exist`);
|
|
52
|
+
}
|
|
53
|
+
if (!existsSync(resolvedTokensCssPath)) {
|
|
54
|
+
throw new AtlasUiPackageContractError(`${ATLAS_UI_PACKAGE}${TOKENS_CSS_EXPORT} resolved to ${resolvedTokensCssPath}, but that file does not exist`);
|
|
55
|
+
}
|
|
56
|
+
if (resolve(resolvedTokensCssPath) !== declaredPath) {
|
|
57
|
+
throw new AtlasUiPackageContractError(`${ATLAS_UI_PACKAGE}${TOKENS_CSS_EXPORT} export target (${declaredPath}) and build resolver (${resolvedTokensCssPath}) disagree`);
|
|
58
|
+
}
|
|
59
|
+
return declaredPath;
|
|
60
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import type { ResolvedStylesheet } from './resolved-stylesheets.js';
|
|
2
|
+
import type { ArtifactStudioPeerVersions } from './peer-versions.js';
|
|
3
|
+
import type { AtlasUiResolvedTokenSelection } from './atlas-ui-css-plugin.js';
|
|
4
|
+
export declare const ARTIFACT_BUILD_PROVENANCE_VERSION = 1;
|
|
5
|
+
export interface ArtifactSourceRevision {
|
|
6
|
+
commit: string | null;
|
|
7
|
+
branch: string | null;
|
|
8
|
+
dirty: boolean | null;
|
|
9
|
+
}
|
|
10
|
+
export interface ArtifactTokenResolution {
|
|
11
|
+
specifier: string;
|
|
12
|
+
resolvedPath: string;
|
|
13
|
+
contentHash: string;
|
|
14
|
+
cssLayers: string[];
|
|
15
|
+
injectedBy: string | null;
|
|
16
|
+
}
|
|
17
|
+
/** Observed local build evidence; values are not independently verified by Atlas. */
|
|
18
|
+
export interface ObservedBuildEvidence {
|
|
19
|
+
schemaVersion: typeof ARTIFACT_BUILD_PROVENANCE_VERSION;
|
|
20
|
+
channel: 'active' | 'preview';
|
|
21
|
+
/** `cliVersion` is retained for historical records and identifies Artifact Studio. */
|
|
22
|
+
toolchain: {
|
|
23
|
+
cliVersion: string;
|
|
24
|
+
selection: 'published' | 'linked';
|
|
25
|
+
};
|
|
26
|
+
atlasUi: {
|
|
27
|
+
version: string;
|
|
28
|
+
contentHash: string;
|
|
29
|
+
};
|
|
30
|
+
tokenResolution: {
|
|
31
|
+
modelVersion: 1;
|
|
32
|
+
entries: ArtifactTokenResolution[];
|
|
33
|
+
};
|
|
34
|
+
/** CSS modules that actually contribute to this artifact's visible cascade. */
|
|
35
|
+
stylesheets: ResolvedStylesheet[];
|
|
36
|
+
sourceRevision: ArtifactSourceRevision;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Evidence from the exact Atlas UI alias targets selected by this build. The
|
|
40
|
+
* format deliberately records paths and resolved layers, rather than naming a
|
|
41
|
+
* migration generation; future canonical token entry points need no schema
|
|
42
|
+
* change.
|
|
43
|
+
*/
|
|
44
|
+
export declare function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTokens, channel, linked, peerVersions, sourceRevision, stylesheets, tokenSelections }: {
|
|
45
|
+
atlasUiDir: string;
|
|
46
|
+
atlasUiDist: string;
|
|
47
|
+
/** Exact tokens.css alias target; releases and linked candidates use different layouts. */
|
|
48
|
+
atlasUiTokens: string;
|
|
49
|
+
channel: ObservedBuildEvidence['channel'];
|
|
50
|
+
linked: boolean;
|
|
51
|
+
peerVersions: ArtifactStudioPeerVersions;
|
|
52
|
+
sourceRevision: ArtifactSourceRevision;
|
|
53
|
+
stylesheets?: ResolvedStylesheet[];
|
|
54
|
+
tokenSelections: readonly AtlasUiResolvedTokenSelection[];
|
|
55
|
+
}): ObservedBuildEvidence;
|
|
@@ -0,0 +1,45 @@
|
|
|
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, stylesheets = [], 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
|
+
stylesheets,
|
|
43
|
+
sourceRevision,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
@@ -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;
|