@sequenceholdings/artifact-studio 0.2.3 → 0.2.5

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 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.5 (Studio CLI 0.1.31) pins Atlas UI 3.1.2 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
@@ -51,7 +57,8 @@ temporary forwarding aliases; the compiler normalizes their source imports to on
51
57
  canonical layer and reports a migration warning. An explicit
52
58
  `seq-studio artifact dev --link <atlas-ui-package-dir>` preview additionally
53
59
  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`.
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`.
56
63
  Import `tokens.css` for standalone/`vite dev` compatibility. **Do not** import
57
64
  `tokens.v2.tenants.css`.
@@ -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
+ }
@@ -1,3 +1,4 @@
1
+ import type { ResolvedStylesheet } from './resolved-stylesheets.js';
1
2
  import type { ArtifactStudioPeerVersions } from './peer-versions.js';
2
3
  import type { AtlasUiResolvedTokenSelection } from './atlas-ui-css-plugin.js';
3
4
  export declare const ARTIFACT_BUILD_PROVENANCE_VERSION = 1;
@@ -17,6 +18,7 @@ export interface ArtifactTokenResolution {
17
18
  export interface ObservedBuildEvidence {
18
19
  schemaVersion: typeof ARTIFACT_BUILD_PROVENANCE_VERSION;
19
20
  channel: 'active' | 'preview';
21
+ /** `cliVersion` is retained for historical records and identifies Artifact Studio. */
20
22
  toolchain: {
21
23
  cliVersion: string;
22
24
  selection: 'published' | 'linked';
@@ -29,6 +31,8 @@ export interface ObservedBuildEvidence {
29
31
  modelVersion: 1;
30
32
  entries: ArtifactTokenResolution[];
31
33
  };
34
+ /** CSS modules that actually contribute to this artifact's visible cascade. */
35
+ stylesheets: ResolvedStylesheet[];
32
36
  sourceRevision: ArtifactSourceRevision;
33
37
  }
34
38
  /**
@@ -37,7 +41,7 @@ export interface ObservedBuildEvidence {
37
41
  * migration generation; future canonical token entry points need no schema
38
42
  * change.
39
43
  */
40
- export declare function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTokens, channel, linked, peerVersions, sourceRevision, tokenSelections }: {
44
+ export declare function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTokens, channel, linked, peerVersions, sourceRevision, stylesheets, tokenSelections }: {
41
45
  atlasUiDir: string;
42
46
  atlasUiDist: string;
43
47
  /** Exact tokens.css alias target; releases and linked candidates use different layouts. */
@@ -46,5 +50,6 @@ export declare function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, a
46
50
  linked: boolean;
47
51
  peerVersions: ArtifactStudioPeerVersions;
48
52
  sourceRevision: ArtifactSourceRevision;
53
+ stylesheets?: ResolvedStylesheet[];
49
54
  tokenSelections: readonly AtlasUiResolvedTokenSelection[];
50
55
  }): ObservedBuildEvidence;
