@invarn/cibuild 2.0.2 → 2.0.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.
@@ -1 +1 @@
1
- {"version":3,"file":"ui-fidelity-render.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-render.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"ui-fidelity-render.d.ts","sourceRoot":"","sources":["../../../../src/yaml/steps/ui-fidelity-render.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiDG;AAEH,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,QAAQ,CAAC;IACnC;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACzB;AAED;;;;;GAKG;AACH,eAAO,MAAM,mBAAmB,YAAY,CAAC;AAE7C,mFAAmF;AACnF,eAAO,MAAM,aAAa,IAAI,CAAC;AAE/B,iDAAiD;AACjD,eAAO,MAAM,eAAe,6BAA8B,CAAC;AAC3D,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,eAAe,CAAC,CAAC,MAAM,CAAC,CAAC;AAE7D,sEAAsE;AACtE,eAAO,MAAM,sBAAsB,EAAE,aAAsB,CAAC;AAE5D,+EAA+E;AAC/E,eAAO,MAAM,wBAAwB,mBAAmB,CAAC;AAEzD;;;;;;;GAOG;AACH,eAAO,MAAM,2BAA2B,QAAmB,CAAC;AAE5D,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAWzD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAQzD;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,kBAAkB;IACjC,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,yEAAyE;IACzE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,4DAA4D;AAC5D,MAAM,WAAW,uBAAuB;IACtC,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,gFAAgF;AAChF,MAAM,WAAW,qBAAqB;IACpC,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IAC1C,2BAA2B,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAAC;IACzF,kBAAkB,CAAC,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAAC;IAC7D,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,uBAAuB,GAAG,MAAM,CAAC;CAC/E;AAyhCD;;;GAGG;AACH,wBAAgB,wBAAwB,IAAI,qBAAqB,CAShE;AAED;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,kBAAkB,GAAG,MAAM,CAUvE;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,gBAAgB;IAChE,yBAAyB,CACvB,MAAM,EAAE,sBAAsB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,qBAAqB,EAAE;IAoCpB,OAAO,CACX,MAAM,EAAE,sBAAsB,EAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAC5B,OAAO,EAAE,QAAQ,GAChB,OAAO,CAAC,OAAO,CAAC;CA0DpB"}
@@ -865,6 +865,127 @@ function cleanupShippedPackage() {
865
865
  shippedExtractDir = null;
866
866
  }
867
867
  `;
868
+ /**
869
+ * Runtime support for asset catalogs (package_source variants only).
870
+ *
871
+ * `swift build` copies a target's .xcassets into its resource bundle but never
872
+ * runs actool, so SwiftUI's Image("name") finds no compiled catalog and the
873
+ * image does not resolve. After the harness builds, compile every .xcassets in
874
+ * the package with actool into the harness bin directory (= the render
875
+ * executable's Bundle.main), so unmodified Image("name") references resolve.
876
+ * One actool invocation over all catalogs (it merges them). Best-effort:
877
+ * a compile failure is logged and rendering continues -- the affected images
878
+ * simply will not appear, which the human comparing the screens will see.
879
+ */
880
+ const RENDER_SCRIPT_ASSET_CATALOG_STAGE = String.raw `
881
+ // ---- asset-catalog stage (package_source variants) ----
882
+
883
+ var ASSET_CATALOG_MIN_MACOS = '13.0';
884
+
885
+ // Recursively collect *.xcassets directories under root, skipping .build and
886
+ // the catalogs' own contents (a catalog is a leaf for this scan).
887
+ function findAssetCatalogs(root) {
888
+ var found = [];
889
+ var stack = [root];
890
+ while (stack.length > 0) {
891
+ var dir = stack.pop();
892
+ var entries;
893
+ try {
894
+ entries = fs.readdirSync(dir, { withFileTypes: true });
895
+ } catch (readError) {
896
+ continue;
897
+ }
898
+ for (var i = 0; i < entries.length; i++) {
899
+ var entry = entries[i];
900
+ if (!entry.isDirectory()) continue;
901
+ if (entry.name === '.build') continue;
902
+ var full = path.join(dir, entry.name);
903
+ if (entry.name.slice(-9) === '.xcassets') {
904
+ found.push(full);
905
+ } else {
906
+ stack.push(full);
907
+ }
908
+ }
909
+ }
910
+ return found.sort();
911
+ }
912
+
913
+ // Run actool over the given catalogs, emitting Assets.car into outDir.
914
+ // Best-effort: a failure is logged (warn-and-continue) and rendering proceeds.
915
+ function compileCatalogsInto(catalogs, outDir, label) {
916
+ try {
917
+ fs.mkdirSync(outDir, { recursive: true });
918
+ } catch (mkdirError) {
919
+ // best effort -- actool reports a missing output dir itself
920
+ }
921
+ var args = ['actool'].concat(catalogs);
922
+ args.push(
923
+ '--compile', outDir,
924
+ '--platform', 'macosx',
925
+ '--minimum-deployment-target', ASSET_CATALOG_MIN_MACOS,
926
+ '--output-format', 'human-readable-text'
927
+ );
928
+ log('xcrun ' + args.join(' '));
929
+ var result = cp.spawnSync('xcrun', args, {
930
+ encoding: 'utf-8',
931
+ maxBuffer: 16 * 1024 * 1024,
932
+ });
933
+ if (result.stdout) process.stdout.write(result.stdout);
934
+ if (result.stderr) process.stderr.write(result.stderr);
935
+ if (result.error || result.status !== 0) {
936
+ var detail = (result.stderr || '') +
937
+ (result.error ? String(result.error.message || result.error) : '');
938
+ logError(
939
+ 'asset-catalog compile failed for ' + label + ' (continuing; catalog ' +
940
+ 'images may not resolve): ' + sanitizeMessage(detail.trim())
941
+ );
942
+ }
943
+ }
944
+
945
+ // SwiftUI resolves Image("name") against Bundle.main and
946
+ // Image("name", bundle: .module) against a target's own resource bundle.
947
+ // swift build copies a .process'd .xcassets into the module bundle but never
948
+ // compiles it, and a loose catalog is copied nowhere. So compile twice:
949
+ // the package's source catalogs into the bin dir (= Bundle.main), AND each
950
+ // module bundle's copied catalog in place (= Bundle.module). This covers both
951
+ // reference idioms, so the agent need not know which bundle the renderer uses.
952
+ function compileAssetCatalogs(packageDir, harnessDir) {
953
+ var sourceCatalogs = findAssetCatalogs(packageDir);
954
+ if (sourceCatalogs.length === 0) return;
955
+
956
+ var binResult = runSwift(['build', '--package-path', harnessDir, '--show-bin-path']);
957
+ if (binResult.status !== 0) {
958
+ logError('asset-catalog compile skipped: could not resolve the build bin path');
959
+ return;
960
+ }
961
+ var binPath = String(binResult.stdout || '').trim().split('\n').pop().trim();
962
+ if (!binPath) {
963
+ logError('asset-catalog compile skipped: empty build bin path');
964
+ return;
965
+ }
966
+
967
+ // Bundle.main: the package's source catalogs, merged into the bin dir.
968
+ compileCatalogsInto(sourceCatalogs, binPath, 'Bundle.main');
969
+
970
+ // Bundle.module: each *.bundle in the bin dir that carries a copied catalog,
971
+ // compiled in place so Image(..., bundle: .module) resolves.
972
+ var entries;
973
+ try {
974
+ entries = fs.readdirSync(binPath, { withFileTypes: true });
975
+ } catch (readError) {
976
+ entries = [];
977
+ }
978
+ for (var i = 0; i < entries.length; i++) {
979
+ var entry = entries[i];
980
+ if (!entry.isDirectory() || entry.name.slice(-7) !== '.bundle') continue;
981
+ var bundleDir = path.join(binPath, entry.name);
982
+ var bundleCatalogs = findAssetCatalogs(bundleDir);
983
+ if (bundleCatalogs.length > 0) {
984
+ compileCatalogsInto(bundleCatalogs, bundleDir, entry.name);
985
+ }
986
+ }
987
+ }
988
+ `;
868
989
  /**
869
990
  * Replaces exactly one occurrence of `anchor` in `source`. Throws when the
870
991
  * anchor is missing or ambiguous, so any drift between the v1 runtime text
@@ -966,6 +1087,12 @@ function buildPackageSourceMain() {
966
1087
  ' }\n');
967
1088
  // The shipped-package stage is defined ahead of main().
968
1089
  main = replaceOnce(main, 'function main() {', RENDER_SCRIPT_PACKAGE_STAGE + '\nfunction main() {');
1090
+ // Asset-catalog stage: define the helpers ahead of renderWithHarness, then
1091
+ // compile any catalogs once the package has proven it builds (after the
1092
+ // probe) and before the per-screen renders that depend on the images.
1093
+ main = replaceOnce(main, 'function renderWithHarness(renderable, packagePath) {', RENDER_SCRIPT_ASSET_CATALOG_STAGE + '\nfunction renderWithHarness(renderable, packagePath) {');
1094
+ main = replaceOnce(main, ' renderable.forEach(function (entry) {', ' compileAssetCatalogs(packagePath, harnessDir);\n\n' +
1095
+ ' renderable.forEach(function (entry) {');
969
1096
  return main;
970
1097
  }
971
1098
  const RENDER_SCRIPT_MAIN_WITH_PACKAGE_SOURCE = buildPackageSourceMain();
@@ -58,15 +58,21 @@ const FAKE_SWIFT_LINES = [
58
58
  ' printf \'{"name":"shipped-package","products":[%s]}\\n\' "$json"',
59
59
  ' exit 0 ;;',
60
60
  ' build)',
61
- ' product=""; target=""; pkg=""',
61
+ ' product=""; target=""; pkg=""; showbin=""',
62
62
  ' while [ $# -gt 0 ]; do',
63
63
  ' case "$1" in',
64
64
  ' --product) product="$2"; shift 2 ;;',
65
65
  ' --target) target="$2"; shift 2 ;;',
66
66
  ' --package-path) pkg="$2"; shift 2 ;;',
67
+ ' --show-bin-path) showbin=1; shift ;;',
67
68
  ' *) shift ;;',
68
69
  ' esac',
69
70
  ' done',
71
+ ' if [ -n "$showbin" ]; then',
72
+ ' bp="$pkg/.build/debug"',
73
+ ' [ -n "$FAKE_SWIFT_MODULE_CATALOG" ] && mkdir -p "$bp/$FAKE_SWIFT_MODULE_CATALOG.bundle/Media.xcassets"',
74
+ ' echo "$bp"; exit 0',
75
+ ' fi',
70
76
  ' name="${product:-$target}"',
71
77
  ' if [ "$name" = "RenderProbe" ]; then',
72
78
  ' if [ -n "$FAKE_SWIFT_PROBE_FAIL" ]; then',
@@ -109,6 +115,35 @@ const FAKE_SWIFT_LINES = [
109
115
  ' exit 0 ;;',
110
116
  'esac',
111
117
  ];
118
+ /**
119
+ * PATH-shimmed fake `xcrun` covering `xcrun actool`. Logs its args to
120
+ * $FAKE_XCRUN_LOG, writes a stub Assets.car into the --compile directory,
121
+ * and fails (exit 1) when FAKE_ACTOOL_FAIL is set so the warn-and-continue
122
+ * path is exercisable.
123
+ */
124
+ const FAKE_XCRUN_LINES = [
125
+ '#!/bin/bash',
126
+ '[ -n "$FAKE_XCRUN_LOG" ] && echo "$*" >> "$FAKE_XCRUN_LOG"',
127
+ 'sub="$1"; shift',
128
+ 'case "$sub" in',
129
+ ' actool)',
130
+ ' out=""',
131
+ ' while [ $# -gt 0 ]; do',
132
+ ' case "$1" in',
133
+ ' --compile) out="$2"; shift 2 ;;',
134
+ ' *) shift ;;',
135
+ ' esac',
136
+ ' done',
137
+ ' if [ -n "$FAKE_ACTOOL_FAIL" ]; then',
138
+ ' echo "error: actool: malformed asset catalog" >&2',
139
+ ' exit 1',
140
+ ' fi',
141
+ ' [ -n "$out" ] && mkdir -p "$out" && printf \'fake-car\' > "$out/Assets.car"',
142
+ ' exit 0 ;;',
143
+ ' *)',
144
+ ' exit 0 ;;',
145
+ 'esac',
146
+ ];
112
147
  const tempDirs = [];
113
148
  afterAll(() => {
114
149
  for (const dir of tempDirs) {
@@ -131,6 +166,9 @@ function makeProject(params) {
131
166
  const swiftPath = join(binDir, 'swift');
132
167
  writeFileSync(swiftPath, FAKE_SWIFT_LINES.join('\n') + '\n');
133
168
  chmodSync(swiftPath, 0o755);
169
+ const xcrunPath = join(binDir, 'xcrun');
170
+ writeFileSync(xcrunPath, FAKE_XCRUN_LINES.join('\n') + '\n');
171
+ chmodSync(xcrunPath, 0o755);
134
172
  if (params !== undefined) {
135
173
  writeFileSync(join(dir, '.ci', 'inputs', 'params.json'), typeof params === 'string' ? params : JSON.stringify(params));
136
174
  }
@@ -862,6 +900,70 @@ describe('render script runtime (package_source: repo)', () => {
862
900
  ],
863
901
  });
864
902
  });
903
+ test('compiles asset catalogs found in the package before rendering', async () => {
904
+ const project = makeProject({ screens: { HomeView: 'home.png' } });
905
+ writeReference(project, 'home.png');
906
+ // A catalog at the package root: SwiftPM ignores it, so the renderer must
907
+ // compile it with actool for Image("name") to resolve at render time.
908
+ const catalog = join(project.packageDir, 'Assets', 'Media.xcassets');
909
+ mkdirSync(catalog, { recursive: true });
910
+ writeFileSync(join(catalog, 'Contents.json'), '{}');
911
+ const script = await buildScript(project, { package_source: 'repo' });
912
+ const xcrunLog = join(project.dir, 'xcrun-invocations.log');
913
+ const run = runScript(project, script, { FAKE_XCRUN_LOG: xcrunLog });
914
+ expect(run.status).toBe(0);
915
+ expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
916
+ const xcrunCalls = readFileSync(xcrunLog, 'utf-8');
917
+ expect(xcrunCalls).toContain('actool');
918
+ expect(xcrunCalls).toContain('Media.xcassets');
919
+ expect(xcrunCalls).toContain('--compile');
920
+ expect(xcrunCalls).toContain('--platform macosx');
921
+ });
922
+ test('also compiles catalogs in module bundles for Bundle.module references', async () => {
923
+ const project = makeProject({ screens: { HomeView: 'home.png' } });
924
+ writeReference(project, 'home.png');
925
+ // Source catalog drives the main-bundle (Bundle.main) compile. The module
926
+ // bundle — which the fake toolchain materializes to mimic `swift build`
927
+ // copying a .process resource — must ALSO be compiled in place, or an
928
+ // Image("name", bundle: .module) reference finds only the raw catalog.
929
+ const catalog = join(project.packageDir, 'Assets', 'Media.xcassets');
930
+ mkdirSync(catalog, { recursive: true });
931
+ writeFileSync(join(catalog, 'Contents.json'), '{}');
932
+ const script = await buildScript(project, { package_source: 'repo' });
933
+ const xcrunLog = join(project.dir, 'xcrun-invocations.log');
934
+ const run = runScript(project, script, {
935
+ FAKE_XCRUN_LOG: xcrunLog,
936
+ FAKE_SWIFT_MODULE_CATALOG: 'FixtureViews',
937
+ });
938
+ expect(run.status).toBe(0);
939
+ const calls = readFileSync(xcrunLog, 'utf-8').trim().split('\n');
940
+ // The module bundle's own catalog is compiled into the bundle directory.
941
+ const moduleCompile = calls.find((c) => c.includes('.bundle/Media.xcassets'));
942
+ expect(moduleCompile).toBeTruthy();
943
+ expect(moduleCompile).toMatch(/--compile \S+\.bundle(\s|$)/);
944
+ });
945
+ test('does not invoke actool when the package has no asset catalogs', async () => {
946
+ const project = makeProject({ screens: { HomeView: 'home.png' } });
947
+ writeReference(project, 'home.png');
948
+ const script = await buildScript(project, { package_source: 'repo' });
949
+ const xcrunLog = join(project.dir, 'xcrun-invocations.log');
950
+ const run = runScript(project, script, { FAKE_XCRUN_LOG: xcrunLog });
951
+ expect(run.status).toBe(0);
952
+ expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
953
+ expect(existsSync(xcrunLog)).toBe(false);
954
+ });
955
+ test('a failing asset-catalog compile warns but still renders (warn-and-continue)', async () => {
956
+ const project = makeProject({ screens: { HomeView: 'home.png' } });
957
+ writeReference(project, 'home.png');
958
+ const catalog = join(project.packageDir, 'Media.xcassets');
959
+ mkdirSync(catalog, { recursive: true });
960
+ writeFileSync(join(catalog, 'Contents.json'), '{}');
961
+ const script = await buildScript(project, { package_source: 'repo' });
962
+ const run = runScript(project, script, { FAKE_ACTOOL_FAIL: '1' });
963
+ expect(run.status).toBe(0);
964
+ expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
965
+ expect(run.stderr).toContain('asset-catalog compile failed');
966
+ });
865
967
  });
866
968
  describe('render script runtime (package_source: inputs)', () => {
867
969
  test('renders every screen from a shipped archive with a single package root', async () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@invarn/cibuild",
3
- "version": "2.0.2",
3
+ "version": "2.0.4",
4
4
  "description": "CI Build CLI — local pipeline orchestration and validation",
5
5
  "type": "module",
6
6
  "main": "dist/cli.cjs",