@invarn/cibuild 2.9.5 → 2.9.7
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/dist/chunk-35SI42CD.mjs +1 -0
- package/dist/chunk-IC45KVLJ.mjs +1 -0
- package/dist/chunk-XDY4V23V.mjs +1 -0
- package/dist/cli.cjs +14 -14
- package/dist/commands.mjs +1 -0
- package/dist/lib.mjs +3934 -0
- package/package.json +6 -10
- package/dist/src/cli.d.ts +0 -3
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -399
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts +0 -38
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts.map +0 -1
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.js +0 -222
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts +0 -41
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts.map +0 -1
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +0 -324
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts +0 -2
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts.map +0 -1
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.js +0 -382
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts +0 -29
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map +0 -1
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.js +0 -160
- package/dist/src/commands/android-gms-guard.test.d.ts +0 -9
- package/dist/src/commands/android-gms-guard.test.d.ts.map +0 -1
- package/dist/src/commands/android-gms-guard.test.js +0 -78
- package/dist/src/commands/android-java-version.test.d.ts +0 -12
- package/dist/src/commands/android-java-version.test.d.ts.map +0 -1
- package/dist/src/commands/android-java-version.test.js +0 -209
- package/dist/src/commands/android-scanner-java.test.d.ts +0 -2
- package/dist/src/commands/android-scanner-java.test.d.ts.map +0 -1
- package/dist/src/commands/android-scanner-java.test.js +0 -130
- package/dist/src/commands/android-scanner.d.ts +0 -250
- package/dist/src/commands/android-scanner.d.ts.map +0 -1
- package/dist/src/commands/android-scanner.js +0 -1639
- package/dist/src/commands/build.d.ts +0 -215
- package/dist/src/commands/build.d.ts.map +0 -1
- package/dist/src/commands/build.js +0 -1433
- package/dist/src/commands/build.test.d.ts +0 -13
- package/dist/src/commands/build.test.d.ts.map +0 -1
- package/dist/src/commands/build.test.js +0 -121
- package/dist/src/commands/detect-platform.d.ts +0 -13
- package/dist/src/commands/detect-platform.d.ts.map +0 -1
- package/dist/src/commands/detect-platform.js +0 -51
- package/dist/src/commands/detect-project.d.ts +0 -7
- package/dist/src/commands/detect-project.d.ts.map +0 -1
- package/dist/src/commands/detect-project.js +0 -12
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts +0 -26
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts.map +0 -1
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.js +0 -196
- package/dist/src/commands/edit.d.ts +0 -12
- package/dist/src/commands/edit.d.ts.map +0 -1
- package/dist/src/commands/edit.js +0 -652
- package/dist/src/commands/edit.test.d.ts +0 -6
- package/dist/src/commands/edit.test.d.ts.map +0 -1
- package/dist/src/commands/edit.test.js +0 -45
- package/dist/src/commands/fidelity-trim.d.ts +0 -13
- package/dist/src/commands/fidelity-trim.d.ts.map +0 -1
- package/dist/src/commands/fidelity-trim.js +0 -21
- package/dist/src/commands/fidelity-trim.test.d.ts +0 -8
- package/dist/src/commands/fidelity-trim.test.d.ts.map +0 -1
- package/dist/src/commands/fidelity-trim.test.js +0 -79
- package/dist/src/commands/file-secret-collector.d.ts +0 -50
- package/dist/src/commands/file-secret-collector.d.ts.map +0 -1
- package/dist/src/commands/file-secret-collector.js +0 -217
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts +0 -38
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts.map +0 -1
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.js +0 -300
- package/dist/src/commands/github-workflow.d.ts +0 -5
- package/dist/src/commands/github-workflow.d.ts.map +0 -1
- package/dist/src/commands/github-workflow.js +0 -45
- package/dist/src/commands/index.d.ts +0 -33
- package/dist/src/commands/index.d.ts.map +0 -1
- package/dist/src/commands/index.js +0 -33
- package/dist/src/commands/init.d.ts +0 -20
- package/dist/src/commands/init.d.ts.map +0 -1
- package/dist/src/commands/init.js +0 -264
- package/dist/src/commands/ios-scanner.d.ts +0 -104
- package/dist/src/commands/ios-scanner.d.ts.map +0 -1
- package/dist/src/commands/ios-scanner.js +0 -492
- package/dist/src/commands/ios-scheme-ranking.test.d.ts +0 -2
- package/dist/src/commands/ios-scheme-ranking.test.d.ts.map +0 -1
- package/dist/src/commands/ios-scheme-ranking.test.js +0 -264
- package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts +0 -30
- package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts.map +0 -1
- package/dist/src/commands/local-properties-reaches-every-workflow.test.js +0 -146
- package/dist/src/commands/reset.d.ts +0 -7
- package/dist/src/commands/reset.d.ts.map +0 -1
- package/dist/src/commands/reset.js +0 -81
- package/dist/src/commands/run.d.ts +0 -22
- package/dist/src/commands/run.d.ts.map +0 -1
- package/dist/src/commands/run.js +0 -141
- package/dist/src/commands/secrets-sync-workflow.d.ts +0 -15
- package/dist/src/commands/secrets-sync-workflow.d.ts.map +0 -1
- package/dist/src/commands/secrets-sync-workflow.js +0 -255
- package/dist/src/commands/secrets-upload.d.ts +0 -21
- package/dist/src/commands/secrets-upload.d.ts.map +0 -1
- package/dist/src/commands/secrets-upload.js +0 -177
- package/dist/src/commands/secrets-upload.test.d.ts +0 -5
- package/dist/src/commands/secrets-upload.test.d.ts.map +0 -1
- package/dist/src/commands/secrets-upload.test.js +0 -60
- package/dist/src/commands/validate.d.ts +0 -10
- package/dist/src/commands/validate.d.ts.map +0 -1
- package/dist/src/commands/validate.js +0 -46
- package/dist/src/config.d.ts +0 -3
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/config.js +0 -47
- package/dist/src/envman/cli.d.ts +0 -21
- package/dist/src/envman/cli.d.ts.map +0 -1
- package/dist/src/envman/cli.js +0 -240
- package/dist/src/envman/envman.d.ts +0 -83
- package/dist/src/envman/envman.d.ts.map +0 -1
- package/dist/src/envman/envman.js +0 -361
- package/dist/src/envman/envman.test.d.ts +0 -5
- package/dist/src/envman/envman.test.d.ts.map +0 -1
- package/dist/src/envman/envman.test.js +0 -236
- package/dist/src/envman/envstore-round-trip.test.d.ts +0 -27
- package/dist/src/envman/envstore-round-trip.test.d.ts.map +0 -1
- package/dist/src/envman/envstore-round-trip.test.js +0 -130
- package/dist/src/envman/index.d.ts +0 -23
- package/dist/src/envman/index.d.ts.map +0 -1
- package/dist/src/envman/index.js +0 -23
- package/dist/src/envman/shim.d.ts +0 -16
- package/dist/src/envman/shim.d.ts.map +0 -1
- package/dist/src/envman/shim.js +0 -66
- package/dist/src/envman/types.d.ts +0 -55
- package/dist/src/envman/types.d.ts.map +0 -1
- package/dist/src/envman/types.js +0 -12
- package/dist/src/lib.d.ts +0 -33
- package/dist/src/lib.d.ts.map +0 -1
- package/dist/src/lib.js +0 -37
- package/dist/src/pipeline.d.ts +0 -3
- package/dist/src/pipeline.d.ts.map +0 -1
- package/dist/src/pipeline.js +0 -57
- package/dist/src/runner-node-step.test.d.ts +0 -12
- package/dist/src/runner-node-step.test.d.ts.map +0 -1
- package/dist/src/runner-node-step.test.js +0 -61
- package/dist/src/runner.d.ts +0 -31
- package/dist/src/runner.d.ts.map +0 -1
- package/dist/src/runner.js +0 -240
- package/dist/src/shared/detect-project.d.ts +0 -57
- package/dist/src/shared/detect-project.d.ts.map +0 -1
- package/dist/src/shared/detect-project.js +0 -219
- package/dist/src/shared/detect-project.test.d.ts +0 -2
- package/dist/src/shared/detect-project.test.d.ts.map +0 -1
- package/dist/src/shared/detect-project.test.js +0 -161
- package/dist/src/shared/git.d.ts +0 -25
- package/dist/src/shared/git.d.ts.map +0 -1
- package/dist/src/shared/git.js +0 -53
- package/dist/src/shared/git.test.d.ts +0 -9
- package/dist/src/shared/git.test.d.ts.map +0 -1
- package/dist/src/shared/git.test.js +0 -66
- package/dist/src/shared/gitignore.d.ts +0 -6
- package/dist/src/shared/gitignore.d.ts.map +0 -1
- package/dist/src/shared/gitignore.js +0 -26
- package/dist/src/shared/gradle-settings.d.ts +0 -49
- package/dist/src/shared/gradle-settings.d.ts.map +0 -1
- package/dist/src/shared/gradle-settings.js +0 -138
- package/dist/src/shared/gradle-settings.test.d.ts +0 -2
- package/dist/src/shared/gradle-settings.test.d.ts.map +0 -1
- package/dist/src/shared/gradle-settings.test.js +0 -65
- package/dist/src/shared/prompts.d.ts +0 -30
- package/dist/src/shared/prompts.d.ts.map +0 -1
- package/dist/src/shared/prompts.js +0 -121
- package/dist/src/shared/xcode-container.test.d.ts +0 -2
- package/dist/src/shared/xcode-container.test.d.ts.map +0 -1
- package/dist/src/shared/xcode-container.test.js +0 -123
- package/dist/src/step-marker.d.ts +0 -12
- package/dist/src/step-marker.d.ts.map +0 -1
- package/dist/src/step-marker.js +0 -29
- package/dist/src/step-marker.test.d.ts +0 -11
- package/dist/src/step-marker.test.d.ts.map +0 -1
- package/dist/src/step-marker.test.js +0 -45
- package/dist/src/types.d.ts +0 -58
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js +0 -2
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts +0 -28
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts.map +0 -1
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.js +0 -223
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts +0 -20
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts.map +0 -1
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.js +0 -201
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts +0 -37
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts.map +0 -1
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.js +0 -255
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts +0 -32
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts.map +0 -1
- package/dist/src/yaml/a-missing-credential-is-loud.test.js +0 -301
- package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts +0 -18
- package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts.map +0 -1
- package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.js +0 -66
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts +0 -34
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts.map +0 -1
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.js +0 -218
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts +0 -27
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts.map +0 -1
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.js +0 -126
- package/dist/src/yaml/bitrise-compat.d.ts +0 -65
- package/dist/src/yaml/bitrise-compat.d.ts.map +0 -1
- package/dist/src/yaml/bitrise-compat.js +0 -206
- package/dist/src/yaml/bitrise-compat.test.d.ts +0 -5
- package/dist/src/yaml/bitrise-compat.test.d.ts.map +0 -1
- package/dist/src/yaml/bitrise-compat.test.js +0 -347
- package/dist/src/yaml/cache-key-missing-file.test.d.ts +0 -12
- package/dist/src/yaml/cache-key-missing-file.test.d.ts.map +0 -1
- package/dist/src/yaml/cache-key-missing-file.test.js +0 -116
- package/dist/src/yaml/converter.d.ts +0 -103
- package/dist/src/yaml/converter.d.ts.map +0 -1
- package/dist/src/yaml/converter.js +0 -420
- package/dist/src/yaml/converter.test.d.ts +0 -5
- package/dist/src/yaml/converter.test.d.ts.map +0 -1
- package/dist/src/yaml/converter.test.js +0 -548
- package/dist/src/yaml/e2e.test.d.ts +0 -6
- package/dist/src/yaml/e2e.test.d.ts.map +0 -1
- package/dist/src/yaml/e2e.test.js +0 -446
- package/dist/src/yaml/env-resolver.d.ts +0 -218
- package/dist/src/yaml/env-resolver.d.ts.map +0 -1
- package/dist/src/yaml/env-resolver.js +0 -591
- package/dist/src/yaml/env-resolver.test.d.ts +0 -5
- package/dist/src/yaml/env-resolver.test.d.ts.map +0 -1
- package/dist/src/yaml/env-resolver.test.js +0 -502
- package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts +0 -16
- package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts.map +0 -1
- package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.js +0 -126
- package/dist/src/yaml/fidelity-input-digest.d.ts +0 -37
- package/dist/src/yaml/fidelity-input-digest.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-input-digest.js +0 -208
- package/dist/src/yaml/fidelity-input-digest.test.d.ts +0 -2
- package/dist/src/yaml/fidelity-input-digest.test.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-input-digest.test.js +0 -167
- package/dist/src/yaml/fidelity-manifest.d.ts +0 -126
- package/dist/src/yaml/fidelity-manifest.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-manifest.js +0 -134
- package/dist/src/yaml/fidelity-manifest.test.d.ts +0 -12
- package/dist/src/yaml/fidelity-manifest.test.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-manifest.test.js +0 -251
- package/dist/src/yaml/interactive-prompts.d.ts +0 -71
- package/dist/src/yaml/interactive-prompts.d.ts.map +0 -1
- package/dist/src/yaml/interactive-prompts.js +0 -260
- package/dist/src/yaml/meta-helpers.d.ts +0 -28
- package/dist/src/yaml/meta-helpers.d.ts.map +0 -1
- package/dist/src/yaml/meta-helpers.js +0 -46
- package/dist/src/yaml/missing-env-handler.d.ts +0 -45
- package/dist/src/yaml/missing-env-handler.d.ts.map +0 -1
- package/dist/src/yaml/missing-env-handler.js +0 -64
- package/dist/src/yaml/parser.d.ts +0 -33
- package/dist/src/yaml/parser.d.ts.map +0 -1
- package/dist/src/yaml/parser.js +0 -148
- package/dist/src/yaml/parser.test.d.ts +0 -5
- package/dist/src/yaml/parser.test.d.ts.map +0 -1
- package/dist/src/yaml/parser.test.js +0 -83
- package/dist/src/yaml/pipeline-with-secrets.d.ts +0 -25
- package/dist/src/yaml/pipeline-with-secrets.d.ts.map +0 -1
- package/dist/src/yaml/pipeline-with-secrets.js +0 -76
- package/dist/src/yaml/platform-detector.d.ts +0 -83
- package/dist/src/yaml/platform-detector.d.ts.map +0 -1
- package/dist/src/yaml/platform-detector.js +0 -189
- package/dist/src/yaml/platform-detector.test.d.ts +0 -5
- package/dist/src/yaml/platform-detector.test.d.ts.map +0 -1
- package/dist/src/yaml/platform-detector.test.js +0 -525
- package/dist/src/yaml/preflight-validation.d.ts +0 -29
- package/dist/src/yaml/preflight-validation.d.ts.map +0 -1
- package/dist/src/yaml/preflight-validation.js +0 -21
- package/dist/src/yaml/secrets-manager.d.ts +0 -87
- package/dist/src/yaml/secrets-manager.d.ts.map +0 -1
- package/dist/src/yaml/secrets-manager.js +0 -230
- package/dist/src/yaml/shell-assignments.d.ts +0 -40
- package/dist/src/yaml/shell-assignments.d.ts.map +0 -1
- package/dist/src/yaml/shell-assignments.js +0 -77
- package/dist/src/yaml/step-validator-check-failure.test.d.ts +0 -24
- package/dist/src/yaml/step-validator-check-failure.test.d.ts.map +0 -1
- package/dist/src/yaml/step-validator-check-failure.test.js +0 -130
- package/dist/src/yaml/step-validator-self-assigned.test.d.ts +0 -23
- package/dist/src/yaml/step-validator-self-assigned.test.d.ts.map +0 -1
- package/dist/src/yaml/step-validator-self-assigned.test.js +0 -191
- package/dist/src/yaml/step-validator.d.ts +0 -67
- package/dist/src/yaml/step-validator.d.ts.map +0 -1
- package/dist/src/yaml/step-validator.js +0 -466
- package/dist/src/yaml/step-validator.test.d.ts +0 -18
- package/dist/src/yaml/step-validator.test.d.ts.map +0 -1
- package/dist/src/yaml/step-validator.test.js +0 -140
- package/dist/src/yaml/steps/android-sign.d.ts +0 -35
- package/dist/src/yaml/steps/android-sign.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-sign.js +0 -147
- package/dist/src/yaml/steps/android-variant-resolution.test.d.ts +0 -2
- package/dist/src/yaml/steps/android-variant-resolution.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-variant-resolution.test.js +0 -260
- package/dist/src/yaml/steps/android-version.d.ts +0 -26
- package/dist/src/yaml/steps/android-version.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-version.js +0 -128
- package/dist/src/yaml/steps/android-version.test.d.ts +0 -5
- package/dist/src/yaml/steps/android-version.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-version.test.js +0 -196
- package/dist/src/yaml/steps/android.d.ts +0 -156
- package/dist/src/yaml/steps/android.d.ts.map +0 -1
- package/dist/src/yaml/steps/android.js +0 -1092
- package/dist/src/yaml/steps/app-store-deploy.d.ts +0 -48
- package/dist/src/yaml/steps/app-store-deploy.d.ts.map +0 -1
- package/dist/src/yaml/steps/app-store-deploy.js +0 -167
- package/dist/src/yaml/steps/base.d.ts +0 -263
- package/dist/src/yaml/steps/base.d.ts.map +0 -1
- package/dist/src/yaml/steps/base.js +0 -380
- package/dist/src/yaml/steps/bitrise-android-tools.d.ts +0 -26
- package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-android-tools.js +0 -200
- package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-android-tools.test.js +0 -280
- package/dist/src/yaml/steps/bitrise-apk-info.d.ts +0 -22
- package/dist/src/yaml/steps/bitrise-apk-info.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-apk-info.js +0 -144
- package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-apk-info.test.js +0 -331
- package/dist/src/yaml/steps/bitrise-slack.d.ts +0 -49
- package/dist/src/yaml/steps/bitrise-slack.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-slack.js +0 -280
- package/dist/src/yaml/steps/bitrise-slack.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-slack.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-slack.test.js +0 -484
- package/dist/src/yaml/steps/bitrise-ssh.d.ts +0 -27
- package/dist/src/yaml/steps/bitrise-ssh.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-ssh.js +0 -134
- package/dist/src/yaml/steps/bitrise-ssh.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-ssh.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-ssh.test.js +0 -205
- package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts +0 -27
- package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache-pull-daemon.test.js +0 -212
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts +0 -42
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.js +0 -209
- package/dist/src/yaml/steps/cache-push-daemon.test.d.ts +0 -27
- package/dist/src/yaml/steps/cache-push-daemon.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache-push-daemon.test.js +0 -171
- package/dist/src/yaml/steps/cache.d.ts +0 -59
- package/dist/src/yaml/steps/cache.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache.js +0 -1027
- package/dist/src/yaml/steps/capture-injection.d.ts +0 -30
- package/dist/src/yaml/steps/capture-injection.d.ts.map +0 -1
- package/dist/src/yaml/steps/capture-injection.js +0 -88
- package/dist/src/yaml/steps/capture-injection.test.d.ts +0 -2
- package/dist/src/yaml/steps/capture-injection.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/capture-injection.test.js +0 -163
- package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts +0 -42
- package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-deployment-floor.js +0 -140
- package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts +0 -11
- package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-deployment-floor.test.js +0 -176
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts +0 -22
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.js +0 -97
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts +0 -17
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.js +0 -282
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts +0 -17
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.js +0 -132
- package/dist/src/yaml/steps/envstore-loader.test.d.ts +0 -15
- package/dist/src/yaml/steps/envstore-loader.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/envstore-loader.test.js +0 -143
- package/dist/src/yaml/steps/fastlane.d.ts +0 -27
- package/dist/src/yaml/steps/fastlane.d.ts.map +0 -1
- package/dist/src/yaml/steps/fastlane.js +0 -79
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts +0 -53
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-aspect-guard.js +0 -72
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts +0 -11
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.js +0 -50
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts +0 -87
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-reference-provenance.js +0 -111
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts +0 -22
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.js +0 -118
- package/dist/src/yaml/steps/file.d.ts +0 -52
- package/dist/src/yaml/steps/file.d.ts.map +0 -1
- package/dist/src/yaml/steps/file.js +0 -108
- package/dist/src/yaml/steps/flutter.d.ts +0 -63
- package/dist/src/yaml/steps/flutter.d.ts.map +0 -1
- package/dist/src/yaml/steps/flutter.js +0 -215
- package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts +0 -16
- package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.js +0 -127
- package/dist/src/yaml/steps/git-clone.d.ts +0 -32
- package/dist/src/yaml/steps/git-clone.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-clone.js +0 -171
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts +0 -10
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.js +0 -162
- package/dist/src/yaml/steps/git-fetch-retry.d.ts +0 -62
- package/dist/src/yaml/steps/git-fetch-retry.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-fetch-retry.js +0 -155
- package/dist/src/yaml/steps/git-fetch-retry.test.d.ts +0 -5
- package/dist/src/yaml/steps/git-fetch-retry.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-fetch-retry.test.js +0 -86
- package/dist/src/yaml/steps/google-play-deploy.d.ts +0 -37
- package/dist/src/yaml/steps/google-play-deploy.d.ts.map +0 -1
- package/dist/src/yaml/steps/google-play-deploy.js +0 -193
- package/dist/src/yaml/steps/google-play-deploy.test.d.ts +0 -5
- package/dist/src/yaml/steps/google-play-deploy.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/google-play-deploy.test.js +0 -310
- package/dist/src/yaml/steps/gradle-memory.d.ts +0 -61
- package/dist/src/yaml/steps/gradle-memory.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-memory.js +0 -386
- package/dist/src/yaml/steps/gradle-memory.test.d.ts +0 -2
- package/dist/src/yaml/steps/gradle-memory.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-memory.test.js +0 -344
- package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts +0 -28
- package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-sdk-cmake.js +0 -107
- package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts +0 -2
- package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-sdk-cmake.test.js +0 -183
- package/dist/src/yaml/steps/index.d.ts +0 -10
- package/dist/src/yaml/steps/index.d.ts.map +0 -1
- package/dist/src/yaml/steps/index.js +0 -1461
- package/dist/src/yaml/steps/ios-deps.d.ts +0 -78
- package/dist/src/yaml/steps/ios-deps.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-deps.js +0 -279
- package/dist/src/yaml/steps/ios-deps.test.d.ts +0 -5
- package/dist/src/yaml/steps/ios-deps.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-deps.test.js +0 -90
- package/dist/src/yaml/steps/ios-signing.d.ts +0 -31
- package/dist/src/yaml/steps/ios-signing.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-signing.js +0 -144
- package/dist/src/yaml/steps/ios-version.d.ts +0 -47
- package/dist/src/yaml/steps/ios-version.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-version.js +0 -151
- package/dist/src/yaml/steps/java-home-version-spec.test.d.ts +0 -10
- package/dist/src/yaml/steps/java-home-version-spec.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/java-home-version-spec.test.js +0 -58
- package/dist/src/yaml/steps/linting.d.ts +0 -47
- package/dist/src/yaml/steps/linting.d.ts.map +0 -1
- package/dist/src/yaml/steps/linting.js +0 -148
- package/dist/src/yaml/steps/phase2.test.d.ts +0 -6
- package/dist/src/yaml/steps/phase2.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase2.test.js +0 -197
- package/dist/src/yaml/steps/phase3.test.d.ts +0 -5
- package/dist/src/yaml/steps/phase3.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase3.test.js +0 -144
- package/dist/src/yaml/steps/phase4.test.d.ts +0 -5
- package/dist/src/yaml/steps/phase4.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase4.test.js +0 -166
- package/dist/src/yaml/steps/phase5.test.d.ts +0 -6
- package/dist/src/yaml/steps/phase5.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase5.test.js +0 -263
- package/dist/src/yaml/steps/registry.d.ts +0 -88
- package/dist/src/yaml/steps/registry.d.ts.map +0 -1
- package/dist/src/yaml/steps/registry.js +0 -125
- package/dist/src/yaml/steps/registry.test.d.ts +0 -5
- package/dist/src/yaml/steps/registry.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/registry.test.js +0 -235
- package/dist/src/yaml/steps/release.d.ts +0 -50
- package/dist/src/yaml/steps/release.d.ts.map +0 -1
- package/dist/src/yaml/steps/release.js +0 -154
- package/dist/src/yaml/steps/render-post-processor.d.ts +0 -100
- package/dist/src/yaml/steps/render-post-processor.d.ts.map +0 -1
- package/dist/src/yaml/steps/render-post-processor.js +0 -270
- package/dist/src/yaml/steps/render-post-processor.test.d.ts +0 -13
- package/dist/src/yaml/steps/render-post-processor.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/render-post-processor.test.js +0 -220
- package/dist/src/yaml/steps/script.d.ts +0 -23
- package/dist/src/yaml/steps/script.d.ts.map +0 -1
- package/dist/src/yaml/steps/script.js +0 -63
- package/dist/src/yaml/steps/spec-validation.test.d.ts +0 -6
- package/dist/src/yaml/steps/spec-validation.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/spec-validation.test.js +0 -130
- package/dist/src/yaml/steps/steps.test.d.ts +0 -6
- package/dist/src/yaml/steps/steps.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/steps.test.js +0 -881
- package/dist/src/yaml/steps/structural-facts.d.ts +0 -992
- package/dist/src/yaml/steps/structural-facts.d.ts.map +0 -1
- package/dist/src/yaml/steps/structural-facts.js +0 -3276
- package/dist/src/yaml/steps/structural-facts.test.d.ts +0 -12
- package/dist/src/yaml/steps/structural-facts.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/structural-facts.test.js +0 -3039
- package/dist/src/yaml/steps/test-config.d.ts +0 -5
- package/dist/src/yaml/steps/test-config.d.ts.map +0 -1
- package/dist/src/yaml/steps/test-config.js +0 -25
- package/dist/src/yaml/steps/trim-align-corpus.test.d.ts +0 -28
- package/dist/src/yaml/steps/trim-align-corpus.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/trim-align-corpus.test.js +0 -383
- package/dist/src/yaml/steps/trim-align.d.ts +0 -87
- package/dist/src/yaml/steps/trim-align.d.ts.map +0 -1
- package/dist/src/yaml/steps/trim-align.js +0 -367
- package/dist/src/yaml/steps/trim-align.test.d.ts +0 -11
- package/dist/src/yaml/steps/trim-align.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/trim-align.test.js +0 -261
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts +0 -66
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview-android.js +0 -251
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts +0 -13
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.js +0 -302
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +0 -134
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview.js +0 -173
- package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts +0 -11
- package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview.test.js +0 -210
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +0 -155
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +0 -1389
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts +0 -2
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +0 -1106
- package/dist/src/yaml/steps/ui-fidelity-render.d.ts +0 -230
- package/dist/src/yaml/steps/ui-fidelity-render.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render.js +0 -1597
- package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts +0 -18
- package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render.test.js +0 -1871
- package/dist/src/yaml/steps/xcode-app-product.d.ts +0 -90
- package/dist/src/yaml/steps/xcode-app-product.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-app-product.js +0 -247
- package/dist/src/yaml/steps/xcode-app-product.test.d.ts +0 -2
- package/dist/src/yaml/steps/xcode-app-product.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-app-product.test.js +0 -202
- package/dist/src/yaml/steps/xcode-derived-data.test.d.ts +0 -30
- package/dist/src/yaml/steps/xcode-derived-data.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-derived-data.test.js +0 -279
- package/dist/src/yaml/steps/xcode-destination.d.ts +0 -47
- package/dist/src/yaml/steps/xcode-destination.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-destination.js +0 -202
- package/dist/src/yaml/steps/xcode-destination.test.d.ts +0 -9
- package/dist/src/yaml/steps/xcode-destination.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-destination.test.js +0 -227
- package/dist/src/yaml/steps/xcode-new.test.d.ts +0 -5
- package/dist/src/yaml/steps/xcode-new.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-new.test.js +0 -211
- package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts +0 -9
- package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-resolve-retry.test.js +0 -146
- package/dist/src/yaml/steps/xcode-simulator-archs.d.ts +0 -53
- package/dist/src/yaml/steps/xcode-simulator-archs.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-simulator-archs.js +0 -98
- package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts +0 -21
- package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-simulator-archs.test.js +0 -155
- package/dist/src/yaml/steps/xcode.d.ts +0 -230
- package/dist/src/yaml/steps/xcode.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode.js +0 -1350
- package/dist/src/yaml/types.d.ts +0 -76
- package/dist/src/yaml/types.d.ts.map +0 -1
- package/dist/src/yaml/types.js +0 -5
- package/dist/src/yaml/validation-types.d.ts +0 -102
- package/dist/src/yaml/validation-types.d.ts.map +0 -1
- package/dist/src/yaml/validation-types.js +0 -8
- package/dist/src/yaml/yaml-updater.d.ts +0 -24
- package/dist/src/yaml/yaml-updater.d.ts.map +0 -1
- package/dist/src/yaml/yaml-updater.js +0 -128
|
@@ -1,1597 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ui-fidelity-render step implementation.
|
|
3
|
-
*
|
|
4
|
-
* Renders parameterless SwiftUI views from a user's SwiftPM package to PNGs
|
|
5
|
-
* with SwiftUI's ImageRenderer on a macOS runner — no app build, no
|
|
6
|
-
* simulator. The step returns a self-contained node script that, at runtime:
|
|
7
|
-
*
|
|
8
|
-
* 1. Reads `.ci/inputs/params.json`
|
|
9
|
-
* ({ "screens": { "<ViewTypeName>": "<referenceFileBasename>" } });
|
|
10
|
-
* reference images live at `.ci/inputs/<basename>`.
|
|
11
|
-
* Optional reference-provenance sidecar: `"render_locale": "<locale>"`
|
|
12
|
-
* plus `"provenance": { "<ViewTypeName>": { figmaNode, locale, resolved,
|
|
13
|
-
* exportedAt } }`. The gate arms per screen from provenance presence and
|
|
14
|
-
* checks METADATA only (never pixels/OCR): an absent reference is FATAL
|
|
15
|
-
* (status `unverifiable`, REFERENCE_ABSENT); `resolved == false` or
|
|
16
|
-
* `locale != render_locale` downgrade ONLY the copy channel — the screen
|
|
17
|
-
* renders and is judged on every non-copy channel, and the downgrade
|
|
18
|
-
* rides as a per-screen `copy_advisory` (`copy_unverifiable` + reason).
|
|
19
|
-
* Unprovenanced screens keep legacy behaviour (no NO_PROVENANCE arming
|
|
20
|
-
* on iOS).
|
|
21
|
-
* 2. Synthesizes a throwaway SwiftPM harness package that depends on the
|
|
22
|
-
* user's package and contains ONE executable target per screen, so a
|
|
23
|
-
* compile failure for one screen (unknown view type, unavailable init)
|
|
24
|
-
* never breaks the others. A screen-free probe target distinguishes
|
|
25
|
-
* "this screen is broken" from "the whole package does not build for
|
|
26
|
-
* macOS" (RENDER_UNSUPPORTED).
|
|
27
|
-
* 3. Builds and runs each per-screen executable, which constructs the view
|
|
28
|
-
* strictly as `<ViewTypeName>()`, renders at render_size x scale on the
|
|
29
|
-
* MainActor, and writes a PNG via CGImage + ImageIO.
|
|
30
|
-
* 4. Writes, inside the artifacts dir (${CIBUILD_ARTIFACTS_DIR:-.ci/artifacts}):
|
|
31
|
-
* - ui-fidelity/rendered/<Screen>.png
|
|
32
|
-
* - ui-fidelity/references/<Screen>.png (one copy per screen, even when
|
|
33
|
-
* two screens share a reference basename)
|
|
34
|
-
* - protocol-result.json (always written, even on partial/total failure;
|
|
35
|
-
* when params.json is absent/malformed the screens array is empty
|
|
36
|
-
* because screens cannot be enumerated)
|
|
37
|
-
* 5. Exits non-zero iff any screen is not "rendered".
|
|
38
|
-
*
|
|
39
|
-
* protocol-result.json contains relative paths only: image paths are relative
|
|
40
|
-
* to the artifacts dir, and error messages are sanitized so absolute runner
|
|
41
|
-
* paths (resolved package_path, harness temp dir, extracted shipped package,
|
|
42
|
-
* compiler diagnostics) are rewritten to relative paths or placeholders
|
|
43
|
-
* before they are stored.
|
|
44
|
-
*
|
|
45
|
-
* The package_source input picks where the SwiftPM package comes from:
|
|
46
|
-
* - "repo" (default): the package lives in the checkout at package_path.
|
|
47
|
-
* Omitting the input generates a byte-identical script to the original
|
|
48
|
-
* step (locked by a snapshot test), and the result document keeps its
|
|
49
|
-
* original { renderer, screens } shape.
|
|
50
|
-
* - "inputs": the caller ships the package as .ci/inputs/package.tar.gz.
|
|
51
|
-
* The archive is extracted into a scratch directory and validated
|
|
52
|
-
* (single package root, Package.swift at its top, bounded extracted
|
|
53
|
-
* size); rendering then proceeds exactly as in repo mode. Validation
|
|
54
|
-
* failures are per-build structured errors (PACKAGE_ARCHIVE_MISSING,
|
|
55
|
-
* PACKAGE_ARCHIVE_INVALID, PACKAGE_MANIFEST_MISSING,
|
|
56
|
-
* PACKAGE_ARCHIVE_TOO_LARGE, PACKAGE_TARGET_UNRESOLVED) recorded as a
|
|
57
|
-
* top-level error object in the result document, distinct from
|
|
58
|
-
* per-screen render errors. With an explicit package_source the result
|
|
59
|
-
* document is { renderer, package_source, error, screens }.
|
|
60
|
-
*/
|
|
61
|
-
import { BaseStepExecutor } from './base.js';
|
|
62
|
-
import { RENDER_SCRIPT_TRIM_STAGE } from './render-post-processor.js';
|
|
63
|
-
import { RENDER_SCRIPT_SOURCE_DIGEST_STAGE } from '../fidelity-input-digest.js';
|
|
64
|
-
/**
|
|
65
|
-
* Default render size in device points. 393x852 is the iPhone-class portrait
|
|
66
|
-
* canvas shared by the iPhone 14 Pro / 15 / 16 — the most common modern
|
|
67
|
-
* iPhone logical resolution, so renders line up with design references by
|
|
68
|
-
* default.
|
|
69
|
-
*/
|
|
70
|
-
export const DEFAULT_RENDER_SIZE = '393x852';
|
|
71
|
-
/** Default display scale (@2x), matching how references are typically exported. */
|
|
72
|
-
export const DEFAULT_SCALE = 2;
|
|
73
|
-
/** Valid values for the package_source input. */
|
|
74
|
-
export const PACKAGE_SOURCES = ['repo', 'inputs', 'url'];
|
|
75
|
-
/** Default package source: the package lives in the repo checkout. */
|
|
76
|
-
export const DEFAULT_PACKAGE_SOURCE = 'repo';
|
|
77
|
-
/** Valid values for the reference_source input. */
|
|
78
|
-
export const REFERENCE_SOURCES = ['repo', 'inputs'];
|
|
79
|
-
/** Default reference source: references arrive as run inputs (v1 behaviour). */
|
|
80
|
-
export const DEFAULT_REFERENCE_SOURCE = 'inputs';
|
|
81
|
-
/** Basename of the shipped package archive inside the run-inputs directory. */
|
|
82
|
-
export const PACKAGE_ARCHIVE_BASENAME = 'package.tar.gz';
|
|
83
|
-
/**
|
|
84
|
-
* Default cap on the extracted size of a shipped package archive (32 MiB),
|
|
85
|
-
* overridable at runtime via UI_FIDELITY_MAX_PACKAGE_BYTES. The archive
|
|
86
|
-
* arrives through a size-limited dispatch channel (256 KiB), and gzip tops
|
|
87
|
-
* out around a 1000:1 ratio, so this bound is a decompression-bomb guard
|
|
88
|
-
* with enormous headroom for legitimate source slices, which are typically
|
|
89
|
-
* well under 1 MiB extracted.
|
|
90
|
-
*/
|
|
91
|
-
export const DEFAULT_MAX_EXTRACTED_BYTES = 32 * 1024 * 1024;
|
|
92
|
-
/**
|
|
93
|
-
* Parses a "<width>x<height>" render size string (device points).
|
|
94
|
-
* @throws Error when the value is malformed or non-positive
|
|
95
|
-
*/
|
|
96
|
-
export function parseRenderSize(value) {
|
|
97
|
-
const match = /^(\d+)x(\d+)$/.exec(String(value).trim());
|
|
98
|
-
const width = match ? Number(match[1]) : 0;
|
|
99
|
-
const height = match ? Number(match[2]) : 0;
|
|
100
|
-
if (!match || width <= 0 || height <= 0) {
|
|
101
|
-
throw new Error(`Invalid render_size '${value}' for step 'ui-fidelity-render': ` +
|
|
102
|
-
`expected "<width>x<height>" in device points, e.g. "${DEFAULT_RENDER_SIZE}"`);
|
|
103
|
-
}
|
|
104
|
-
return { width, height };
|
|
105
|
-
}
|
|
106
|
-
/**
|
|
107
|
-
* Parses the scale input into a positive finite number.
|
|
108
|
-
* @throws Error when the value is not a positive number
|
|
109
|
-
*/
|
|
110
|
-
export function parseScale(value) {
|
|
111
|
-
const scale = typeof value === 'number' ? value : Number(String(value).trim());
|
|
112
|
-
if (!Number.isFinite(scale) || scale <= 0) {
|
|
113
|
-
throw new Error(`Invalid scale '${value}' for step 'ui-fidelity-render': expected a positive number`);
|
|
114
|
-
}
|
|
115
|
-
return scale;
|
|
116
|
-
}
|
|
117
|
-
/**
|
|
118
|
-
* Swift source generators, written as plain dependency-free JS so the exact
|
|
119
|
-
* same code is embedded into the runtime node script AND directly evaluable
|
|
120
|
-
* in unit tests (see getRenderScriptInternals). String.raw keeps escape
|
|
121
|
-
* sequences literal; the body must not contain backticks or "${".
|
|
122
|
-
*/
|
|
123
|
-
const RENDER_SCRIPT_GENERATORS = String.raw `
|
|
124
|
-
// ---- Swift source generation (pure string builders) ----
|
|
125
|
-
|
|
126
|
-
function swiftStringLiteral(value) {
|
|
127
|
-
var out = '"';
|
|
128
|
-
var s = String(value);
|
|
129
|
-
for (var i = 0; i < s.length; i++) {
|
|
130
|
-
var ch = s[i];
|
|
131
|
-
var code = s.charCodeAt(i);
|
|
132
|
-
if (ch === '\\') out += '\\\\';
|
|
133
|
-
else if (ch === '"') out += '\\"';
|
|
134
|
-
else if (ch === '\n') out += '\\n';
|
|
135
|
-
else if (ch === '\r') out += '\\r';
|
|
136
|
-
else if (ch === '\t') out += '\\t';
|
|
137
|
-
else if (code < 0x20) out += '\\u{' + code.toString(16) + '}';
|
|
138
|
-
else out += ch;
|
|
139
|
-
}
|
|
140
|
-
return out + '"';
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// One executable target (and product) per screen: SwiftPM compiles the user
|
|
144
|
-
// package once, while a per-screen harness compile failure stays isolated to
|
|
145
|
-
// that screen. RenderProbe imports the user package without referencing any
|
|
146
|
-
// screen, so its failure means the package itself does not build for macOS.
|
|
147
|
-
function generateHarnessPackageSwift(screens, options) {
|
|
148
|
-
var dependency =
|
|
149
|
-
'.product(name: ' + swiftStringLiteral(options.target) +
|
|
150
|
-
', package: ' + swiftStringLiteral(options.packageRef) + ')';
|
|
151
|
-
var lines = [];
|
|
152
|
-
lines.push('// swift-tools-version:5.9');
|
|
153
|
-
lines.push('import PackageDescription');
|
|
154
|
-
lines.push('');
|
|
155
|
-
lines.push('let package = Package(');
|
|
156
|
-
lines.push(' name: "UIFidelityRenderHarness",');
|
|
157
|
-
lines.push(' platforms: [.macOS(.v13)],');
|
|
158
|
-
lines.push(' products: [');
|
|
159
|
-
screens.forEach(function (screen) {
|
|
160
|
-
lines.push(
|
|
161
|
-
' .executable(name: "Render_' + screen +
|
|
162
|
-
'", targets: ["Render_' + screen + '"]),'
|
|
163
|
-
);
|
|
164
|
-
});
|
|
165
|
-
lines.push(' ],');
|
|
166
|
-
lines.push(' dependencies: [');
|
|
167
|
-
lines.push(' .package(path: ' + swiftStringLiteral(options.packagePath) + ')');
|
|
168
|
-
lines.push(' ],');
|
|
169
|
-
lines.push(' targets: [');
|
|
170
|
-
lines.push(' .executableTarget(name: "RenderProbe", dependencies: [' + dependency + ']),');
|
|
171
|
-
screens.forEach(function (screen) {
|
|
172
|
-
lines.push(
|
|
173
|
-
' .executableTarget(name: "Render_' + screen +
|
|
174
|
-
'", dependencies: [' + dependency + ']),'
|
|
175
|
-
);
|
|
176
|
-
});
|
|
177
|
-
lines.push(' ]');
|
|
178
|
-
lines.push(')');
|
|
179
|
-
return lines.join('\n') + '\n';
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
function generateProbeSwift(options) {
|
|
183
|
-
var lines = [];
|
|
184
|
-
lines.push('import ' + options.target);
|
|
185
|
-
lines.push('');
|
|
186
|
-
lines.push('@main');
|
|
187
|
-
lines.push('struct Probe {');
|
|
188
|
-
lines.push(' static func main() {}');
|
|
189
|
-
lines.push('}');
|
|
190
|
-
return lines.join('\n') + '\n';
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// ImageRenderer is MainActor-isolated, so the @main entry point runs on the
|
|
194
|
-
// MainActor. The view is constructed strictly via the parameterless init.
|
|
195
|
-
function generateScreenSwift(screen, options) {
|
|
196
|
-
var lines = [];
|
|
197
|
-
lines.push('import Foundation');
|
|
198
|
-
lines.push('import ImageIO');
|
|
199
|
-
lines.push('import SwiftUI');
|
|
200
|
-
lines.push('import UniformTypeIdentifiers');
|
|
201
|
-
lines.push('import ' + options.target);
|
|
202
|
-
lines.push('');
|
|
203
|
-
lines.push('@main');
|
|
204
|
-
lines.push('struct RenderEntry {');
|
|
205
|
-
lines.push(' @MainActor');
|
|
206
|
-
lines.push(' static func main() {');
|
|
207
|
-
lines.push(' let arguments = CommandLine.arguments');
|
|
208
|
-
lines.push(' guard arguments.count >= 2 else {');
|
|
209
|
-
lines.push(' FileHandle.standardError.write(Data("usage: Render_' + screen + ' <output.png>".utf8))');
|
|
210
|
-
lines.push(' exit(64)');
|
|
211
|
-
lines.push(' }');
|
|
212
|
-
lines.push(' let outputPath = arguments[1]');
|
|
213
|
-
lines.push(' let width: CGFloat = ' + options.width);
|
|
214
|
-
lines.push(' let height: CGFloat = ' + options.height);
|
|
215
|
-
lines.push(' let view = ' + screen + '().frame(width: width, height: height)');
|
|
216
|
-
lines.push(' let renderer = ImageRenderer(content: view)');
|
|
217
|
-
lines.push(' renderer.scale = ' + options.scale);
|
|
218
|
-
lines.push(' renderer.proposedSize = ProposedViewSize(width: width, height: height)');
|
|
219
|
-
lines.push(' guard let image = renderer.cgImage else {');
|
|
220
|
-
lines.push(' FileHandle.standardError.write(Data("ImageRenderer produced no image for ' + screen + '".utf8))');
|
|
221
|
-
lines.push(' exit(65)');
|
|
222
|
-
lines.push(' }');
|
|
223
|
-
lines.push(' let url = URL(fileURLWithPath: outputPath)');
|
|
224
|
-
lines.push(' guard let destination = CGImageDestinationCreateWithURL(url as CFURL, UTType.png.identifier as CFString, 1, nil) else {');
|
|
225
|
-
lines.push(' FileHandle.standardError.write(Data(("could not create PNG destination at " + outputPath).utf8))');
|
|
226
|
-
lines.push(' exit(66)');
|
|
227
|
-
lines.push(' }');
|
|
228
|
-
lines.push(' CGImageDestinationAddImage(destination, image, nil)');
|
|
229
|
-
lines.push(' guard CGImageDestinationFinalize(destination) else {');
|
|
230
|
-
lines.push(' FileHandle.standardError.write(Data(("could not write PNG to " + outputPath).utf8))');
|
|
231
|
-
lines.push(' exit(66)');
|
|
232
|
-
lines.push(' }');
|
|
233
|
-
lines.push(' }');
|
|
234
|
-
lines.push('}');
|
|
235
|
-
return lines.join('\n') + '\n';
|
|
236
|
-
}
|
|
237
|
-
`;
|
|
238
|
-
/**
|
|
239
|
-
* Runtime body of the generated node script. Plain dependency-free CJS
|
|
240
|
-
* (the runner executes it via "node -e"). Must not contain backticks or
|
|
241
|
-
* "${" — String.raw keeps escape sequences literal.
|
|
242
|
-
*/
|
|
243
|
-
const RENDER_SCRIPT_MAIN = String.raw `
|
|
244
|
-
// ---- runtime ----
|
|
245
|
-
|
|
246
|
-
var fs = require('fs');
|
|
247
|
-
var path = require('path');
|
|
248
|
-
var os = require('os');
|
|
249
|
-
var cp = require('child_process');
|
|
250
|
-
|
|
251
|
-
var RENDERER = 'imagerenderer-spm';
|
|
252
|
-
var SWIFT_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
253
|
-
|
|
254
|
-
var artifactsDir = process.env.CIBUILD_ARTIFACTS_DIR || '.ci/artifacts';
|
|
255
|
-
var inputsDir = path.join('.ci', 'inputs');
|
|
256
|
-
var paramsPath = path.join(inputsDir, 'params.json');
|
|
257
|
-
var resultPath = path.join(artifactsDir, 'protocol-result.json');
|
|
258
|
-
|
|
259
|
-
var currentEntries = [];
|
|
260
|
-
|
|
261
|
-
function log(message) {
|
|
262
|
-
console.log('[ui-fidelity-render] ' + message);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
function logError(message) {
|
|
266
|
-
console.error('[ui-fidelity-render] ' + message);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// ---- result-document path hygiene ----
|
|
270
|
-
//
|
|
271
|
-
// protocol-result.json must contain relative paths only. Error messages can
|
|
272
|
-
// embed absolute runner paths (resolved package_path, harness temp dir,
|
|
273
|
-
// swift compiler diagnostics, copy failures), so every message stored in the
|
|
274
|
-
// result document is passed through sanitizeMessage(), which rewrites each
|
|
275
|
-
// known absolute directory -- and its symlink-resolved variant, since macOS
|
|
276
|
-
// tools print /var/folders/... as /private/var/folders/... -- to a relative
|
|
277
|
-
// path or a stable placeholder.
|
|
278
|
-
|
|
279
|
-
var pathReplacements = [];
|
|
280
|
-
|
|
281
|
-
function registerPathReplacement(absolutePath, replacement) {
|
|
282
|
-
if (!absolutePath || !path.isAbsolute(absolutePath)) return;
|
|
283
|
-
pathReplacements.push([absolutePath, replacement]);
|
|
284
|
-
try {
|
|
285
|
-
var real = fs.realpathSync(absolutePath);
|
|
286
|
-
if (real !== absolutePath) pathReplacements.push([real, replacement]);
|
|
287
|
-
} catch (realpathError) {
|
|
288
|
-
// path does not exist (yet) -- keep the literal form only
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
function sanitizeMessage(message) {
|
|
293
|
-
var sanitized = String(message);
|
|
294
|
-
pathReplacements
|
|
295
|
-
.slice()
|
|
296
|
-
.sort(function (a, b) { return b[0].length - a[0].length; })
|
|
297
|
-
.forEach(function (pair) {
|
|
298
|
-
sanitized = sanitized.split(pair[0]).join(pair[1]);
|
|
299
|
-
});
|
|
300
|
-
return sanitized;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
var resolvedPackagePath = path.resolve(CONFIG.packagePath);
|
|
304
|
-
registerPathReplacement(
|
|
305
|
-
resolvedPackagePath,
|
|
306
|
-
path.isAbsolute(CONFIG.packagePath) ? '<package_path>' : CONFIG.packagePath
|
|
307
|
-
);
|
|
308
|
-
registerPathReplacement(
|
|
309
|
-
path.resolve(artifactsDir),
|
|
310
|
-
path.isAbsolute(artifactsDir) ? '<artifacts>' : artifactsDir
|
|
311
|
-
);
|
|
312
|
-
registerPathReplacement(process.cwd(), '.');
|
|
313
|
-
registerPathReplacement(os.tmpdir(), '<tmp>');
|
|
314
|
-
|
|
315
|
-
// protocol-result.json is ALWAYS written, with one entry per screen in
|
|
316
|
-
// params.json order, even on partial or total failure. All paths inside it
|
|
317
|
-
// are relative to the artifacts dir.
|
|
318
|
-
function writeResult() {
|
|
319
|
-
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
320
|
-
var doc = { renderer: RENDERER, screens: currentEntries };
|
|
321
|
-
fs.writeFileSync(resultPath, JSON.stringify(doc, null, 2) + '\n');
|
|
322
|
-
log('wrote ' + resultPath);
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
function setError(entry, code, message) {
|
|
326
|
-
entry.status = 'render_failed';
|
|
327
|
-
entry.error = { code: code, message: sanitizeMessage(message) };
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// A reference the gate cannot honestly judge at all: a hard, mechanical
|
|
331
|
-
// metadata verdict (never pixels/OCR), distinct from render_failed so a rotten
|
|
332
|
-
// reference fails loud instead of producing a confident-but-false PASS.
|
|
333
|
-
function setUnverifiable(entry, code, message) {
|
|
334
|
-
entry.status = 'unverifiable';
|
|
335
|
-
entry.error = { code: code, message: sanitizeMessage(message) };
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Inlined twins of src/yaml/steps/fidelity-reference-provenance.ts (this script
|
|
339
|
-
// ships self-contained and cannot import it). The render-step integration tests
|
|
340
|
-
// exercise BOTH against the same fixtures so they cannot silently drift. The
|
|
341
|
-
// iOS gate arms ONLY from provenance presence — the step never refused
|
|
342
|
-
// unprovenanced screens, so NO_PROVENANCE (an Android-legacy arming via
|
|
343
|
-
// render_locale) is deliberately not wired here.
|
|
344
|
-
|
|
345
|
-
// Provenance is only "present" when the fields the gate reasons over are there.
|
|
346
|
-
// A partial object (e.g. a hand-dropped { figmaNode }) is treated as no
|
|
347
|
-
// provenance — verifiable is earned only through the full export path.
|
|
348
|
-
function hasReferenceProvenance(provenance) {
|
|
349
|
-
return (
|
|
350
|
-
provenance != null &&
|
|
351
|
-
typeof provenance === 'object' &&
|
|
352
|
-
typeof provenance.locale === 'string' &&
|
|
353
|
-
typeof provenance.resolved === 'boolean'
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
// Assesses one provenanced screen's reference. Returns { fault, copyAdvisory }:
|
|
358
|
-
// a fatal fault makes the screen unverifiable; a copy advisory downgrades only
|
|
359
|
-
// the copy channel (the screen still renders and is judged on every non-copy
|
|
360
|
-
// channel). An absent reference wins over a downgrade — there is nothing to
|
|
361
|
-
// judge against.
|
|
362
|
-
function assessReferenceProvenance(screen, provenance, renderLocale, referenceExists) {
|
|
363
|
-
if (!referenceExists) {
|
|
364
|
-
return {
|
|
365
|
-
fault: {
|
|
366
|
-
code: 'REFERENCE_ABSENT',
|
|
367
|
-
message: 'reference image for ' + screen + ' is absent',
|
|
368
|
-
},
|
|
369
|
-
copyAdvisory: null,
|
|
370
|
-
};
|
|
371
|
-
}
|
|
372
|
-
if (provenance.resolved === false) {
|
|
373
|
-
return {
|
|
374
|
-
fault: null,
|
|
375
|
-
copyAdvisory: {
|
|
376
|
-
code: 'copy_unverifiable',
|
|
377
|
-
reason: 'unresolved_placeholders',
|
|
378
|
-
message:
|
|
379
|
-
'reference for ' + screen + ' carries unresolved copy placeholders ' +
|
|
380
|
-
'(resolved=false); copy comparisons are skipped — non-copy judging ' +
|
|
381
|
-
'still holds',
|
|
382
|
-
},
|
|
383
|
-
};
|
|
384
|
-
}
|
|
385
|
-
if (renderLocale !== null && provenance.locale !== renderLocale) {
|
|
386
|
-
return {
|
|
387
|
-
fault: null,
|
|
388
|
-
copyAdvisory: {
|
|
389
|
-
code: 'copy_unverifiable',
|
|
390
|
-
reason: 'locale_mismatch',
|
|
391
|
-
message:
|
|
392
|
-
'reference for ' + screen + ' is locale "' + provenance.locale +
|
|
393
|
-
'" but the render locale is "' + renderLocale + '"; copy comparisons ' +
|
|
394
|
-
'are skipped — non-copy judging still holds',
|
|
395
|
-
},
|
|
396
|
-
};
|
|
397
|
-
}
|
|
398
|
-
return { fault: null, copyAdvisory: null };
|
|
399
|
-
}
|
|
400
|
-
|
|
401
|
-
function outputTail(result) {
|
|
402
|
-
var combined = ((result.stdout || '') + '\n' + (result.stderr || '')).trim();
|
|
403
|
-
if (combined.length > 2000) combined = '...' + combined.slice(-2000);
|
|
404
|
-
return combined;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
function runSwift(args) {
|
|
408
|
-
log('swift ' + args.join(' '));
|
|
409
|
-
var result = cp.spawnSync('swift', args, {
|
|
410
|
-
encoding: 'utf-8',
|
|
411
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
412
|
-
});
|
|
413
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
414
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
415
|
-
if (result.error) {
|
|
416
|
-
result.status = result.status == null ? 127 : result.status;
|
|
417
|
-
result.stderr =
|
|
418
|
-
(result.stderr || '') + String(result.error.message || result.error);
|
|
419
|
-
}
|
|
420
|
-
return result;
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
function renderWithHarness(renderable, packagePath) {
|
|
424
|
-
// Snapshot the user package's input files BEFORE any Swift build, so build
|
|
425
|
-
// products (.build, DerivedData) never perturb the content key. A package is
|
|
426
|
-
// safely cacheable only when it is hermetic — its own files are its whole
|
|
427
|
-
// dependency closure; a non-hermetic one emits no digest and so is never
|
|
428
|
-
// skipped by a consumer.
|
|
429
|
-
var packageFiles = __fidCollectPackageFiles(packagePath);
|
|
430
|
-
var packageHermetic = __fidIsHermetic(packageFiles);
|
|
431
|
-
var harnessDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-fidelity-harness-'));
|
|
432
|
-
registerPathReplacement(harnessDir, '<harness>');
|
|
433
|
-
log('harness package: ' + harnessDir);
|
|
434
|
-
try {
|
|
435
|
-
var screens = renderable.map(function (entry) { return entry.screen; });
|
|
436
|
-
var generatorOptions = {
|
|
437
|
-
packagePath: packagePath,
|
|
438
|
-
packageRef: path.basename(packagePath),
|
|
439
|
-
target: CONFIG.target,
|
|
440
|
-
width: CONFIG.width,
|
|
441
|
-
height: CONFIG.height,
|
|
442
|
-
scale: CONFIG.scale,
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
fs.writeFileSync(
|
|
446
|
-
path.join(harnessDir, 'Package.swift'),
|
|
447
|
-
generateHarnessPackageSwift(screens, generatorOptions)
|
|
448
|
-
);
|
|
449
|
-
var probeDir = path.join(harnessDir, 'Sources', 'RenderProbe');
|
|
450
|
-
fs.mkdirSync(probeDir, { recursive: true });
|
|
451
|
-
fs.writeFileSync(path.join(probeDir, 'Probe.swift'), generateProbeSwift(generatorOptions));
|
|
452
|
-
screens.forEach(function (screen) {
|
|
453
|
-
var sourceDir = path.join(harnessDir, 'Sources', 'Render_' + screen);
|
|
454
|
-
fs.mkdirSync(sourceDir, { recursive: true });
|
|
455
|
-
fs.writeFileSync(
|
|
456
|
-
path.join(sourceDir, 'Render.swift'),
|
|
457
|
-
generateScreenSwift(screen, generatorOptions)
|
|
458
|
-
);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// Harness-level gate: the probe imports the user package without touching
|
|
462
|
-
// any screen. If it fails, the package does not resolve/compile for
|
|
463
|
-
// macOS and EVERY screen is marked RENDER_UNSUPPORTED.
|
|
464
|
-
var probe = runSwift(['build', '--package-path', harnessDir, '--target', 'RenderProbe']);
|
|
465
|
-
if (probe.status !== 0) {
|
|
466
|
-
var probeDetail = outputTail(probe);
|
|
467
|
-
currentEntries.forEach(function (entry) {
|
|
468
|
-
setError(
|
|
469
|
-
entry,
|
|
470
|
-
'RENDER_UNSUPPORTED',
|
|
471
|
-
'the package at ' + packagePath + ' does not build for macOS:\n' + probeDetail
|
|
472
|
-
);
|
|
473
|
-
});
|
|
474
|
-
return;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
renderable.forEach(function (entry) {
|
|
478
|
-
var product = 'Render_' + entry.screen;
|
|
479
|
-
var build = runSwift(['build', '--package-path', harnessDir, '--product', product]);
|
|
480
|
-
if (build.status !== 0) {
|
|
481
|
-
setError(
|
|
482
|
-
entry,
|
|
483
|
-
'VIEW_COMPILE_FAILED',
|
|
484
|
-
'harness for ' + entry.screen + ' did not compile (is ' + entry.screen +
|
|
485
|
-
' a public View with a parameterless init in ' + CONFIG.target + '?):\n' +
|
|
486
|
-
outputTail(build)
|
|
487
|
-
);
|
|
488
|
-
return;
|
|
489
|
-
}
|
|
490
|
-
var relative = 'ui-fidelity/rendered/' + entry.screen + '.png';
|
|
491
|
-
var outputPath = path.resolve(artifactsDir, relative);
|
|
492
|
-
var run = runSwift(['run', '--package-path', harnessDir, '--skip-build', product, outputPath]);
|
|
493
|
-
if (run.status !== 0) {
|
|
494
|
-
setError(
|
|
495
|
-
entry,
|
|
496
|
-
'RENDER_FAILED',
|
|
497
|
-
'renderer for ' + entry.screen + ' exited with code ' + run.status + ':\n' + outputTail(run)
|
|
498
|
-
);
|
|
499
|
-
return;
|
|
500
|
-
}
|
|
501
|
-
if (!fs.existsSync(outputPath)) {
|
|
502
|
-
setError(
|
|
503
|
-
entry,
|
|
504
|
-
'RENDER_FAILED',
|
|
505
|
-
'renderer for ' + entry.screen + ' exited 0 but produced no PNG at ' + outputPath
|
|
506
|
-
);
|
|
507
|
-
return;
|
|
508
|
-
}
|
|
509
|
-
entry.rendered_image_path = relative;
|
|
510
|
-
entry.status = 'rendered';
|
|
511
|
-
entry.error = null;
|
|
512
|
-
// Content key over the package source + this screen's reference: lets a
|
|
513
|
-
// consumer skip a re-render whose inputs are unchanged. Emitted only for a
|
|
514
|
-
// hermetic package; otherwise omitted so no stale verdict can be replayed.
|
|
515
|
-
if (packageHermetic) {
|
|
516
|
-
var referenceBytes = null;
|
|
517
|
-
try {
|
|
518
|
-
if (entry.reference_image_path) {
|
|
519
|
-
referenceBytes = fs.readFileSync(path.resolve(artifactsDir, entry.reference_image_path));
|
|
520
|
-
}
|
|
521
|
-
} catch (digestReferenceError) {
|
|
522
|
-
referenceBytes = null;
|
|
523
|
-
}
|
|
524
|
-
entry.source_digest = __fidSourceDigest(packageFiles, referenceBytes);
|
|
525
|
-
}
|
|
526
|
-
log('rendered ' + entry.screen + ' -> ' + relative);
|
|
527
|
-
});
|
|
528
|
-
} finally {
|
|
529
|
-
if (process.env.UI_FIDELITY_KEEP_HARNESS) {
|
|
530
|
-
log('keeping harness package (UI_FIDELITY_KEEP_HARNESS set)');
|
|
531
|
-
} else {
|
|
532
|
-
try {
|
|
533
|
-
fs.rmSync(harnessDir, { recursive: true, force: true });
|
|
534
|
-
} catch (cleanupError) {
|
|
535
|
-
// best effort
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
function main() {
|
|
542
|
-
// 1. Read and validate .ci/inputs/params.json.
|
|
543
|
-
var paramsProblem = null;
|
|
544
|
-
var params = null;
|
|
545
|
-
if (!fs.existsSync(paramsPath)) {
|
|
546
|
-
paramsProblem = 'not found at ' + paramsPath;
|
|
547
|
-
} else {
|
|
548
|
-
try {
|
|
549
|
-
params = JSON.parse(fs.readFileSync(paramsPath, 'utf-8'));
|
|
550
|
-
} catch (parseError) {
|
|
551
|
-
paramsProblem = 'is not valid JSON: ' +
|
|
552
|
-
(parseError && parseError.message ? parseError.message : String(parseError));
|
|
553
|
-
}
|
|
554
|
-
}
|
|
555
|
-
if (!paramsProblem) {
|
|
556
|
-
if (
|
|
557
|
-
!params || typeof params !== 'object' || Array.isArray(params) ||
|
|
558
|
-
!params.screens || typeof params.screens !== 'object' || Array.isArray(params.screens)
|
|
559
|
-
) {
|
|
560
|
-
paramsProblem =
|
|
561
|
-
'must have the shape { "screens": { "<ViewTypeName>": "<referenceFileBasename>" } }';
|
|
562
|
-
}
|
|
563
|
-
}
|
|
564
|
-
if (paramsProblem) {
|
|
565
|
-
// Screens cannot be enumerated, so the result document is written with
|
|
566
|
-
// an empty screens array and the step fails.
|
|
567
|
-
logError('params.json ' + paramsProblem);
|
|
568
|
-
writeResult();
|
|
569
|
-
return 1;
|
|
570
|
-
}
|
|
571
|
-
|
|
572
|
-
// Reference-provenance sidecar (optional): per-screen export provenance plus
|
|
573
|
-
// the run's declared render locale. The gate arms per screen from provenance
|
|
574
|
-
// presence; the locale is only needed for the locale comparison itself.
|
|
575
|
-
var renderLocale =
|
|
576
|
-
typeof params.render_locale === 'string' && params.render_locale !== ''
|
|
577
|
-
? params.render_locale
|
|
578
|
-
: null;
|
|
579
|
-
var provenanceMap =
|
|
580
|
-
params.provenance && typeof params.provenance === 'object' && !Array.isArray(params.provenance)
|
|
581
|
-
? params.provenance
|
|
582
|
-
: {};
|
|
583
|
-
|
|
584
|
-
var screens = Object.keys(params.screens);
|
|
585
|
-
currentEntries = screens.map(function (screen) {
|
|
586
|
-
return {
|
|
587
|
-
screen: screen,
|
|
588
|
-
status: 'render_failed',
|
|
589
|
-
error: null,
|
|
590
|
-
copy_advisory: null,
|
|
591
|
-
reference_image_path: null,
|
|
592
|
-
rendered_image_path: null,
|
|
593
|
-
};
|
|
594
|
-
});
|
|
595
|
-
log('screens: ' + (screens.join(', ') || '(none)'));
|
|
596
|
-
|
|
597
|
-
fs.mkdirSync(path.join(artifactsDir, 'ui-fidelity', 'rendered'), { recursive: true });
|
|
598
|
-
fs.mkdirSync(path.join(artifactsDir, 'ui-fidelity', 'references'), { recursive: true });
|
|
599
|
-
|
|
600
|
-
// 2. Per-screen validation + reference copies. Each screen gets its OWN
|
|
601
|
-
// copy named by screen, even when two screens share a reference
|
|
602
|
-
// basename.
|
|
603
|
-
currentEntries.forEach(function (entry) {
|
|
604
|
-
if (!SWIFT_IDENTIFIER.test(entry.screen)) {
|
|
605
|
-
setError(
|
|
606
|
-
entry,
|
|
607
|
-
'INVALID_SCREEN_NAME',
|
|
608
|
-
'screen name ' + JSON.stringify(entry.screen) + ' is not a valid Swift type identifier'
|
|
609
|
-
);
|
|
610
|
-
return;
|
|
611
|
-
}
|
|
612
|
-
var basename = params.screens[entry.screen];
|
|
613
|
-
if (
|
|
614
|
-
typeof basename !== 'string' || basename === '' ||
|
|
615
|
-
basename === '.' || basename === '..' ||
|
|
616
|
-
basename !== path.basename(basename)
|
|
617
|
-
) {
|
|
618
|
-
setError(
|
|
619
|
-
entry,
|
|
620
|
-
'REFERENCE_MISSING',
|
|
621
|
-
'reference for ' + entry.screen + ' must be a plain file basename inside ' +
|
|
622
|
-
inputsDir + ', got: ' + JSON.stringify(basename)
|
|
623
|
-
);
|
|
624
|
-
return;
|
|
625
|
-
}
|
|
626
|
-
var source = path.join(inputsDir, basename);
|
|
627
|
-
var sourceStat = null;
|
|
628
|
-
try {
|
|
629
|
-
sourceStat = fs.statSync(source);
|
|
630
|
-
} catch (statError) {
|
|
631
|
-
sourceStat = null;
|
|
632
|
-
}
|
|
633
|
-
var referenceExists = !!(sourceStat && sourceStat.isFile());
|
|
634
|
-
|
|
635
|
-
var screenProvenance = provenanceMap[entry.screen];
|
|
636
|
-
if (hasReferenceProvenance(screenProvenance)) {
|
|
637
|
-
// Provenance assessment (armed by provenance presence): an absent
|
|
638
|
-
// reference is FATAL (unverifiable — never a silent PASS); unresolved
|
|
639
|
-
// placeholders and locale mismatches downgrade ONLY the copy channel —
|
|
640
|
-
// the screen renders and is judged on every non-copy channel, and the
|
|
641
|
-
// downgrade rides in the result as a structured copy_advisory.
|
|
642
|
-
var assessment = assessReferenceProvenance(
|
|
643
|
-
entry.screen,
|
|
644
|
-
screenProvenance,
|
|
645
|
-
renderLocale,
|
|
646
|
-
referenceExists
|
|
647
|
-
);
|
|
648
|
-
if (assessment.fault) {
|
|
649
|
-
setUnverifiable(entry, assessment.fault.code, assessment.fault.message);
|
|
650
|
-
return;
|
|
651
|
-
}
|
|
652
|
-
if (assessment.copyAdvisory) {
|
|
653
|
-
entry.copy_advisory = {
|
|
654
|
-
code: assessment.copyAdvisory.code,
|
|
655
|
-
reason: assessment.copyAdvisory.reason,
|
|
656
|
-
message: sanitizeMessage(assessment.copyAdvisory.message),
|
|
657
|
-
};
|
|
658
|
-
log(
|
|
659
|
-
entry.screen + ': copy channel downgraded (' +
|
|
660
|
-
assessment.copyAdvisory.reason + '), non-copy judging proceeds'
|
|
661
|
-
);
|
|
662
|
-
}
|
|
663
|
-
} else if (!referenceExists) {
|
|
664
|
-
// Legacy (no provenance for this screen): an absent reference stays a
|
|
665
|
-
// render_failed REFERENCE_MISSING, exactly as before.
|
|
666
|
-
setError(
|
|
667
|
-
entry,
|
|
668
|
-
'REFERENCE_MISSING',
|
|
669
|
-
sourceStat
|
|
670
|
-
? 'reference for ' + entry.screen + ' is not a regular file: ' + source
|
|
671
|
-
: 'reference image not found: ' + source
|
|
672
|
-
);
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
var relative = 'ui-fidelity/references/' + entry.screen + '.png';
|
|
676
|
-
// The copy is guarded per screen so an unexpected filesystem failure
|
|
677
|
-
// never escapes this forEach and takes the other screens down with it.
|
|
678
|
-
try {
|
|
679
|
-
fs.copyFileSync(source, path.join(artifactsDir, relative));
|
|
680
|
-
} catch (copyError) {
|
|
681
|
-
setError(
|
|
682
|
-
entry,
|
|
683
|
-
'REFERENCE_MISSING',
|
|
684
|
-
'could not copy reference for ' + entry.screen + ': ' +
|
|
685
|
-
(copyError && copyError.message ? copyError.message : String(copyError))
|
|
686
|
-
);
|
|
687
|
-
return;
|
|
688
|
-
}
|
|
689
|
-
entry.reference_image_path = relative;
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
// 3. Render the remaining screens through the synthesized harness.
|
|
693
|
-
var renderable = currentEntries.filter(function (entry) { return entry.error === null; });
|
|
694
|
-
if (renderable.length > 0) {
|
|
695
|
-
if (!fs.existsSync(resolvedPackagePath)) {
|
|
696
|
-
currentEntries.forEach(function (entry) {
|
|
697
|
-
setError(entry, 'RENDER_UNSUPPORTED', 'package_path does not exist: ' + resolvedPackagePath);
|
|
698
|
-
});
|
|
699
|
-
} else {
|
|
700
|
-
renderWithHarness(renderable, resolvedPackagePath);
|
|
701
|
-
}
|
|
702
|
-
} else if (currentEntries.length > 0) {
|
|
703
|
-
log('no renderable screens, skipping harness build');
|
|
704
|
-
}
|
|
705
|
-
|
|
706
|
-
// 4. Exit code: zero only when every screen rendered.
|
|
707
|
-
writeResult();
|
|
708
|
-
var failed = currentEntries.filter(function (entry) { return entry.status !== 'rendered'; });
|
|
709
|
-
if (failed.length > 0) {
|
|
710
|
-
logError(failed.length + ' of ' + currentEntries.length + ' screen(s) not rendered');
|
|
711
|
-
return 1;
|
|
712
|
-
}
|
|
713
|
-
log('all ' + currentEntries.length + ' screen(s) rendered');
|
|
714
|
-
return 0;
|
|
715
|
-
}
|
|
716
|
-
|
|
717
|
-
var exitCode = 1;
|
|
718
|
-
try {
|
|
719
|
-
exitCode = main();
|
|
720
|
-
} catch (unexpectedError) {
|
|
721
|
-
logError(
|
|
722
|
-
'unexpected failure: ' +
|
|
723
|
-
(unexpectedError && unexpectedError.stack ? unexpectedError.stack : String(unexpectedError))
|
|
724
|
-
);
|
|
725
|
-
try {
|
|
726
|
-
currentEntries.forEach(function (entry) {
|
|
727
|
-
if (entry.status !== 'rendered' && entry.error === null) {
|
|
728
|
-
setError(
|
|
729
|
-
entry,
|
|
730
|
-
'INTERNAL_ERROR',
|
|
731
|
-
String(unexpectedError && unexpectedError.message ? unexpectedError.message : unexpectedError)
|
|
732
|
-
);
|
|
733
|
-
}
|
|
734
|
-
});
|
|
735
|
-
writeResult();
|
|
736
|
-
} catch (writeError) {
|
|
737
|
-
logError('could not write protocol-result.json: ' + writeError);
|
|
738
|
-
}
|
|
739
|
-
exitCode = 1;
|
|
740
|
-
}
|
|
741
|
-
process.exit(exitCode);
|
|
742
|
-
`;
|
|
743
|
-
/**
|
|
744
|
-
* Runtime support for package_source "inputs": locating, extracting, and
|
|
745
|
-
* validating the shipped archive, plus target discovery from its manifest.
|
|
746
|
-
* Inserted into the runtime script only when package_source is explicitly
|
|
747
|
-
* configured. Plain dependency-free CJS; must not contain backticks, "${",
|
|
748
|
-
* or bare $UPPERCASE tokens (the pre-execution validator would parse those
|
|
749
|
-
* as variable references).
|
|
750
|
-
*/
|
|
751
|
-
const RENDER_SCRIPT_PACKAGE_STAGE = String.raw `
|
|
752
|
-
// ---- shipped-package stage (package_source: "inputs") ----
|
|
753
|
-
//
|
|
754
|
-
// The caller ships a SwiftPM package as .ci/inputs/package.tar.gz. Before
|
|
755
|
-
// any rendering, the archive is located, extracted into a scratch
|
|
756
|
-
// directory, and validated. Failures here are PER-BUILD structured errors,
|
|
757
|
-
// recorded as the result document's top-level error object -- distinct from
|
|
758
|
-
// per-screen render errors, which keep their v1 semantics. The extracted
|
|
759
|
-
// size is bounded as a decompression-bomb guard.
|
|
760
|
-
|
|
761
|
-
var PACKAGE_ARCHIVE_NAME = 'package.tar.gz';
|
|
762
|
-
var DEFAULT_MAX_EXTRACTED_BYTES = 32 * 1024 * 1024;
|
|
763
|
-
var maxExtractedBytes =
|
|
764
|
-
Number(process.env.UI_FIDELITY_MAX_PACKAGE_BYTES || '') || DEFAULT_MAX_EXTRACTED_BYTES;
|
|
765
|
-
|
|
766
|
-
var buildError = null;
|
|
767
|
-
var activeTarget = CONFIG.target;
|
|
768
|
-
var shippedExtractDir = null;
|
|
769
|
-
|
|
770
|
-
function setBuildError(code, message) {
|
|
771
|
-
buildError = { code: code, message: sanitizeMessage(message) };
|
|
772
|
-
logError(code + ': ' + buildError.message);
|
|
773
|
-
}
|
|
774
|
-
|
|
775
|
-
function runTar(args) {
|
|
776
|
-
var result = cp.spawnSync('tar', args, {
|
|
777
|
-
encoding: 'utf-8',
|
|
778
|
-
maxBuffer: 64 * 1024 * 1024,
|
|
779
|
-
});
|
|
780
|
-
if (result.error) {
|
|
781
|
-
result.status = result.status == null ? 127 : result.status;
|
|
782
|
-
result.stderr =
|
|
783
|
-
(result.stderr || '') + String(result.error.message || result.error);
|
|
784
|
-
}
|
|
785
|
-
return result;
|
|
786
|
-
}
|
|
787
|
-
|
|
788
|
-
function isUnsafeArchiveMember(name) {
|
|
789
|
-
if (name.charAt(0) === '/') return true;
|
|
790
|
-
var segments = name.split('/');
|
|
791
|
-
for (var i = 0; i < segments.length; i++) {
|
|
792
|
-
if (segments[i] === '..') return true;
|
|
793
|
-
}
|
|
794
|
-
return false;
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
function extractedSize(dir) {
|
|
798
|
-
var total = 0;
|
|
799
|
-
var stack = [dir];
|
|
800
|
-
while (stack.length > 0) {
|
|
801
|
-
var current = stack.pop();
|
|
802
|
-
var names = fs.readdirSync(current);
|
|
803
|
-
for (var i = 0; i < names.length; i++) {
|
|
804
|
-
var entryPath = path.join(current, names[i]);
|
|
805
|
-
var stat = fs.lstatSync(entryPath);
|
|
806
|
-
if (stat.isDirectory()) stack.push(entryPath);
|
|
807
|
-
else total += stat.size;
|
|
808
|
-
}
|
|
809
|
-
}
|
|
810
|
-
return total;
|
|
811
|
-
}
|
|
812
|
-
|
|
813
|
-
// Entries that macOS archiving tools add but that say nothing about the
|
|
814
|
-
// package layout are ignored when locating the package root.
|
|
815
|
-
function isJunkEntry(name) {
|
|
816
|
-
return name === '.DS_Store' || name === '__MACOSX' || name.indexOf('._') === 0;
|
|
817
|
-
}
|
|
818
|
-
|
|
819
|
-
function hasManifest(dir) {
|
|
820
|
-
try {
|
|
821
|
-
return fs.statSync(path.join(dir, 'Package.swift')).isFile();
|
|
822
|
-
} catch (statError) {
|
|
823
|
-
return false;
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
// Package-root rule: Package.swift at the extraction root wins; otherwise
|
|
828
|
-
// exactly one top-level directory with Package.swift at its top is the
|
|
829
|
-
// root; anything else is a structured per-build error.
|
|
830
|
-
function findPackageRoot(extractDir) {
|
|
831
|
-
if (hasManifest(extractDir)) return extractDir;
|
|
832
|
-
var entries = fs.readdirSync(extractDir).filter(function (name) {
|
|
833
|
-
return !isJunkEntry(name);
|
|
834
|
-
});
|
|
835
|
-
if (entries.length === 0) {
|
|
836
|
-
setBuildError('PACKAGE_ARCHIVE_INVALID', PACKAGE_ARCHIVE_NAME + ' is empty');
|
|
837
|
-
return null;
|
|
838
|
-
}
|
|
839
|
-
if (entries.length > 1) {
|
|
840
|
-
setBuildError(
|
|
841
|
-
'PACKAGE_ARCHIVE_INVALID',
|
|
842
|
-
PACKAGE_ARCHIVE_NAME + ' must contain a single package root; found ' +
|
|
843
|
-
entries.length + ' top-level entries (' + entries.sort().join(', ') +
|
|
844
|
-
') and no Package.swift at the archive root'
|
|
845
|
-
);
|
|
846
|
-
return null;
|
|
847
|
-
}
|
|
848
|
-
var rootPath = path.join(extractDir, entries[0]);
|
|
849
|
-
if (!fs.statSync(rootPath).isDirectory()) {
|
|
850
|
-
setBuildError(
|
|
851
|
-
'PACKAGE_ARCHIVE_INVALID',
|
|
852
|
-
PACKAGE_ARCHIVE_NAME + ' must contain a package directory; its only ' +
|
|
853
|
-
'top-level entry (' + entries[0] + ') is not a directory'
|
|
854
|
-
);
|
|
855
|
-
return null;
|
|
856
|
-
}
|
|
857
|
-
if (!hasManifest(rootPath)) {
|
|
858
|
-
setBuildError(
|
|
859
|
-
'PACKAGE_MANIFEST_MISSING',
|
|
860
|
-
'no Package.swift at the top of the package root (' + entries[0] + ') in ' +
|
|
861
|
-
PACKAGE_ARCHIVE_NAME
|
|
862
|
-
);
|
|
863
|
-
return null;
|
|
864
|
-
}
|
|
865
|
-
return rootPath;
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
// When no target input is configured, the target is discovered from the
|
|
869
|
-
// shipped package's manifest: the package must declare exactly one library
|
|
870
|
-
// product. The harness depends on the package via .product(name:package:),
|
|
871
|
-
// so library products are the unit of discovery.
|
|
872
|
-
function resolveTargetFromManifest(packageRoot) {
|
|
873
|
-
var dump = runSwift(['package', '--package-path', packageRoot, 'dump-package']);
|
|
874
|
-
if (dump.status !== 0) {
|
|
875
|
-
setBuildError(
|
|
876
|
-
'PACKAGE_TARGET_UNRESOLVED',
|
|
877
|
-
'could not read the shipped package manifest (swift package dump-package failed):\n' +
|
|
878
|
-
outputTail(dump)
|
|
879
|
-
);
|
|
880
|
-
return null;
|
|
881
|
-
}
|
|
882
|
-
var manifest = null;
|
|
883
|
-
try {
|
|
884
|
-
manifest = JSON.parse(dump.stdout);
|
|
885
|
-
} catch (parseError) {
|
|
886
|
-
setBuildError(
|
|
887
|
-
'PACKAGE_TARGET_UNRESOLVED',
|
|
888
|
-
'swift package dump-package produced unparseable output: ' +
|
|
889
|
-
(parseError && parseError.message ? parseError.message : String(parseError))
|
|
890
|
-
);
|
|
891
|
-
return null;
|
|
892
|
-
}
|
|
893
|
-
var libraries = ((manifest && manifest.products) || [])
|
|
894
|
-
.filter(function (product) {
|
|
895
|
-
return product && product.type && typeof product.type === 'object' &&
|
|
896
|
-
'library' in product.type;
|
|
897
|
-
})
|
|
898
|
-
.map(function (product) { return String(product.name); });
|
|
899
|
-
if (libraries.length === 0) {
|
|
900
|
-
setBuildError(
|
|
901
|
-
'PACKAGE_TARGET_UNRESOLVED',
|
|
902
|
-
'the shipped package declares no library products; declare exactly one ' +
|
|
903
|
-
'library product, or set the target input'
|
|
904
|
-
);
|
|
905
|
-
return null;
|
|
906
|
-
}
|
|
907
|
-
if (libraries.length > 1) {
|
|
908
|
-
setBuildError(
|
|
909
|
-
'PACKAGE_TARGET_UNRESOLVED',
|
|
910
|
-
'the shipped package declares ' + libraries.length + ' library products (' +
|
|
911
|
-
libraries.sort().join(', ') + '); set the target input to pick one'
|
|
912
|
-
);
|
|
913
|
-
return null;
|
|
914
|
-
}
|
|
915
|
-
if (!SWIFT_IDENTIFIER.test(libraries[0])) {
|
|
916
|
-
setBuildError(
|
|
917
|
-
'PACKAGE_TARGET_UNRESOLVED',
|
|
918
|
-
'discovered library product ' + JSON.stringify(libraries[0]) +
|
|
919
|
-
' is not a valid Swift module identifier'
|
|
920
|
-
);
|
|
921
|
-
return null;
|
|
922
|
-
}
|
|
923
|
-
return libraries[0];
|
|
924
|
-
}
|
|
925
|
-
|
|
926
|
-
// Locates, extracts, and validates the shipped archive. Returns the package
|
|
927
|
-
// root to build from, or null after recording a structured build error.
|
|
928
|
-
function prepareShippedPackage() {
|
|
929
|
-
var archivePath = path.join(inputsDir, PACKAGE_ARCHIVE_NAME);
|
|
930
|
-
var archiveStat = null;
|
|
931
|
-
try {
|
|
932
|
-
archiveStat = fs.statSync(archivePath);
|
|
933
|
-
} catch (statError) {
|
|
934
|
-
archiveStat = null;
|
|
935
|
-
}
|
|
936
|
-
if (!archiveStat || !archiveStat.isFile()) {
|
|
937
|
-
setBuildError(
|
|
938
|
-
'PACKAGE_ARCHIVE_MISSING',
|
|
939
|
-
'no ' + PACKAGE_ARCHIVE_NAME + ' in ' + inputsDir + '; ship the SwiftPM ' +
|
|
940
|
-
'package as a run input named ' + PACKAGE_ARCHIVE_NAME
|
|
941
|
-
);
|
|
942
|
-
return null;
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
// bsdtar and GNU tar both refuse absolute and dot-dot member paths by
|
|
946
|
-
// default; the listing is checked explicitly anyway so the guard does not
|
|
947
|
-
// depend on the host tar implementation. A failed listing also catches
|
|
948
|
-
// unreadable or corrupt archives before anything touches the disk.
|
|
949
|
-
var listing = runTar(['-tzf', archivePath]);
|
|
950
|
-
if (listing.status !== 0) {
|
|
951
|
-
setBuildError(
|
|
952
|
-
'PACKAGE_ARCHIVE_INVALID',
|
|
953
|
-
PACKAGE_ARCHIVE_NAME + ' is not a readable gzipped tar archive:\n' +
|
|
954
|
-
outputTail(listing)
|
|
955
|
-
);
|
|
956
|
-
return null;
|
|
957
|
-
}
|
|
958
|
-
var members = (listing.stdout || '').split('\n').filter(function (name) {
|
|
959
|
-
return name !== '';
|
|
960
|
-
});
|
|
961
|
-
var unsafe = members.filter(isUnsafeArchiveMember);
|
|
962
|
-
if (unsafe.length > 0) {
|
|
963
|
-
setBuildError(
|
|
964
|
-
'PACKAGE_ARCHIVE_INVALID',
|
|
965
|
-
PACKAGE_ARCHIVE_NAME + ' contains unsafe member paths (absolute or dot-dot): ' +
|
|
966
|
-
unsafe.slice(0, 5).join(', ')
|
|
967
|
-
);
|
|
968
|
-
return null;
|
|
969
|
-
}
|
|
970
|
-
|
|
971
|
-
shippedExtractDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ui-fidelity-package-'));
|
|
972
|
-
registerPathReplacement(shippedExtractDir, '<package>');
|
|
973
|
-
log('extracting ' + PACKAGE_ARCHIVE_NAME + ' (' + archiveStat.size + ' bytes)');
|
|
974
|
-
var extraction = runTar(['-xzf', archivePath, '-C', shippedExtractDir]);
|
|
975
|
-
if (extraction.status !== 0) {
|
|
976
|
-
setBuildError(
|
|
977
|
-
'PACKAGE_ARCHIVE_INVALID',
|
|
978
|
-
PACKAGE_ARCHIVE_NAME + ' could not be extracted:\n' + outputTail(extraction)
|
|
979
|
-
);
|
|
980
|
-
return null;
|
|
981
|
-
}
|
|
982
|
-
|
|
983
|
-
var totalBytes = extractedSize(shippedExtractDir);
|
|
984
|
-
if (totalBytes > maxExtractedBytes) {
|
|
985
|
-
setBuildError(
|
|
986
|
-
'PACKAGE_ARCHIVE_TOO_LARGE',
|
|
987
|
-
PACKAGE_ARCHIVE_NAME + ' extracts to ' + totalBytes + ' bytes, over the ' +
|
|
988
|
-
maxExtractedBytes + '-byte limit; ship a leaner source slice'
|
|
989
|
-
);
|
|
990
|
-
return null;
|
|
991
|
-
}
|
|
992
|
-
|
|
993
|
-
var packageRoot = findPackageRoot(shippedExtractDir);
|
|
994
|
-
if (packageRoot === null) return null;
|
|
995
|
-
var rootLabel = path.relative(shippedExtractDir, packageRoot);
|
|
996
|
-
log('package root: ' + (rootLabel === '' ? '.' : rootLabel));
|
|
997
|
-
|
|
998
|
-
if (activeTarget === null) {
|
|
999
|
-
var discovered = resolveTargetFromManifest(packageRoot);
|
|
1000
|
-
if (discovered === null) return null;
|
|
1001
|
-
log('discovered library target: ' + discovered);
|
|
1002
|
-
activeTarget = discovered;
|
|
1003
|
-
}
|
|
1004
|
-
return packageRoot;
|
|
1005
|
-
}
|
|
1006
|
-
|
|
1007
|
-
function cleanupShippedPackage() {
|
|
1008
|
-
if (shippedExtractDir === null) return;
|
|
1009
|
-
if (process.env.UI_FIDELITY_KEEP_HARNESS) {
|
|
1010
|
-
log('keeping extracted package (UI_FIDELITY_KEEP_HARNESS set)');
|
|
1011
|
-
return;
|
|
1012
|
-
}
|
|
1013
|
-
try {
|
|
1014
|
-
fs.rmSync(shippedExtractDir, { recursive: true, force: true });
|
|
1015
|
-
} catch (cleanupError) {
|
|
1016
|
-
// best effort
|
|
1017
|
-
}
|
|
1018
|
-
shippedExtractDir = null;
|
|
1019
|
-
}
|
|
1020
|
-
`;
|
|
1021
|
-
/**
|
|
1022
|
-
* Runtime support for asset catalogs (package_source variants only).
|
|
1023
|
-
*
|
|
1024
|
-
* `swift build` copies a target's .xcassets into its resource bundle but never
|
|
1025
|
-
* runs actool, so SwiftUI's Image("name") finds no compiled catalog and the
|
|
1026
|
-
* image does not resolve. After the harness builds, compile every .xcassets in
|
|
1027
|
-
* the package with actool into the harness bin directory (= the render
|
|
1028
|
-
* executable's Bundle.main), so unmodified Image("name") references resolve.
|
|
1029
|
-
* One actool invocation over all catalogs (it merges them). Best-effort:
|
|
1030
|
-
* a compile failure is logged and rendering continues -- the affected images
|
|
1031
|
-
* simply will not appear, which the human comparing the screens will see.
|
|
1032
|
-
*/
|
|
1033
|
-
const RENDER_SCRIPT_ASSET_CATALOG_STAGE = String.raw `
|
|
1034
|
-
// ---- asset-catalog stage (package_source variants) ----
|
|
1035
|
-
|
|
1036
|
-
var ASSET_CATALOG_MIN_MACOS = '13.0';
|
|
1037
|
-
|
|
1038
|
-
// Recursively collect *.xcassets directories under root, skipping .build and
|
|
1039
|
-
// the catalogs' own contents (a catalog is a leaf for this scan).
|
|
1040
|
-
function findAssetCatalogs(root) {
|
|
1041
|
-
var found = [];
|
|
1042
|
-
var stack = [root];
|
|
1043
|
-
while (stack.length > 0) {
|
|
1044
|
-
var dir = stack.pop();
|
|
1045
|
-
var entries;
|
|
1046
|
-
try {
|
|
1047
|
-
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
1048
|
-
} catch (readError) {
|
|
1049
|
-
continue;
|
|
1050
|
-
}
|
|
1051
|
-
for (var i = 0; i < entries.length; i++) {
|
|
1052
|
-
var entry = entries[i];
|
|
1053
|
-
if (!entry.isDirectory()) continue;
|
|
1054
|
-
if (entry.name === '.build') continue;
|
|
1055
|
-
var full = path.join(dir, entry.name);
|
|
1056
|
-
if (entry.name.slice(-9) === '.xcassets') {
|
|
1057
|
-
found.push(full);
|
|
1058
|
-
} else {
|
|
1059
|
-
stack.push(full);
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
}
|
|
1063
|
-
return found.sort();
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
// Run actool over the given catalogs, emitting Assets.car into outDir.
|
|
1067
|
-
// Best-effort: a failure is logged (warn-and-continue) and rendering proceeds.
|
|
1068
|
-
function compileCatalogsInto(catalogs, outDir, label) {
|
|
1069
|
-
try {
|
|
1070
|
-
fs.mkdirSync(outDir, { recursive: true });
|
|
1071
|
-
} catch (mkdirError) {
|
|
1072
|
-
// best effort -- actool reports a missing output dir itself
|
|
1073
|
-
}
|
|
1074
|
-
var args = ['actool'].concat(catalogs);
|
|
1075
|
-
args.push(
|
|
1076
|
-
'--compile', outDir,
|
|
1077
|
-
'--platform', 'macosx',
|
|
1078
|
-
'--minimum-deployment-target', ASSET_CATALOG_MIN_MACOS,
|
|
1079
|
-
'--output-format', 'human-readable-text'
|
|
1080
|
-
);
|
|
1081
|
-
log('xcrun ' + args.join(' '));
|
|
1082
|
-
var result = cp.spawnSync('xcrun', args, {
|
|
1083
|
-
encoding: 'utf-8',
|
|
1084
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
1085
|
-
});
|
|
1086
|
-
if (result.stdout) process.stdout.write(result.stdout);
|
|
1087
|
-
if (result.stderr) process.stderr.write(result.stderr);
|
|
1088
|
-
if (result.error || result.status !== 0) {
|
|
1089
|
-
var detail = (result.stderr || '') +
|
|
1090
|
-
(result.error ? String(result.error.message || result.error) : '');
|
|
1091
|
-
logError(
|
|
1092
|
-
'asset-catalog compile failed for ' + label + ' (continuing; catalog ' +
|
|
1093
|
-
'images may not resolve): ' + sanitizeMessage(detail.trim())
|
|
1094
|
-
);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
// SwiftUI resolves Image("name") against Bundle.main and
|
|
1099
|
-
// Image("name", bundle: .module) against a target's own resource bundle.
|
|
1100
|
-
// swift build copies a .process'd .xcassets into the module bundle but never
|
|
1101
|
-
// compiles it, and a loose catalog is copied nowhere. So compile twice:
|
|
1102
|
-
// the package's source catalogs into the bin dir (= Bundle.main), AND each
|
|
1103
|
-
// module bundle's copied catalog in place (= Bundle.module). This covers both
|
|
1104
|
-
// reference idioms, so the agent need not know which bundle the renderer uses.
|
|
1105
|
-
function compileAssetCatalogs(packageDir, harnessDir) {
|
|
1106
|
-
var sourceCatalogs = findAssetCatalogs(packageDir);
|
|
1107
|
-
if (sourceCatalogs.length === 0) return;
|
|
1108
|
-
|
|
1109
|
-
var binResult = runSwift(['build', '--package-path', harnessDir, '--show-bin-path']);
|
|
1110
|
-
if (binResult.status !== 0) {
|
|
1111
|
-
logError('asset-catalog compile skipped: could not resolve the build bin path');
|
|
1112
|
-
return;
|
|
1113
|
-
}
|
|
1114
|
-
var binPath = String(binResult.stdout || '').trim().split('\n').pop().trim();
|
|
1115
|
-
if (!binPath) {
|
|
1116
|
-
logError('asset-catalog compile skipped: empty build bin path');
|
|
1117
|
-
return;
|
|
1118
|
-
}
|
|
1119
|
-
|
|
1120
|
-
// Bundle.main: the package's source catalogs, merged into the bin dir.
|
|
1121
|
-
compileCatalogsInto(sourceCatalogs, binPath, 'Bundle.main');
|
|
1122
|
-
|
|
1123
|
-
// Bundle.module: each *.bundle in the bin dir that carries a copied catalog,
|
|
1124
|
-
// compiled in place so Image(..., bundle: .module) resolves.
|
|
1125
|
-
var entries;
|
|
1126
|
-
try {
|
|
1127
|
-
entries = fs.readdirSync(binPath, { withFileTypes: true });
|
|
1128
|
-
} catch (readError) {
|
|
1129
|
-
entries = [];
|
|
1130
|
-
}
|
|
1131
|
-
for (var i = 0; i < entries.length; i++) {
|
|
1132
|
-
var entry = entries[i];
|
|
1133
|
-
if (!entry.isDirectory() || entry.name.slice(-7) !== '.bundle') continue;
|
|
1134
|
-
var bundleDir = path.join(binPath, entry.name);
|
|
1135
|
-
var bundleCatalogs = findAssetCatalogs(bundleDir);
|
|
1136
|
-
if (bundleCatalogs.length > 0) {
|
|
1137
|
-
compileCatalogsInto(bundleCatalogs, bundleDir, entry.name);
|
|
1138
|
-
}
|
|
1139
|
-
}
|
|
1140
|
-
}
|
|
1141
|
-
`;
|
|
1142
|
-
/**
|
|
1143
|
-
* Replaces exactly one occurrence of `anchor` in `source`. Throws when the
|
|
1144
|
-
* anchor is missing or ambiguous, so any drift between the v1 runtime text
|
|
1145
|
-
* and the package-source variant fails loudly at module load.
|
|
1146
|
-
*/
|
|
1147
|
-
function replaceOnce(source, anchor, replacement) {
|
|
1148
|
-
const first = source.indexOf(anchor);
|
|
1149
|
-
if (first === -1 || source.indexOf(anchor, first + anchor.length) !== -1) {
|
|
1150
|
-
throw new Error('ui-fidelity-render: expected exactly one occurrence of anchor: ' + anchor);
|
|
1151
|
-
}
|
|
1152
|
-
return source.slice(0, first) + replacement + source.slice(first + anchor.length);
|
|
1153
|
-
}
|
|
1154
|
-
/**
|
|
1155
|
-
* Builds the runtime main for explicitly-configured package_source ("repo"
|
|
1156
|
-
* or "inputs") by patching the v1 runtime text. RENDER_SCRIPT_MAIN itself is
|
|
1157
|
-
* never modified: omitting package_source keeps the generated script
|
|
1158
|
-
* byte-identical to v1 (locked by a snapshot test), and the patch points
|
|
1159
|
-
* below are the complete, reviewable diff between the two modes.
|
|
1160
|
-
*/
|
|
1161
|
-
function buildPackageSourceMain() {
|
|
1162
|
-
let main = RENDER_SCRIPT_MAIN;
|
|
1163
|
-
// The result document gains the configured package source and a top-level
|
|
1164
|
-
// per-build error slot, distinct from per-screen errors.
|
|
1165
|
-
main = replaceOnce(main, 'var doc = { renderer: RENDERER, screens: currentEntries };', 'var doc = {\n' +
|
|
1166
|
-
' renderer: RENDERER,\n' +
|
|
1167
|
-
' package_source: CONFIG.packageSource,\n' +
|
|
1168
|
-
' error: buildError,\n' +
|
|
1169
|
-
' screens: currentEntries,\n' +
|
|
1170
|
-
' };');
|
|
1171
|
-
// packagePath is null with package_source "inputs": the package to build
|
|
1172
|
-
// from is only known after extraction.
|
|
1173
|
-
main = replaceOnce(main, "var resolvedPackagePath = path.resolve(CONFIG.packagePath);\n" +
|
|
1174
|
-
'registerPathReplacement(\n' +
|
|
1175
|
-
' resolvedPackagePath,\n' +
|
|
1176
|
-
" path.isAbsolute(CONFIG.packagePath) ? '<package_path>' : CONFIG.packagePath\n" +
|
|
1177
|
-
');', 'var resolvedPackagePath =\n' +
|
|
1178
|
-
' CONFIG.packagePath === null ? null : path.resolve(CONFIG.packagePath);\n' +
|
|
1179
|
-
'if (resolvedPackagePath !== null) {\n' +
|
|
1180
|
-
' registerPathReplacement(\n' +
|
|
1181
|
-
' resolvedPackagePath,\n' +
|
|
1182
|
-
" path.isAbsolute(CONFIG.packagePath) ? '<package_path>' : CONFIG.packagePath\n" +
|
|
1183
|
-
' );\n' +
|
|
1184
|
-
'}');
|
|
1185
|
-
// The harness target may be discovered at runtime from the shipped
|
|
1186
|
-
// manifest, so renderWithHarness reads the resolved activeTarget.
|
|
1187
|
-
main = replaceOnce(main, 'target: CONFIG.target,', 'target: activeTarget,');
|
|
1188
|
-
main = replaceOnce(main, "' a public View with a parameterless init in ' + CONFIG.target + '?):\\n' +", "' a public View with a parameterless init in ' + activeTarget + '?):\\n' +");
|
|
1189
|
-
// The shipped-package stage runs before any harness build. It is reached
|
|
1190
|
-
// even when no screen is renderable, so packaging mistakes are always
|
|
1191
|
-
// reported; its scratch directory is cleaned up afterwards.
|
|
1192
|
-
main = replaceOnce(main, ' // 3. Render the remaining screens through the synthesized harness.\n' +
|
|
1193
|
-
' var renderable = currentEntries.filter(function (entry) { return entry.error === null; });\n' +
|
|
1194
|
-
' if (renderable.length > 0) {\n' +
|
|
1195
|
-
' if (!fs.existsSync(resolvedPackagePath)) {\n' +
|
|
1196
|
-
' currentEntries.forEach(function (entry) {\n' +
|
|
1197
|
-
" setError(entry, 'RENDER_UNSUPPORTED', 'package_path does not exist: ' + resolvedPackagePath);\n" +
|
|
1198
|
-
' });\n' +
|
|
1199
|
-
' } else {\n' +
|
|
1200
|
-
' renderWithHarness(renderable, resolvedPackagePath);\n' +
|
|
1201
|
-
' }\n' +
|
|
1202
|
-
' } else if (currentEntries.length > 0) {\n' +
|
|
1203
|
-
" log('no renderable screens, skipping harness build');\n" +
|
|
1204
|
-
' }', ' // 3. Resolve the package to build from, then render the remaining\n' +
|
|
1205
|
-
' // screens through the synthesized harness.\n' +
|
|
1206
|
-
' var renderable = currentEntries.filter(function (entry) { return entry.error === null; });\n' +
|
|
1207
|
-
" if (CONFIG.packageSource === 'inputs') {\n" +
|
|
1208
|
-
' try {\n' +
|
|
1209
|
-
' var shippedRoot = prepareShippedPackage();\n' +
|
|
1210
|
-
' if (shippedRoot !== null) {\n' +
|
|
1211
|
-
' if (renderable.length > 0) {\n' +
|
|
1212
|
-
' renderWithHarness(renderable, shippedRoot);\n' +
|
|
1213
|
-
' } else if (currentEntries.length > 0) {\n' +
|
|
1214
|
-
" log('no renderable screens, skipping harness build');\n" +
|
|
1215
|
-
' }\n' +
|
|
1216
|
-
' }\n' +
|
|
1217
|
-
' } finally {\n' +
|
|
1218
|
-
' cleanupShippedPackage();\n' +
|
|
1219
|
-
' }\n' +
|
|
1220
|
-
' } else if (renderable.length > 0) {\n' +
|
|
1221
|
-
' if (!fs.existsSync(resolvedPackagePath)) {\n' +
|
|
1222
|
-
' currentEntries.forEach(function (entry) {\n' +
|
|
1223
|
-
" setError(entry, 'RENDER_UNSUPPORTED', 'package_path does not exist: ' + resolvedPackagePath);\n" +
|
|
1224
|
-
' });\n' +
|
|
1225
|
-
' } else {\n' +
|
|
1226
|
-
' renderWithHarness(renderable, resolvedPackagePath);\n' +
|
|
1227
|
-
' }\n' +
|
|
1228
|
-
' } else if (currentEntries.length > 0) {\n' +
|
|
1229
|
-
" log('no renderable screens, skipping harness build');\n" +
|
|
1230
|
-
' }');
|
|
1231
|
-
// A per-build package error fails the step even when no screen reached
|
|
1232
|
-
// the harness (including the zero-screens case).
|
|
1233
|
-
main = replaceOnce(main, ' // 4. Exit code: zero only when every screen rendered.\n' +
|
|
1234
|
-
' writeResult();\n', ' // 4. Exit code: zero only when the shipped package (if any) was valid\n' +
|
|
1235
|
-
' // and every screen rendered.\n' +
|
|
1236
|
-
' writeResult();\n' +
|
|
1237
|
-
' if (buildError !== null) {\n' +
|
|
1238
|
-
" logError('package validation failed: ' + buildError.code);\n" +
|
|
1239
|
-
' return 1;\n' +
|
|
1240
|
-
' }\n');
|
|
1241
|
-
// The shipped-package stage is defined ahead of main().
|
|
1242
|
-
main = replaceOnce(main, 'function main() {', RENDER_SCRIPT_PACKAGE_STAGE + '\nfunction main() {');
|
|
1243
|
-
// Asset-catalog stage: define the helpers ahead of renderWithHarness, then
|
|
1244
|
-
// compile any catalogs once the package has proven it builds (after the
|
|
1245
|
-
// probe) and before the per-screen renders that depend on the images.
|
|
1246
|
-
main = replaceOnce(main, 'function renderWithHarness(renderable, packagePath) {', RENDER_SCRIPT_ASSET_CATALOG_STAGE + '\nfunction renderWithHarness(renderable, packagePath) {');
|
|
1247
|
-
main = replaceOnce(main, ' renderable.forEach(function (entry) {', ' compileAssetCatalogs(packagePath, harnessDir);\n\n' +
|
|
1248
|
-
' renderable.forEach(function (entry) {');
|
|
1249
|
-
// Render-trimming stage: define the helpers ahead of renderWithHarness, then
|
|
1250
|
-
// crop each rendered PNG to its content bounding box right after it renders.
|
|
1251
|
-
main = replaceOnce(main, 'function renderWithHarness(renderable, packagePath) {', RENDER_SCRIPT_TRIM_STAGE + '\nfunction renderWithHarness(renderable, packagePath) {');
|
|
1252
|
-
main = replaceOnce(main, " log('rendered ' + entry.screen + ' -> ' + relative);", " log('rendered ' + entry.screen + ' -> ' + relative);\n" +
|
|
1253
|
-
' trimRenderedImage(entry, outputPath);');
|
|
1254
|
-
// Per-screen result entries gain the trimmed-render fields: the aligned
|
|
1255
|
-
// (reference-sized) image and the rendered/logical/reference dimensions.
|
|
1256
|
-
// They stay null until a screen renders and is trimmed.
|
|
1257
|
-
main = replaceOnce(main, ' reference_image_path: null,\n' +
|
|
1258
|
-
' rendered_image_path: null,\n' +
|
|
1259
|
-
' };', ' reference_image_path: null,\n' +
|
|
1260
|
-
' rendered_image_path: null,\n' +
|
|
1261
|
-
' aligned_image_path: null,\n' +
|
|
1262
|
-
' dimensions: null,\n' +
|
|
1263
|
-
' };');
|
|
1264
|
-
return main;
|
|
1265
|
-
}
|
|
1266
|
-
const RENDER_SCRIPT_MAIN_WITH_PACKAGE_SOURCE = buildPackageSourceMain();
|
|
1267
|
-
/**
|
|
1268
|
-
* Builds the runtime main for package_source "url" (fidelity-linux 03) by
|
|
1269
|
-
* patching the package_source variant. Three departures, all forced by the
|
|
1270
|
-
* same fact — a clone-free worker has no checkout:
|
|
1271
|
-
*
|
|
1272
|
-
* 1. The package is DOWNLOADED (curl, a runner requirement in this mode) to
|
|
1273
|
-
* `.ci/inputs/package.tar.gz` and extracted BEFORE the per-screen
|
|
1274
|
-
* reference stage, because committed references travel inside it. A failed
|
|
1275
|
-
* download is one structured per-build error, never per-screen noise.
|
|
1276
|
-
* 2. Each screen's reference is the committed repo-relative path resolved
|
|
1277
|
-
* INSIDE the extracted package: it must sit under packagePath, and the
|
|
1278
|
-
* remainder resolves against the extracted package root.
|
|
1279
|
-
* 3. The harness stage reuses the already-extracted root (no second extract);
|
|
1280
|
-
* cleanup mirrors the inputs path. Mirrors the Android step's url mode so
|
|
1281
|
-
* both platform kinds accept the same dispatched inputs.
|
|
1282
|
-
*/
|
|
1283
|
-
function buildPackageUrlMain() {
|
|
1284
|
-
let main = RENDER_SCRIPT_MAIN_WITH_PACKAGE_SOURCE;
|
|
1285
|
-
// Download + extract FIRST — the reference stage below reads from inside
|
|
1286
|
-
// the extracted package.
|
|
1287
|
-
main = replaceOnce(main, ' // 2. Per-screen validation + reference copies. Each screen gets its OWN\n' +
|
|
1288
|
-
' // copy named by screen, even when two screens share a reference\n' +
|
|
1289
|
-
' // basename.\n' +
|
|
1290
|
-
' currentEntries.forEach(function (entry) {', ' // 2a. Download + extract the shipped package FIRST (URL mode): committed\n' +
|
|
1291
|
-
' // references live inside it, so the per-screen reference copies below\n' +
|
|
1292
|
-
' // need the extracted package root. A failed download or an invalid\n' +
|
|
1293
|
-
' // archive is ONE structured per-build error; per-screen reference\n' +
|
|
1294
|
-
' // work then stops cleanly (no bogus REFERENCE_MISSING noise).\n' +
|
|
1295
|
-
' var urlPackageRoot = null;\n' +
|
|
1296
|
-
' (function () {\n' +
|
|
1297
|
-
' try {\n' +
|
|
1298
|
-
' fs.mkdirSync(inputsDir, { recursive: true });\n' +
|
|
1299
|
-
' } catch (mkdirError) {\n' +
|
|
1300
|
-
' // the download below reports the real failure\n' +
|
|
1301
|
-
' }\n' +
|
|
1302
|
-
' var dest = path.join(inputsDir, PACKAGE_ARCHIVE_NAME);\n' +
|
|
1303
|
-
" log('downloading render package');\n" +
|
|
1304
|
-
' var download = cp.spawnSync(\n' +
|
|
1305
|
-
" 'curl',\n" +
|
|
1306
|
-
" ['-fsSL', '--retry', '2', '--max-time', '300', '-o', dest, CONFIG.packageUrl],\n" +
|
|
1307
|
-
" { encoding: 'utf-8', maxBuffer: 16 * 1024 * 1024 }\n" +
|
|
1308
|
-
' );\n' +
|
|
1309
|
-
' if (download.error) {\n' +
|
|
1310
|
-
' download.status = download.status == null ? 127 : download.status;\n' +
|
|
1311
|
-
" download.stderr = (download.stderr || '') + String(download.error.message || download.error);\n" +
|
|
1312
|
-
' }\n' +
|
|
1313
|
-
' if (download.status !== 0) {\n' +
|
|
1314
|
-
' setBuildError(\n' +
|
|
1315
|
-
" 'PACKAGE_DOWNLOAD_FAILED',\n" +
|
|
1316
|
-
" 'render package download failed (curl exit ' + download.status + '):\\n' + outputTail(download)\n" +
|
|
1317
|
-
' );\n' +
|
|
1318
|
-
' return;\n' +
|
|
1319
|
-
' }\n' +
|
|
1320
|
-
' urlPackageRoot = prepareShippedPackage();\n' +
|
|
1321
|
-
' })();\n' +
|
|
1322
|
-
'\n' +
|
|
1323
|
-
' // 2. Per-screen validation + reference copies. Each screen gets its OWN\n' +
|
|
1324
|
-
' // copy named by screen, even when two screens share a reference\n' +
|
|
1325
|
-
' // basename.\n' +
|
|
1326
|
-
' currentEntries.forEach(function (entry) {');
|
|
1327
|
-
// References resolve INSIDE the extracted package: repo-relative path minus
|
|
1328
|
-
// the packagePath prefix, against the extracted package root.
|
|
1329
|
-
main = replaceOnce(main, 'var basename = params.screens[entry.screen];\n' +
|
|
1330
|
-
' if (\n' +
|
|
1331
|
-
" typeof basename !== 'string' || basename === '' ||\n" +
|
|
1332
|
-
" basename === '.' || basename === '..' ||\n" +
|
|
1333
|
-
' basename !== path.basename(basename)\n' +
|
|
1334
|
-
' ) {\n' +
|
|
1335
|
-
' setError(\n' +
|
|
1336
|
-
' entry,\n' +
|
|
1337
|
-
" 'REFERENCE_MISSING',\n" +
|
|
1338
|
-
" 'reference for ' + entry.screen + ' must be a plain file basename inside ' +\n" +
|
|
1339
|
-
" inputsDir + ', got: ' + JSON.stringify(basename)\n" +
|
|
1340
|
-
' );\n' +
|
|
1341
|
-
' return;\n' +
|
|
1342
|
-
' }\n' +
|
|
1343
|
-
' var source = path.join(inputsDir, basename);', 'var refPath = params.screens[entry.screen];\n' +
|
|
1344
|
-
" if (typeof refPath !== 'string' || refPath === '') {\n" +
|
|
1345
|
-
' setError(\n' +
|
|
1346
|
-
' entry,\n' +
|
|
1347
|
-
" 'REFERENCE_MISSING',\n" +
|
|
1348
|
-
" 'reference for ' + entry.screen + ' must be a non-empty repo-relative path, got: ' +\n" +
|
|
1349
|
-
' JSON.stringify(refPath)\n' +
|
|
1350
|
-
' );\n' +
|
|
1351
|
-
' return;\n' +
|
|
1352
|
-
' }\n' +
|
|
1353
|
-
' if (urlPackageRoot === null) {\n' +
|
|
1354
|
-
' // Package unavailable: the per-build error above carries the cause.\n' +
|
|
1355
|
-
' return;\n' +
|
|
1356
|
-
' }\n' +
|
|
1357
|
-
" var packagePrefix = CONFIG.packagePath.replace(/^\\.\\//, '').replace(/\\/+$/, '');\n" +
|
|
1358
|
-
" var normalizedRef = refPath.replace(/^\\.\\//, '');\n" +
|
|
1359
|
-
" if (normalizedRef.indexOf(packagePrefix + '/') !== 0) {\n" +
|
|
1360
|
-
' setError(\n' +
|
|
1361
|
-
' entry,\n' +
|
|
1362
|
-
" 'REFERENCE_MISSING',\n" +
|
|
1363
|
-
" 'reference for ' + entry.screen + ' must live inside the shipped package (' +\n" +
|
|
1364
|
-
" packagePrefix + '), got: ' + JSON.stringify(refPath)\n" +
|
|
1365
|
-
' );\n' +
|
|
1366
|
-
' return;\n' +
|
|
1367
|
-
' }\n' +
|
|
1368
|
-
' var source = path.join(urlPackageRoot, normalizedRef.slice(packagePrefix.length + 1));');
|
|
1369
|
-
// Harness stage: build from the package extracted in stage 2a — no second
|
|
1370
|
-
// extract. This variant only ever runs with packageSource "url".
|
|
1371
|
-
main = replaceOnce(main, ' // 3. Resolve the package to build from, then render the remaining\n' +
|
|
1372
|
-
' // screens through the synthesized harness.\n' +
|
|
1373
|
-
' var renderable = currentEntries.filter(function (entry) { return entry.error === null; });\n' +
|
|
1374
|
-
" if (CONFIG.packageSource === 'inputs') {\n" +
|
|
1375
|
-
' try {\n' +
|
|
1376
|
-
' var shippedRoot = prepareShippedPackage();\n' +
|
|
1377
|
-
' if (shippedRoot !== null) {\n' +
|
|
1378
|
-
' if (renderable.length > 0) {\n' +
|
|
1379
|
-
' renderWithHarness(renderable, shippedRoot);\n' +
|
|
1380
|
-
' } else if (currentEntries.length > 0) {\n' +
|
|
1381
|
-
" log('no renderable screens, skipping harness build');\n" +
|
|
1382
|
-
' }\n' +
|
|
1383
|
-
' }\n' +
|
|
1384
|
-
' } finally {\n' +
|
|
1385
|
-
' cleanupShippedPackage();\n' +
|
|
1386
|
-
' }\n' +
|
|
1387
|
-
' } else if (renderable.length > 0) {\n' +
|
|
1388
|
-
' if (!fs.existsSync(resolvedPackagePath)) {\n' +
|
|
1389
|
-
' currentEntries.forEach(function (entry) {\n' +
|
|
1390
|
-
" setError(entry, 'RENDER_UNSUPPORTED', 'package_path does not exist: ' + resolvedPackagePath);\n" +
|
|
1391
|
-
' });\n' +
|
|
1392
|
-
' } else {\n' +
|
|
1393
|
-
' renderWithHarness(renderable, resolvedPackagePath);\n' +
|
|
1394
|
-
' }\n' +
|
|
1395
|
-
' } else if (currentEntries.length > 0) {\n' +
|
|
1396
|
-
" log('no renderable screens, skipping harness build');\n" +
|
|
1397
|
-
' }', ' // 3. Render from the package downloaded + extracted in stage 2a. The\n' +
|
|
1398
|
-
' // extraction already reported packaging mistakes as the per-build\n' +
|
|
1399
|
-
' // error, so a null root here just skips the harness build.\n' +
|
|
1400
|
-
' var renderable = currentEntries.filter(function (entry) { return entry.error === null; });\n' +
|
|
1401
|
-
' try {\n' +
|
|
1402
|
-
' if (urlPackageRoot !== null) {\n' +
|
|
1403
|
-
' if (renderable.length > 0) {\n' +
|
|
1404
|
-
' renderWithHarness(renderable, urlPackageRoot);\n' +
|
|
1405
|
-
' } else if (currentEntries.length > 0) {\n' +
|
|
1406
|
-
" log('no renderable screens, skipping harness build');\n" +
|
|
1407
|
-
' }\n' +
|
|
1408
|
-
' }\n' +
|
|
1409
|
-
' } finally {\n' +
|
|
1410
|
-
' cleanupShippedPackage();\n' +
|
|
1411
|
-
' }');
|
|
1412
|
-
return main;
|
|
1413
|
-
}
|
|
1414
|
-
const RENDER_SCRIPT_MAIN_URL = buildPackageUrlMain();
|
|
1415
|
-
/**
|
|
1416
|
-
* Patches the reference-sourcing block to read each screen's reference from a
|
|
1417
|
-
* repo-relative path (resolved against the checkout) instead of a plain
|
|
1418
|
-
* basename under `.ci/inputs/`. Applied on top of EITHER base main (v1 or the
|
|
1419
|
-
* package_source variant) — the package patches never touch this block, so the
|
|
1420
|
-
* anchor is present and unique in both. The downstream stat/copy logic is
|
|
1421
|
-
* unchanged; only how `source` is computed differs.
|
|
1422
|
-
*/
|
|
1423
|
-
function buildReferenceRepoMain(base) {
|
|
1424
|
-
return replaceOnce(base, 'var basename = params.screens[entry.screen];\n' +
|
|
1425
|
-
' if (\n' +
|
|
1426
|
-
" typeof basename !== 'string' || basename === '' ||\n" +
|
|
1427
|
-
" basename === '.' || basename === '..' ||\n" +
|
|
1428
|
-
' basename !== path.basename(basename)\n' +
|
|
1429
|
-
' ) {\n' +
|
|
1430
|
-
' setError(\n' +
|
|
1431
|
-
' entry,\n' +
|
|
1432
|
-
" 'REFERENCE_MISSING',\n" +
|
|
1433
|
-
" 'reference for ' + entry.screen + ' must be a plain file basename inside ' +\n" +
|
|
1434
|
-
" inputsDir + ', got: ' + JSON.stringify(basename)\n" +
|
|
1435
|
-
' );\n' +
|
|
1436
|
-
' return;\n' +
|
|
1437
|
-
' }\n' +
|
|
1438
|
-
' var source = path.join(inputsDir, basename);', 'var refPath = params.screens[entry.screen];\n' +
|
|
1439
|
-
" if (typeof refPath !== 'string' || refPath === '') {\n" +
|
|
1440
|
-
' setError(\n' +
|
|
1441
|
-
' entry,\n' +
|
|
1442
|
-
" 'REFERENCE_MISSING',\n" +
|
|
1443
|
-
" 'reference for ' + entry.screen + ' must be a non-empty repo-relative path, got: ' +\n" +
|
|
1444
|
-
' JSON.stringify(refPath)\n' +
|
|
1445
|
-
' );\n' +
|
|
1446
|
-
' return;\n' +
|
|
1447
|
-
' }\n' +
|
|
1448
|
-
' var source = path.resolve(refPath);');
|
|
1449
|
-
}
|
|
1450
|
-
/**
|
|
1451
|
-
* Evaluates the embedded generator source and returns the real functions, so
|
|
1452
|
-
* unit tests exercise exactly the code that ships inside the runtime script.
|
|
1453
|
-
*/
|
|
1454
|
-
export function getRenderScriptInternals() {
|
|
1455
|
-
const factory = new Function(RENDER_SCRIPT_GENERATORS +
|
|
1456
|
-
'\nreturn { swiftStringLiteral: swiftStringLiteral,' +
|
|
1457
|
-
' generateHarnessPackageSwift: generateHarnessPackageSwift,' +
|
|
1458
|
-
' generateProbeSwift: generateProbeSwift,' +
|
|
1459
|
-
' generateScreenSwift: generateScreenSwift };');
|
|
1460
|
-
return factory();
|
|
1461
|
-
}
|
|
1462
|
-
/**
|
|
1463
|
-
* Generates the self-contained runtime node script for the step.
|
|
1464
|
-
* Pure function of its config — exported so tests can execute the script.
|
|
1465
|
-
*
|
|
1466
|
-
* When config.packageSource is absent the output is byte-identical to the
|
|
1467
|
-
* pre-package_source script (JSON.stringify drops undefined keys, and the
|
|
1468
|
-
* unpatched v1 runtime is used).
|
|
1469
|
-
*/
|
|
1470
|
-
export function generateRenderScript(config) {
|
|
1471
|
-
let main = config.packageSource === undefined
|
|
1472
|
-
? RENDER_SCRIPT_MAIN
|
|
1473
|
-
: config.packageSource === 'url'
|
|
1474
|
-
? RENDER_SCRIPT_MAIN_URL
|
|
1475
|
-
: RENDER_SCRIPT_MAIN_WITH_PACKAGE_SOURCE;
|
|
1476
|
-
// Reference-from-repo is an independent dimension layered on top of whichever
|
|
1477
|
-
// package base applies. Absent → byte-identical reference block (v1).
|
|
1478
|
-
if (config.referenceSource === 'repo') {
|
|
1479
|
-
main = buildReferenceRepoMain(main);
|
|
1480
|
-
}
|
|
1481
|
-
return [
|
|
1482
|
-
"'use strict';",
|
|
1483
|
-
'// ui-fidelity-render runtime script (generated by cibuild at YAML conversion time)',
|
|
1484
|
-
'var CONFIG = ' + JSON.stringify(config) + ';',
|
|
1485
|
-
RENDER_SCRIPT_GENERATORS,
|
|
1486
|
-
RENDER_SCRIPT_SOURCE_DIGEST_STAGE,
|
|
1487
|
-
main,
|
|
1488
|
-
].join('\n');
|
|
1489
|
-
}
|
|
1490
|
-
/**
|
|
1491
|
-
* ui-fidelity-render step executor.
|
|
1492
|
-
* Returns a node-kind StepDef whose script performs the render at runtime on
|
|
1493
|
-
* the macOS runner.
|
|
1494
|
-
*/
|
|
1495
|
-
export class UiFidelityRenderStepExecutor extends BaseStepExecutor {
|
|
1496
|
-
getValidationRequirements(inputs, _env, _config) {
|
|
1497
|
-
if (inputs && (inputs.package_source === 'inputs' || inputs.package_source === 'url')) {
|
|
1498
|
-
// The package ships as .ci/inputs/package.tar.gz at run time (uploaded
|
|
1499
|
-
// run input, or downloaded from the signed package_url in url mode), so
|
|
1500
|
-
// neither package_path nor target is required up front.
|
|
1501
|
-
const requirements = [
|
|
1502
|
-
this.requireCommand('swift', 'Swift toolchain used to build and run the render harness', 'Install Xcode (or the Swift toolchain) on the runner'),
|
|
1503
|
-
this.requireCommand('tar', 'Archive tool used to extract the shipped package', 'Install tar on the runner'),
|
|
1504
|
-
];
|
|
1505
|
-
if (inputs.package_source === 'url') {
|
|
1506
|
-
requirements.push(this.requireCommand('curl', 'Downloader used to fetch the render package by signed URL', 'Install curl on the runner'));
|
|
1507
|
-
}
|
|
1508
|
-
return requirements;
|
|
1509
|
-
}
|
|
1510
|
-
return [
|
|
1511
|
-
this.requireInput('package_path', inputs, 'Path to the SwiftPM package containing the screens'),
|
|
1512
|
-
this.requireInput('target', inputs, 'SPM library product to import in the render harness'),
|
|
1513
|
-
this.requireCommand('swift', 'Swift toolchain used to build and run the render harness', 'Install Xcode (or the Swift toolchain) on the runner'),
|
|
1514
|
-
];
|
|
1515
|
-
}
|
|
1516
|
-
async execute(inputs, _env, _config) {
|
|
1517
|
-
const stepName = 'ui-fidelity-render';
|
|
1518
|
-
// Omitted package_source means v1 repo behavior with a byte-identical
|
|
1519
|
-
// generated script; an explicit value (repo or inputs) is recorded in
|
|
1520
|
-
// the script config and the result document.
|
|
1521
|
-
const rawPackageSource = this.getInput(inputs, 'package_source', undefined);
|
|
1522
|
-
let packageSource;
|
|
1523
|
-
if (rawPackageSource !== undefined) {
|
|
1524
|
-
const value = String(rawPackageSource);
|
|
1525
|
-
if (!PACKAGE_SOURCES.includes(value)) {
|
|
1526
|
-
throw new Error(`Invalid package_source '${value}' for step '${stepName}': ` +
|
|
1527
|
-
`expected one of: ${PACKAGE_SOURCES.join(', ')}`);
|
|
1528
|
-
}
|
|
1529
|
-
packageSource = value;
|
|
1530
|
-
}
|
|
1531
|
-
let packagePath = null;
|
|
1532
|
-
let target = null;
|
|
1533
|
-
let packageUrl;
|
|
1534
|
-
if (packageSource === 'inputs' || packageSource === 'url') {
|
|
1535
|
-
// package_path names no local directory here: inputs mode ignores it
|
|
1536
|
-
// (the package arrives as a run input); url mode requires it as the
|
|
1537
|
-
// repo-relative prefix committed reference paths sit under, inside the
|
|
1538
|
-
// downloaded package. target is optional and discovered from the
|
|
1539
|
-
// shipped manifest when the package declares exactly one library
|
|
1540
|
-
// product.
|
|
1541
|
-
if (packageSource === 'url') {
|
|
1542
|
-
packageUrl = String(this.getRequiredInput(inputs, 'package_url', stepName));
|
|
1543
|
-
if (!/^https?:\/\//.test(packageUrl)) {
|
|
1544
|
-
throw new Error(`Invalid package_url for step '${stepName}': expected an http(s) URL`);
|
|
1545
|
-
}
|
|
1546
|
-
packagePath = String(this.getRequiredInput(inputs, 'package_path', stepName));
|
|
1547
|
-
}
|
|
1548
|
-
const configuredTarget = this.getInput(inputs, 'target', undefined);
|
|
1549
|
-
target = configuredTarget === undefined ? null : String(configuredTarget);
|
|
1550
|
-
}
|
|
1551
|
-
else {
|
|
1552
|
-
packagePath = String(this.getRequiredInput(inputs, 'package_path', stepName));
|
|
1553
|
-
target = String(this.getRequiredInput(inputs, 'target', stepName));
|
|
1554
|
-
}
|
|
1555
|
-
if (target !== null && !/^[A-Za-z_][A-Za-z0-9_]*$/.test(target)) {
|
|
1556
|
-
throw new Error(`Invalid target '${target}' for step '${stepName}': ` +
|
|
1557
|
-
'must be a Swift module identifier (letters, digits, underscores)');
|
|
1558
|
-
}
|
|
1559
|
-
// reference_source mirrors package_source: validate when provided, but only
|
|
1560
|
-
// record (and patch the script for) the non-default "repo" so omitting it —
|
|
1561
|
-
// or setting the default "inputs" — keeps the v1 reference block intact.
|
|
1562
|
-
const rawReferenceSource = this.getInput(inputs, 'reference_source', undefined);
|
|
1563
|
-
let referenceSource;
|
|
1564
|
-
if (rawReferenceSource !== undefined) {
|
|
1565
|
-
const value = String(rawReferenceSource);
|
|
1566
|
-
if (!REFERENCE_SOURCES.includes(value)) {
|
|
1567
|
-
throw new Error(`Invalid reference_source '${value}' for step '${stepName}': ` +
|
|
1568
|
-
`expected one of: ${REFERENCE_SOURCES.join(', ')}`);
|
|
1569
|
-
}
|
|
1570
|
-
// URL mode owns reference sourcing (inside the extracted package) —
|
|
1571
|
-
// an explicit reference_source alongside it is a contradiction.
|
|
1572
|
-
if (packageSource === 'url') {
|
|
1573
|
-
throw new Error(`Invalid reference_source for step '${stepName}': ` +
|
|
1574
|
-
"package_source 'url' resolves references inside the downloaded " +
|
|
1575
|
-
'package; omit reference_source');
|
|
1576
|
-
}
|
|
1577
|
-
if (value === 'repo') {
|
|
1578
|
-
referenceSource = 'repo';
|
|
1579
|
-
}
|
|
1580
|
-
}
|
|
1581
|
-
const { width, height } = parseRenderSize(String(this.getInput(inputs, 'render_size', DEFAULT_RENDER_SIZE)));
|
|
1582
|
-
const scale = parseScale(this.getInput(inputs, 'scale', DEFAULT_SCALE));
|
|
1583
|
-
const config = { packagePath, target, width, height, scale };
|
|
1584
|
-
if (packageSource !== undefined) {
|
|
1585
|
-
config.packageSource = packageSource;
|
|
1586
|
-
}
|
|
1587
|
-
if (packageUrl !== undefined) {
|
|
1588
|
-
config.packageUrl = packageUrl;
|
|
1589
|
-
}
|
|
1590
|
-
if (referenceSource !== undefined) {
|
|
1591
|
-
config.referenceSource = referenceSource;
|
|
1592
|
-
}
|
|
1593
|
-
const script = generateRenderScript(config);
|
|
1594
|
-
return this.createNodeStep(script, stepName);
|
|
1595
|
-
}
|
|
1596
|
-
}
|
|
1597
|
-
//# sourceMappingURL=ui-fidelity-render.js.map
|