@@ -14,7 +14,7 @@ function contentIdentity(entries) {
14
14
  * migration generation; future canonical token entry points need no schema
15
15
  * change.
16
16
  */
17
- export function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTokens, channel, linked, peerVersions, sourceRevision, tokenSelections }) {
17
+ export function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTokens, channel, linked, peerVersions, sourceRevision, stylesheets = [], tokenSelections }) {
18
18
  const knownTargets = new Map([
19
19
  ['@sequenceholdings/atlas-ui/tokens.css', { filePath: atlasUiTokens }],
20
20
  ['@sequenceholdings/atlas-ui/tokens.v2.css', { filePath: `${atlasUiDir}/tokens.v2.css` }],
@@ -39,6 +39,7 @@ export function createObservedBuildEvidence({ atlasUiDir, atlasUiDist, atlasUiTo
39
39
  toolchain: { cliVersion: peerVersions.cliVersion, selection: linked ? 'linked' : 'published' },
40
40
  atlasUi: { version: peerVersions.atlasUiVersion, contentHash: contentIdentity(identityEntries) },
41
41
  tokenResolution: { modelVersion: 1, entries },
42
+ stylesheets,
42
43
  sourceRevision,
43
44
  };
44
45
  }
package/dist/build.js CHANGED
@@ -4,7 +4,9 @@ import { createRequire } from 'node:module';
4
4
  import { dirname, relative, resolve } from 'node:path';
5
5
  import { build as viteBuild } from 'vite';
6
6
  import { atlasUiCssResolverPlugin, collectAtlasUiResolvedTokenSelections } from './atlas-ui-css-plugin.js';
7
+ import { validateAtlasUiTokenStylesheetContract } from './atlas-ui-package-contract.js';
7
8
  import { createObservedBuildEvidence } from './build-provenance.js';
9
+ import { collectResolvedStylesheets } from './resolved-stylesheets.js';
8
10
  import { sha256Hex } from './hash.js';
9
11
  import { isArtifactIgnoredPath } from './artifact-ignore.js';
10
12
  import { readArtifactStudioSource } from './project.js';
@@ -37,7 +39,8 @@ const atlasUiDist = resolve(atlasUiDir, 'dist');
37
39
  */
38
40
  export function resolveAtlasUiTokensPath(packageDir) {
39
41
  const rootEntry = resolve(packageDir, 'tokens.css');
40
- return existsSync(rootEntry) ? rootEntry : resolve(packageDir, 'dist', 'tokens.css');
42
+ const resolvedTokensCssPath = existsSync(rootEntry) ? rootEntry : resolve(packageDir, 'dist', 'tokens.css');
43
+ return validateAtlasUiTokenStylesheetContract({ packageDir, resolvedTokensCssPath });
41
44
  }
42
45
  const atlasUiTokens = resolveAtlasUiTokensPath(atlasUiDir);
43
46
  // The shared Lattice form renderer is aliased the same way as atlas-ui (it is
@@ -156,20 +159,28 @@ export async function buildArtifactStudioProject(rootDir, { candidateTokenCompat
156
159
  const chunks = collectRuntimeChunks(output);
157
160
  const sourceMap = fitSourceMapForDeploy(collectSourceMap(output));
158
161
  const peerVersions = resolveArtifactStudioPeerVersions();
162
+ const cssModules = [...transformedCssModules].map(([id, code]) => ({ id, code }));
163
+ const tokenSelections = collectAtlasUiResolvedTokenSelections({
164
+ cssModules,
165
+ atlasUiDir: selectedAtlasUiDir,
166
+ atlasUiDist: selectedAtlasUiDist,
167
+ formRendererDist,
168
+ });
159
169
  const observedBuildEvidence = createObservedBuildEvidence({
160
170
  atlasUiDir: selectedAtlasUiDir,
161
171
  atlasUiDist: selectedAtlasUiDist,
162
172
  atlasUiTokens: selectedAtlasUiTokens,
163
173
  channel: 'active',
174
+ stylesheets: collectResolvedStylesheets({
175
+ root,
176
+ atlasUiTokens: selectedAtlasUiTokens,
177
+ cssModules,
178
+ tokenSelections,
179
+ }),
164
180
  linked: atlasUiPackageDir !== undefined,
165
181
  peerVersions,
166
182
  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
- }),
183
+ tokenSelections,
173
184
  });
174
185
  return {
175
186
  manifest: source.manifest,
@@ -187,6 +198,7 @@ export async function buildArtifactStudioProject(rootDir, { candidateTokenCompat
187
198
  toolchain: observedBuildEvidence.toolchain,
188
199
  atlasUi: observedBuildEvidence.atlasUi,
189
200
  tokenResolution: observedBuildEvidence.tokenResolution,
201
+ stylesheets: observedBuildEvidence.stylesheets,
190
202
  },
191
203
  };
192
204
  }
@@ -0,0 +1,21 @@
1
+ import type { AtlasUiResolvedTokenSelection } from './atlas-ui-css-plugin.js';
2
+ export interface ResolvedStylesheet {
3
+ /** Stable display identifier, never an absolute local path. */
4
+ identifier: string;
5
+ contentHash: string;
6
+ }
7
+ /**
8
+ * Records CSS modules Vite actually transformed for the artifact, plus the
9
+ * canonical Atlas UI sheet selected by the resolver. It deliberately excludes
10
+ * JavaScript/TypeScript and `?raw` imports: neither participates in CSS
11
+ * cascade order.
12
+ */
13
+ export declare function collectResolvedStylesheets({ root, cssModules, tokenSelections, atlasUiTokens, }: {
14
+ root: string;
15
+ cssModules: readonly {
16
+ id: string;
17
+ code: string;
18
+ }[];
19
+ tokenSelections: readonly AtlasUiResolvedTokenSelection[];
20
+ atlasUiTokens: string;
21
+ }): ResolvedStylesheet[];
@@ -0,0 +1,33 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { relative } from 'node:path';
3
+ import { sha256Hex } from './hash.js';
4
+ const MAX_RECORDED_STYLESHEETS = 100;
5
+ /**
6
+ * Records CSS modules Vite actually transformed for the artifact, plus the
7
+ * canonical Atlas UI sheet selected by the resolver. It deliberately excludes
8
+ * JavaScript/TypeScript and `?raw` imports: neither participates in CSS
9
+ * cascade order.
10
+ */
11
+ export function collectResolvedStylesheets({ root, cssModules, tokenSelections, atlasUiTokens, }) {
12
+ const stylesheets = new Map();
13
+ if (tokenSelections.some((selection) => selection.specifier === '@sequenceholdings/atlas-ui/tokens.css')) {
14
+ stylesheets.set('@sequenceholdings/atlas-ui/tokens.css', {
15
+ identifier: '@sequenceholdings/atlas-ui/tokens.css',
16
+ contentHash: sha256Hex(readFileSync(atlasUiTokens, 'utf8')),
17
+ });
18
+ }
19
+ for (const { id, code } of cssModules) {
20
+ if (id.includes('?'))
21
+ continue;
22
+ const identifier = relative(root, id).replaceAll('\\', '/');
23
+ if (!identifier.endsWith('.css') || identifier === '' || identifier.startsWith('../'))
24
+ continue;
25
+ stylesheets.set(identifier, { identifier, contentHash: sha256Hex(code) });
26
+ }
27
+ // The deployment API accepts at most 100 evidence records. Evidence is
28
+ // observational, so it must never turn an otherwise valid build into a
29
+ // failed deployment merely because Vite transformed many stylesheets.
30
+ return [...stylesheets.values()]
31
+ .sort((left, right) => left.identifier.localeCompare(right.identifier))
32
+ .slice(0, MAX_RECORDED_STYLESHEETS);
33
+ }
@@ -9,8 +9,8 @@
9
9
  },
10
10
  "dependencies": {},
11
11
  "devDependencies": {
12
- "@sequenceholdings/artifact-studio": "^0.2.1",
13
- "@sequenceholdings/atlas-ui": "^2.0.1",
12
+ "@sequenceholdings/artifact-studio": "^0.2.4",
13
+ "@sequenceholdings/atlas-ui": "^3.1.1",
14
14
  "@tailwindcss/postcss": "^4.2.2",
15
15
  "@tailwindcss/vite": "^4.2.2",
16
16
  "@tanstack/react-query": "^4.36.1",
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "version": "0.2.3",
12
+ "version": "0.2.5",
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": {
@@ -72,7 +72,7 @@
72
72
  "VERSION-PIN.md"
73
73
  ],
74
74
  "dependencies": {
75
- "@sequenceholdings/atlas-ui": "2.0.1",
75
+ "@sequenceholdings/atlas-ui": "3.1.2",
76
76
  "@tailwindcss/postcss": "^4.1.8",
77
77
  "@tailwindcss/vite": "^4.1.8",
78
78
  "@tanstack/react-query": "^4.36.1",
@@ -85,7 +85,7 @@
85
85
  "tw-animate-css": "^1.4.0",
86
86
  "vite": "8.0.16",
87
87
  "zod": "^4.1.13",
88
- "@sequenceholdings/lattice-form-renderer": "0.1.2"
88
+ "@sequenceholdings/lattice-form-renderer": "0.1.3"
89
89
  },
90
90
  "devDependencies": {
91
91
  "@types/js-yaml": "^4.0.9",
@@ -9,8 +9,8 @@
9
9
  },
10
10
  "dependencies": {},
11
11
  "devDependencies": {
12
- "@sequenceholdings/artifact-studio": "^0.2.1",
13
- "@sequenceholdings/atlas-ui": "^2.0.1",
12
+ "@sequenceholdings/artifact-studio": "^0.2.4",
13
+ "@sequenceholdings/atlas-ui": "^3.1.1",
14
14
  "@tailwindcss/postcss": "^4.2.2",
15
15
  "@tailwindcss/vite": "^4.2.2",
16
16
  "@tanstack/react-query": "^4.36.1",