From 9892d0ee37ec7d1c8595992be10e61ca6f9d2ee6 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 22 Jul 2026 15:58:34 +1000 Subject: [PATCH 01/17] Generate the secret into the build dir, not the repo The decrypted `SPCredentials.swift` no longer lands in the checkout: each consumer target's build phase declares its own `$(DERIVED_FILE_DIR)` output and the script writes there, via `SCRIPT_OUTPUT_FILE_0`. Part of AINFRA-2731, applying the pattern doapple and woocommerce-ios developed and Gravatar-SDK-iOS solidified. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.8 --- Scripts/Build-Phases/copy-secret.sh | 12 ++++++++++-- Scripts/Build-Phases/copy-secret.xcfilelist | 6 ++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index 4d441cf6f..d6a5d27c5 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -56,7 +56,15 @@ EXAMPLE_SECRETS_FILE="${SRCROOT}/Simplenote/SPCredentials-demo.swift" ensure_is_in_input_files_list $SECRETS_FILE ensure_is_in_input_files_list $EXAMPLE_SECRETS_FILE -SECRETS_DESTINATION_FILE="${SRCROOT}/Simplenote/Credentials/SPCredentials.swift" +# The destination comes from the build phase's `outputPaths`, which Xcode +# exposes as SCRIPT_OUTPUT_FILE_N. Each consumer target writes into its own +# $(DERIVED_FILE_DIR), keeping the decrypted secret out of the checkout. +if [ "${SCRIPT_OUTPUT_FILE_COUNT:-0}" -lt 1 ]; then + echo "error: No output file given. Declare the destination in the build phase's output files list." + exit 1 +fi + +SECRETS_DESTINATION_FILE="${SCRIPT_OUTPUT_FILE_0}" mkdir -p $(dirname "$SECRETS_DESTINATION_FILE") if cmp --silent -- ${SECRETS_FILE} ${SECRETS_DESTINATION_FILE}; then @@ -73,7 +81,7 @@ fi # No secrets file found. Use the example secrets file as a last resort, unless # building for Release. -COULD_NOT_FIND_SECRET_MSG="Could not find secrets file at ${SECRETS_DESTINATION_FILE}. This is likely due to the source secrets being missing from ${SECRETS_ROOT}" +COULD_NOT_FIND_SECRET_MSG="Could not find secrets file at ${SECRETS_FILE}" INTERNAL_CONTRIBUTOR_MSG="If you are an internal contributor, run \`bundle exec fastlane run configure_apply\` to update your secrets" case $CONFIGURATION in diff --git a/Scripts/Build-Phases/copy-secret.xcfilelist b/Scripts/Build-Phases/copy-secret.xcfilelist index 36c78f3c8..ddaf4fd87 100644 --- a/Scripts/Build-Phases/copy-secret.xcfilelist +++ b/Scripts/Build-Phases/copy-secret.xcfilelist @@ -1,5 +1,7 @@ -# Inputs for the build phase run script marshalling the secrets for the app, -# currently running from the SimplenoteSecrets aggregate targets +# Inputs for the build phase run script marshalling the secrets for the app. +# Shared between the per-target "Copy Secret" build phases on the Simplenote and +# IntentsExtension targets — each phase writes its own SPCredentials.swift into +# $(DERIVED_FILE_DIR). ${HOME}/.configure/simplenote-macos/secrets/SPCredentials.swift ${SRCROOT}/Simplenote/SPCredentials-demo.swift # Add the script itself as an input, so the build system will know to run it From 6459560b80850bd53fe789c31df706439f1b3d3c Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 22 Jul 2026 15:58:44 +1000 Subject: [PATCH 02/17] Compile SPCredentials.swift out of DerivedSources MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the `SimplenoteSecrets` aggregate target — which wrote the plaintext into `Simplenote/Credentials/` — with a `Copy Secret` phase on each target that actually compiles the file, writing into that target's own `$(DERIVED_FILE_DIR)`. The two file references are `DERIVED_FILE_DIR`-relative, so Xcode shows them in red with no resolvable path until a build runs. `Simplenote/DerivedSources/README.md` exists to stop the next reader "fixing" that. `Simplenote/Credentials/` stays gitignored so a stale decrypted copy from before this change cannot be committed. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.8 --- .gitignore | 5 +- Simplenote.xcodeproj/project.pbxproj | 121 ++++++++++++--------------- Simplenote/DerivedSources/README.md | 35 ++++++++ 3 files changed, 91 insertions(+), 70 deletions(-) create mode 100644 Simplenote/DerivedSources/README.md diff --git a/.gitignore b/.gitignore index 23b8512d1..a09fb2c40 100644 --- a/.gitignore +++ b/.gitignore @@ -25,10 +25,13 @@ DerivedData .idea/ *.hmap *.xcscmblueprint -Simplenote/DerivedSources/ +Simplenote/DerivedSources/* +!Simplenote/DerivedSources/README.md # Settings Simplenote/config.plist + +# Stale copies of the decrypted credentials, from before they moved to DerivedSources Simplenote/Credentials/ # Bundler diff --git a/Simplenote.xcodeproj/project.pbxproj b/Simplenote.xcodeproj/project.pbxproj index 30d64403f..65d9204e4 100644 --- a/Simplenote.xcodeproj/project.pbxproj +++ b/Simplenote.xcodeproj/project.pbxproj @@ -6,20 +6,6 @@ objectVersion = 54; objects = { -/* Begin PBXAggregateTarget section */ - B52D0EC5230DCAD7003F799D /* SimplenoteSecrets */ = { - isa = PBXAggregateTarget; - buildConfigurationList = B52D0EC8230DCAD7003F799D /* Build configuration list for PBXAggregateTarget "SimplenoteSecrets" */; - buildPhases = ( - B52D0EC9230DCB15003F799D /* ShellScript */, - ); - dependencies = ( - ); - name = SimplenoteSecrets; - productName = GenerateCredentials; - }; -/* End PBXAggregateTarget section */ - /* Begin PBXBuildFile section */ 3700E97721C1E390004771C9 /* SPTextAttachment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3700E97521C1E390004771C9 /* SPTextAttachment.swift */; }; 3712FC831FE1ACAA008544AC /* Storage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3712FC701FE1ACA9008544AC /* Storage.swift */; }; @@ -233,7 +219,7 @@ B5E086362448EA3C00DEF476 /* TagTableCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E086342448EA3C00DEF476 /* TagTableCellView.swift */; }; B5E086392448EE9D00DEF476 /* NSTableView+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E086372448EE9D00DEF476 /* NSTableView+Simplenote.swift */; }; B5E0863C2449012700DEF476 /* NSImage+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E0863A2449012700DEF476 /* NSImage+Simplenote.swift */; }; - B5E196C2230F5F5300F5658A /* SPCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E196C0230F5F5300F5658A /* SPCredentials.swift */; }; + B5E196C2230F5F5300F5658A /* SPCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C27310000000000000A003 /* SPCredentials.swift */; }; B5E8E41224575C990098892B /* ToolbarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E8E41024575C990098892B /* ToolbarState.swift */; }; B5E96B671BDE732500D707F5 /* AuthViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = B5E96B651BDE732500D707F5 /* AuthViewController.m */; }; B5EB3AD22458B9940089858D /* NSNotification+Simplenote.m in Sources */ = {isa = PBXBuildFile; fileRef = B5EB3AD02458B9940089858D /* NSNotification+Simplenote.m */; }; @@ -264,7 +250,7 @@ BA2BF3402C07C75500A7C894 /* FindNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2BF33F2C07C75500A7C894 /* FindNoteIntentHandler.swift */; }; BA2C65CF26FE996A00FA84E1 /* NSButton+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA2C65CA26FE996100FA84E1 /* NSButton+Extensions.swift */; }; BA4C6D18264CAAF800B723A7 /* URLRequest+Simplenote.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA4C6D17264CAAF800B723A7 /* URLRequest+Simplenote.swift */; }; - BA4F223E2C1255A500144EDA /* SPCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E196C0230F5F5300F5658A /* SPCredentials.swift */; }; + BA4F223E2C1255A500144EDA /* SPCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8C27310000000000000A004 /* SPCredentials.swift */; }; BA54F2462C0E63C700DBCE9D /* AppendNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA54F2452C0E63C700DBCE9D /* AppendNoteIntentHandler.swift */; }; BA54F2482C0E6A6900DBCE9D /* CreateNewNoteIntentHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA54F2472C0E6A6900DBCE9D /* CreateNewNoteIntentHandler.swift */; }; BA553F0927065E20007737E9 /* FontSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BA553F0727065E20007737E9 /* FontSettings.swift */; }; @@ -340,13 +326,6 @@ /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ - B52D0ECE230DCEAC003F799D /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = 26F72A7F14032D2900A7935E /* Project object */; - proxyType = 1; - remoteGlobalIDString = B52D0EC5230DCAD7003F799D; - remoteInfo = GenerateCredentials; - }; BAB261692BFFD0AF009A98D7 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 26F72A7F14032D2900A7935E /* Project object */; @@ -679,7 +658,9 @@ B5E086342448EA3C00DEF476 /* TagTableCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagTableCellView.swift; sourceTree = ""; }; B5E086372448EE9D00DEF476 /* NSTableView+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Simplenote.swift"; sourceTree = ""; }; B5E0863A2449012700DEF476 /* NSImage+Simplenote.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSImage+Simplenote.swift"; sourceTree = ""; }; - B5E196C0230F5F5300F5658A /* SPCredentials.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SPCredentials.swift; sourceTree = ""; }; + A8C27310000000000000A003 /* SPCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPCredentials.swift; sourceTree = DERIVED_FILE_DIR; }; + A8C27310000000000000A004 /* SPCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SPCredentials.swift; sourceTree = DERIVED_FILE_DIR; }; + A8C27310000000000000A008 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; B5E8E41024575C990098892B /* ToolbarState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarState.swift; sourceTree = ""; }; B5E96B641BDE732500D707F5 /* AuthViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AuthViewController.h; sourceTree = ""; }; B5E96B651BDE732500D707F5 /* AuthViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AuthViewController.m; sourceTree = ""; }; @@ -897,7 +878,7 @@ B53BF1A224AC388F00938C34 /* Controllers */, B5CBB05A241971240003C271 /* Converters */, B557A0E825BB0DC700E5313E /* Coordinators */, - B52D0ECA230DCE22003F799D /* Credentials */, + A8C27310000000000000A005 /* DerivedSources */, B51E9FE322E64473004F16B4 /* Extensions */, B574CA71242D539400F8D02F /* Handlers */, 463774DB171F111600E2E333 /* Models */, @@ -1190,12 +1171,30 @@ name = Extensions; sourceTree = ""; }; - B52D0ECA230DCE22003F799D /* Credentials */ = { + A8C27310000000000000A005 /* DerivedSources */ = { + isa = PBXGroup; + children = ( + A8C27310000000000000A006 /* Simplenote */, + A8C27310000000000000A007 /* IntentsExtension */, + A8C27310000000000000A008 /* README.md */, + ); + path = DerivedSources; + sourceTree = ""; + }; + A8C27310000000000000A006 /* Simplenote */ = { + isa = PBXGroup; + children = ( + A8C27310000000000000A003 /* SPCredentials.swift */, + ); + name = Simplenote; + sourceTree = ""; + }; + A8C27310000000000000A007 /* IntentsExtension */ = { isa = PBXGroup; children = ( - B5E196C0230F5F5300F5658A /* SPCredentials.swift */, + A8C27310000000000000A004 /* SPCredentials.swift */, ); - path = Credentials; + name = IntentsExtension; sourceTree = ""; }; B52EE32C25927D5D00347F2B /* Notes */ = { @@ -1697,6 +1696,7 @@ buildConfigurationList = 466FFF2C17CC10A800399652 /* Build configuration list for PBXNativeTarget "Simplenote" */; buildPhases = ( BAAA71F42C07B8BB00244C01 /* Swiftlint */, + A8C27310000000000000A001 /* Copy Secret */, 466FFEA717CC10A800399652 /* Sources */, 466FFEDC17CC10A800399652 /* Frameworks */, 466FFEE517CC10A800399652 /* Resources */, @@ -1706,7 +1706,6 @@ buildRules = ( ); dependencies = ( - B52D0ECF230DCEAC003F799D /* PBXTargetDependency */, BAB2616A2BFFD0AF009A98D7 /* PBXTargetDependency */, ); name = Simplenote; @@ -1743,6 +1742,7 @@ isa = PBXNativeTarget; buildConfigurationList = BAB2616F2BFFD0AF009A98D7 /* Build configuration list for PBXNativeTarget "IntentsExtension" */; buildPhases = ( + A8C27310000000000000A002 /* Copy Secret */, BAB2615E2BFFD0AF009A98D7 /* Sources */, BAB2615F2BFFD0AF009A98D7 /* Frameworks */, BAB261602BFFD0AF009A98D7 /* Resources */, @@ -1775,10 +1775,6 @@ LastSwiftMigration = 0920; ProvisioningStyle = Manual; }; - B52D0EC5230DCAD7003F799D = { - CreatedOnToolsVersion = 11.0; - ProvisioningStyle = Manual; - }; B5CBB068241976230003C271 = { CreatedOnToolsVersion = 11.3.1; ProvisioningStyle = Manual; @@ -1830,7 +1826,6 @@ projectRoot = ""; targets = ( 466FFEA617CC10A800399652 /* Simplenote */, - B52D0EC5230DCAD7003F799D /* SimplenoteSecrets */, B5CBB068241976230003C271 /* SimplenoteTests */, BAB261612BFFD0AF009A98D7 /* IntentsExtension */, ); @@ -1884,9 +1879,28 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - B52D0EC9230DCB15003F799D /* ShellScript */ = { + A8C27310000000000000A001 /* Copy Secret */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${SRCROOT}/Scripts/Build-Phases/copy-secret.xcfilelist", + ); + inputPaths = ( + ); + name = "Copy Secret"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/SPCredentials.swift", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "${SRCROOT}/Scripts/Build-Phases/copy-secret.sh\n"; + }; + A8C27310000000000000A002 /* Copy Secret */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); @@ -1895,10 +1909,11 @@ ); inputPaths = ( ); + name = "Copy Secret"; outputFileListPaths = ( ); outputPaths = ( - "$(SRCROOT)/Simplenote/Credentials/SPCredentials.swift", + "$(DERIVED_FILE_DIR)/SPCredentials.swift", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -2228,11 +2243,6 @@ /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ - B52D0ECF230DCEAC003F799D /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - target = B52D0EC5230DCAD7003F799D /* SimplenoteSecrets */; - targetProxy = B52D0ECE230DCEAC003F799D /* PBXContainerItemProxy */; - }; BAB2616A2BFFD0AF009A98D7 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = BAB261612BFFD0AF009A98D7 /* IntentsExtension */; @@ -2546,24 +2556,6 @@ }; name = Release; }; - B52D0EC6230DCAD7003F799D /* Debug */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEAD_CODE_STRIPPING = YES; - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Debug; - }; - B52D0EC7230DCAD7003F799D /* Release */ = { - isa = XCBuildConfiguration; - buildSettings = { - DEAD_CODE_STRIPPING = YES; - MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)"; - PRODUCT_NAME = "$(TARGET_NAME)"; - }; - name = Release; - }; B5CBB071241976230003C271 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2759,15 +2751,6 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; - B52D0EC8230DCAD7003F799D /* Build configuration list for PBXAggregateTarget "SimplenoteSecrets" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - B52D0EC6230DCAD7003F799D /* Debug */, - B52D0EC7230DCAD7003F799D /* Release */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; - }; B5CBB070241976230003C271 /* Build configuration list for PBXNativeTarget "SimplenoteTests" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Simplenote/DerivedSources/README.md b/Simplenote/DerivedSources/README.md new file mode 100644 index 000000000..a87b641d6 --- /dev/null +++ b/Simplenote/DerivedSources/README.md @@ -0,0 +1,35 @@ +# DerivedSources + +Xcode shows two `SPCredentials.swift` entries in the project navigator under +`DerivedSources/Simplenote/` and `DerivedSources/IntentsExtension/`. +Both appear in **red** with an empty **Full Path** in the File Inspector. +This is **expected** and **harmless**. + +## Why they are red and "not accessible" + +Both file references use `sourceTree = DERIVED_FILE_DIR`. +`DERIVED_FILE_DIR` is a *per-target, per-configuration* build setting that only has a value while a specific target is actively being built. +The project navigator is populated at project-parse time, before any build and with no target or configuration context, so Xcode has no single absolute path to resolve. +The File Inspector still shows **Location: Relative to DERIVED_FILE_DIR**, confirming the declaration is understood — there just isn't one path to display, because it differs between the `Simplenote` and `IntentsExtension` targets, and per configuration. + +At **build time** Xcode resolves `DERIVED_FILE_DIR` per target, a `Copy Secret` script build phase on each consumer target writes `SPCredentials.swift` into that target's own derived sources directory under Derived Data, and the Swift compiler reads it back from that same location. +The actual paths look like: + +``` +~/Library/Developer/Xcode/DerivedData/Simplenote-/ + Build/Intermediates.noindex/Simplenote.build// + Simplenote.build/DerivedSources/SPCredentials.swift # Simplenote target + IntentsExtension.build/DerivedSources/SPCredentials.swift # IntentsExtension target +``` + +Search a build log for `Applying Production Secrets` or `Applying Example Secrets` to see the exact path for the current build. + +## How to actually view a generated file + +1. Build the target at least once. +2. Right-click the red `SPCredentials.swift` entry in the navigator and pick **Show in Finder** — Xcode opens the per-target derived sources folder for the most recent build of that target. + +## The generation pipeline itself + +Lives in `Scripts/Build-Phases/copy-secret.sh` and its `copy-secret.xcfilelist`. +The script copies the decrypted credentials from outside the checkout, falling back to `Simplenote/SPCredentials-demo.swift` for non-Release builds so the app compiles without secrets. From 94b786c3cfc2bc6cb373ed2dcc4550668298f16a Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 22 Jul 2026 15:58:49 +1000 Subject: [PATCH 03/17] Drop the manual credentials setup step The build phase now falls back to the demo credentials on its own, so copying them into place by hand is both unnecessary and, since the destination moved out of the checkout, wrong. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 4.8 --- readme.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/readme.md b/readme.md index c4107a669..8b0e8a0c6 100644 --- a/readme.md +++ b/readme.md @@ -52,14 +52,8 @@ Simplenote is powered by the [Simperium Sync'ing protocol](https://www.simperium **⚠️ Please note → We're not accepting any new Simperium accounts at this time.** - -Please copy the **testing Simperium credentials** as follows: - -``` -mkdir -p Simplenote/Credentials && cp Simplenote/SPCredentials-demo.swift Simplenote/Credentials/SPCredentials.swift -``` - -This will allow you to compile and run the app on a device or a simulator. +Refer to the `simperium*` properties in `SPCredentials`. +The type is generated at build time from internal secrets when available, falling back to `Simplenote/SPCredentials-demo.swift` otherwise. _Note: Simplenote API features such as sharing and publishing will not work with development builds._ From fcd149d8103f6291c1c69cefbdd46bab952d8d81 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 14:02:59 +1000 Subject: [PATCH 04/17] Use the env-lookup bash shebang in copy-secret Matches `Scripts/build-phases/generate-secrets.sh` in Gravatar-SDK-iOS, whose DerivedData secret generation this branch is modelled on. Style only: `#!/bin/bash -euo pipefail` does apply all three options on macOS. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- Scripts/Build-Phases/copy-secret.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index d6a5d27c5..2d89632f5 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -1,4 +1,6 @@ -#!/bin/bash -euo pipefail +#!/usr/bin/env bash + +set -euo pipefail # To help the Xcode build system optimize the build, we want to ensure each of # the secrets we want to copy is defined as an input file for the run script From e4ae1b152b54a5ded4add5a2335810ff90591e6d Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 14:03:58 +1000 Subject: [PATCH 05/17] Quote the path expansions in copy-secret MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `$(dirname …)` and the `cmp` operands were unquoted, so a `DERIVED_FILE_DIR` containing a space — an Xcode path the script does not control — split into multiple words. Verified against a destination under `dir with space/`. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- Scripts/Build-Phases/copy-secret.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index 2d89632f5..f1558eaf4 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -24,7 +24,7 @@ function ensure_is_in_input_files_list() { fi file_to_find=$1 - if [ $SCRIPT_INPUT_FILE_LIST_COUNT -eq 0 ]; then + if [ "$SCRIPT_INPUT_FILE_LIST_COUNT" -eq 0 ]; then echo "error: No input file list given (.xcfilelist). Cannot continue." exit 1 fi @@ -55,8 +55,8 @@ SECRETS_ROOT="${HOME}/.configure/simplenote-macos/secrets" SECRETS_FILE="${SECRETS_ROOT}/SPCredentials.swift" EXAMPLE_SECRETS_FILE="${SRCROOT}/Simplenote/SPCredentials-demo.swift" -ensure_is_in_input_files_list $SECRETS_FILE -ensure_is_in_input_files_list $EXAMPLE_SECRETS_FILE +ensure_is_in_input_files_list "$SECRETS_FILE" +ensure_is_in_input_files_list "$EXAMPLE_SECRETS_FILE" # The destination comes from the build phase's `outputPaths`, which Xcode # exposes as SCRIPT_OUTPUT_FILE_N. Each consumer target writes into its own @@ -67,9 +67,9 @@ if [ "${SCRIPT_OUTPUT_FILE_COUNT:-0}" -lt 1 ]; then fi SECRETS_DESTINATION_FILE="${SCRIPT_OUTPUT_FILE_0}" -mkdir -p $(dirname "$SECRETS_DESTINATION_FILE") +mkdir -p "$(dirname "$SECRETS_DESTINATION_FILE")" -if cmp --silent -- ${SECRETS_FILE} ${SECRETS_DESTINATION_FILE}; then +if cmp --silent -- "$SECRETS_FILE" "$SECRETS_DESTINATION_FILE"; then echo "☑️ Credentials were not modified. Skipping..." exit 0 fi @@ -86,7 +86,7 @@ fi COULD_NOT_FIND_SECRET_MSG="Could not find secrets file at ${SECRETS_FILE}" INTERNAL_CONTRIBUTOR_MSG="If you are an internal contributor, run \`bundle exec fastlane run configure_apply\` to update your secrets" -case $CONFIGURATION in +case "$CONFIGURATION" in Release) echo "error: $COULD_NOT_FIND_SECRET_MSG. Cannot continue Release build. $INTERNAL_CONTRIBUTOR_MSG and try again. External contributors should not need to perform a Release build." exit 1 From ec768d4e2527e7be6e86e9392566f57fa5efe7c3 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 14:04:54 +1000 Subject: [PATCH 06/17] Funnel both secret sources through one apply Mirrors the `apply()` helper in Gravatar-SDK-iOS's `generate-secrets.sh`. The build log now names the source path instead of labelling it "Production" or "Example"; the following commit drops the `DerivedSources/README.md` pointer to those old strings. Keeps `cp -v`, which Gravatar does not need: with two consumer targets writing to different `DERIVED_FILE_DIR`s, the destination in the log is what tells you which target's copy you are looking at. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- Scripts/Build-Phases/copy-secret.sh | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index f1558eaf4..ba5c42f71 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -69,15 +69,20 @@ fi SECRETS_DESTINATION_FILE="${SCRIPT_OUTPUT_FILE_0}" mkdir -p "$(dirname "$SECRETS_DESTINATION_FILE")" +# `cp -v` names the destination, which differs per consumer target. +apply() { + echo "Applying secrets from ${1}" + cp -v "$1" "$SECRETS_DESTINATION_FILE" + exit 0 +} + if cmp --silent -- "$SECRETS_FILE" "$SECRETS_DESTINATION_FILE"; then echo "☑️ Credentials were not modified. Skipping..." exit 0 fi if [ -f "$SECRETS_FILE" ]; then - echo "Applying Production Secrets" - cp -v "$SECRETS_FILE" "${SECRETS_DESTINATION_FILE}" - exit 0 + apply "$SECRETS_FILE" fi # No secrets file found. Use the example secrets file as a last resort, unless @@ -93,7 +98,6 @@ case "$CONFIGURATION" in ;; *) echo "warning: $COULD_NOT_FIND_SECRET_MSG. Falling back to $EXAMPLE_SECRETS_FILE. In a Release build, this would be an error. $INTERNAL_CONTRIBUTOR_MSG and try again. If you are an external contributor, you can ignore this warning." - echo "Applying Example Secrets" - cp -v "$EXAMPLE_SECRETS_FILE" "$SECRETS_DESTINATION_FILE" + apply "$EXAMPLE_SECRETS_FILE" ;; esac From bee377135794e219b8010530dbe78b3bbd61f485 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 14:05:37 +1000 Subject: [PATCH 07/17] Trim the DerivedSources README to the surprise MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Matches the length and scope of `Demo/Demo/Generated/README.md` in Gravatar-SDK-iOS: say why the reference is red, say not to delete it, point at the script. The dropped material — why `DERIVED_FILE_DIR` cannot resolve at parse time, a sample Derived Data tree, how to reveal a generated file in Finder — explained Xcode rather than this project. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- Simplenote/DerivedSources/README.md | 36 ++++------------------------- 1 file changed, 5 insertions(+), 31 deletions(-) diff --git a/Simplenote/DerivedSources/README.md b/Simplenote/DerivedSources/README.md index a87b641d6..63e6edb0e 100644 --- a/Simplenote/DerivedSources/README.md +++ b/Simplenote/DerivedSources/README.md @@ -1,35 +1,9 @@ # DerivedSources -Xcode shows two `SPCredentials.swift` entries in the project navigator under -`DerivedSources/Simplenote/` and `DerivedSources/IntentsExtension/`. -Both appear in **red** with an empty **Full Path** in the File Inspector. -This is **expected** and **harmless**. +The two `SPCredentials.swift` entries here — one per consumer target, under `Simplenote/` and `IntentsExtension/` — are shown in red in Xcode and `Open Quickly` cannot find them. +That is expected: they do not exist in the repository. -## Why they are red and "not accessible" +The `Copy Secret` build phase writes each into its own target's `$(DERIVED_FILE_DIR)`, from the decrypted credentials under `~/.configure/simplenote-macos/secrets/`, or, missing those and outside a Release build, from the committed `SPCredentials-demo.swift`. +See `Scripts/Build-Phases/copy-secret.sh`. -Both file references use `sourceTree = DERIVED_FILE_DIR`. -`DERIVED_FILE_DIR` is a *per-target, per-configuration* build setting that only has a value while a specific target is actively being built. -The project navigator is populated at project-parse time, before any build and with no target or configuration context, so Xcode has no single absolute path to resolve. -The File Inspector still shows **Location: Relative to DERIVED_FILE_DIR**, confirming the declaration is understood — there just isn't one path to display, because it differs between the `Simplenote` and `IntentsExtension` targets, and per configuration. - -At **build time** Xcode resolves `DERIVED_FILE_DIR` per target, a `Copy Secret` script build phase on each consumer target writes `SPCredentials.swift` into that target's own derived sources directory under Derived Data, and the Swift compiler reads it back from that same location. -The actual paths look like: - -``` -~/Library/Developer/Xcode/DerivedData/Simplenote-/ - Build/Intermediates.noindex/Simplenote.build// - Simplenote.build/DerivedSources/SPCredentials.swift # Simplenote target - IntentsExtension.build/DerivedSources/SPCredentials.swift # IntentsExtension target -``` - -Search a build log for `Applying Production Secrets` or `Applying Example Secrets` to see the exact path for the current build. - -## How to actually view a generated file - -1. Build the target at least once. -2. Right-click the red `SPCredentials.swift` entry in the navigator and pick **Show in Finder** — Xcode opens the per-target derived sources folder for the most recent build of that target. - -## The generation pipeline itself - -Lives in `Scripts/Build-Phases/copy-secret.sh` and its `copy-secret.xcfilelist`. -The script copies the decrypted credentials from outside the checkout, falling back to `Simplenote/SPCredentials-demo.swift` for non-Release builds so the app compiles without secrets. +Do not delete the red references. The app will not compile without them! From 8c74cd07c30eca511915762ad7f41872b68bf31e Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 14:48:21 +1000 Subject: [PATCH 08/17] Move `cp -v` explanation close to `cp -v` usage --- Scripts/Build-Phases/copy-secret.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index ba5c42f71..ae9f5cf4a 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -69,9 +69,9 @@ fi SECRETS_DESTINATION_FILE="${SCRIPT_OUTPUT_FILE_0}" mkdir -p "$(dirname "$SECRETS_DESTINATION_FILE")" -# `cp -v` names the destination, which differs per consumer target. apply() { echo "Applying secrets from ${1}" + # `cp -v` names the destination, which differs per consumer target. cp -v "$1" "$SECRETS_DESTINATION_FILE" exit 0 } From 753f9a8c110ab8da719e14b0965e8e19cad57c6f Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 14:48:56 +1000 Subject: [PATCH 09/17] Remove now-outdated ignore rule for in-repo credentials --- .gitignore | 3 --- 1 file changed, 3 deletions(-) diff --git a/.gitignore b/.gitignore index a09fb2c40..6fff7302e 100644 --- a/.gitignore +++ b/.gitignore @@ -31,9 +31,6 @@ Simplenote/DerivedSources/* # Settings Simplenote/config.plist -# Stale copies of the decrypted credentials, from before they moved to DerivedSources -Simplenote/Credentials/ - # Bundler /vendor/ /vendor/bundle/ From 800d08b3d141e4f54775233a0632daf190528862 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 15:35:54 +1000 Subject: [PATCH 10/17] Add script description and reorder a few steps --- Scripts/Build-Phases/copy-secret.sh | 38 ++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index ae9f5cf4a..a53051143 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -2,6 +2,22 @@ set -euo pipefail +# Materialize secrets into the target's DERIVED_FILE_DIR so the decrypted +# credentials never land in the repo checkout. +# +# The compiled file comes from one of two sources, in order: +# +# 1. ${SECRETS_ROOT}, for internal contributors. `bundle exec fastlane run +# configure_apply` decrypts it there, outside the repo; this phase only +# reads it. +# 2. Simplenote/SPCredentials-demo.swift, committed, so external contributors +# can build without any secrets. Under Release the missing secrets are an +# error instead. + +SECRETS_ROOT="${HOME}/.configure/simplenote-macos/secrets" +SECRETS_FILE="${SECRETS_ROOT}/SPCredentials.swift" +EXAMPLE_SECRETS_FILE="${SRCROOT}/Simplenote/SPCredentials-demo.swift" + # To help the Xcode build system optimize the build, we want to ensure each of # the secrets we want to copy is defined as an input file for the run script # build phase. @@ -22,13 +38,14 @@ function ensure_is_in_input_files_list() { echo "error: Input file list verification needs a path to verify!" exit 1 fi - file_to_find=$1 if [ "$SCRIPT_INPUT_FILE_LIST_COUNT" -eq 0 ]; then echo "error: No input file list given (.xcfilelist). Cannot continue." exit 1 fi + file_to_find=$1 + i=0 found=false while [[ $i -lt $SCRIPT_INPUT_FILE_LIST_COUNT && "$found" = false ]] @@ -37,24 +54,21 @@ function ensure_is_in_input_files_list() { file_list_resolved_var_name=SCRIPT_INPUT_FILE_LIST_${i} # The following reads the processed xcfilelist line by line looking for # the given file - while read input_file; do + while read -r input_file; do if [ "$file_to_find" == "$input_file" ]; then found=true break fi done <"${!file_list_resolved_var_name}" - let i=i+1 + (( i=i+1 )) done + if [ "$found" = false ]; then echo "error: Could not find $file_to_find as an input to the build phase. Add $file_to_find to the input files list using the .xcfilelist." exit 1 fi } -SECRETS_ROOT="${HOME}/.configure/simplenote-macos/secrets" -SECRETS_FILE="${SECRETS_ROOT}/SPCredentials.swift" -EXAMPLE_SECRETS_FILE="${SRCROOT}/Simplenote/SPCredentials-demo.swift" - ensure_is_in_input_files_list "$SECRETS_FILE" ensure_is_in_input_files_list "$EXAMPLE_SECRETS_FILE" @@ -69,6 +83,11 @@ fi SECRETS_DESTINATION_FILE="${SCRIPT_OUTPUT_FILE_0}" mkdir -p "$(dirname "$SECRETS_DESTINATION_FILE")" +if cmp --silent -- "$SECRETS_FILE" "$SECRETS_DESTINATION_FILE"; then + echo "☑️ Credentials were not modified. Skipping..." + exit 0 +fi + apply() { echo "Applying secrets from ${1}" # `cp -v` names the destination, which differs per consumer target. @@ -76,11 +95,6 @@ apply() { exit 0 } -if cmp --silent -- "$SECRETS_FILE" "$SECRETS_DESTINATION_FILE"; then - echo "☑️ Credentials were not modified. Skipping..." - exit 0 -fi - if [ -f "$SECRETS_FILE" ]; then apply "$SECRETS_FILE" fi From 490bb9f768f820bfc9d189c7afec68c8fda73d7d Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 16:15:12 +1000 Subject: [PATCH 11/17] Introduce template + external credentials This mirrors the agreed implementation in Gravatar SDK iOS. See https://github.com/Automattic/Gravatar-SDK-iOS/pull/824 --- .gitignore | 2 + Scripts/Build-Phases/copy-secret.sh | 44 ++++++++----------- Scripts/Build-Phases/copy-secret.xcfilelist | 2 +- Simplenote/DerivedSources/README.md | 3 +- ...emo.swift => SPCredentials.template.swift} | 5 ++- readme.md | 4 +- 6 files changed, 30 insertions(+), 30 deletions(-) rename Simplenote/{SPCredentials-demo.swift => SPCredentials.template.swift} (87%) diff --git a/.gitignore b/.gitignore index 6fff7302e..1ba49be18 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,8 @@ build/ .configure-files/* !.configure-files/*.enc +Simplenote/SPCredentials.external-contributors.swift + # Swift Package Manager .build diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index a53051143..598018ad2 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -10,13 +10,17 @@ set -euo pipefail # 1. ${SECRETS_ROOT}, for internal contributors. `bundle exec fastlane run # configure_apply` decrypts it there, outside the repo; this phase only # reads it. -# 2. Simplenote/SPCredentials-demo.swift, committed, so external contributors -# can build without any secrets. Under Release the missing secrets are an -# error instead. +# 2. Simplenote/SPCredentials.external-contributors.swift — gitignored, so +# external contributors can keep their own Simperium credentials with +# little-to-no risk of committing them, starting from a copy of the +# committed template. +# +# If neither is present, the build will fail. SECRETS_ROOT="${HOME}/.configure/simplenote-macos/secrets" SECRETS_FILE="${SECRETS_ROOT}/SPCredentials.swift" -EXAMPLE_SECRETS_FILE="${SRCROOT}/Simplenote/SPCredentials-demo.swift" +TEMPLATE_SECRETS_FILE="${SRCROOT}/Simplenote/SPCredentials.template.swift" +EXTERNAL_SECRETS_FILE="${SRCROOT}/Simplenote/SPCredentials.external-contributors.swift" # To help the Xcode build system optimize the build, we want to ensure each of # the secrets we want to copy is defined as an input file for the run script @@ -70,7 +74,7 @@ function ensure_is_in_input_files_list() { } ensure_is_in_input_files_list "$SECRETS_FILE" -ensure_is_in_input_files_list "$EXAMPLE_SECRETS_FILE" +ensure_is_in_input_files_list "$EXTERNAL_SECRETS_FILE" # The destination comes from the build phase's `outputPaths`, which Xcode # exposes as SCRIPT_OUTPUT_FILE_N. Each consumer target writes into its own @@ -83,12 +87,12 @@ fi SECRETS_DESTINATION_FILE="${SCRIPT_OUTPUT_FILE_0}" mkdir -p "$(dirname "$SECRETS_DESTINATION_FILE")" -if cmp --silent -- "$SECRETS_FILE" "$SECRETS_DESTINATION_FILE"; then - echo "☑️ Credentials were not modified. Skipping..." - exit 0 -fi - apply() { + if cmp --silent -- "$1" "$SECRETS_DESTINATION_FILE"; then + echo "☑️ Credentials were not modified. Skipping..." + exit 0 + fi + echo "Applying secrets from ${1}" # `cp -v` names the destination, which differs per consumer target. cp -v "$1" "$SECRETS_DESTINATION_FILE" @@ -99,19 +103,9 @@ if [ -f "$SECRETS_FILE" ]; then apply "$SECRETS_FILE" fi -# No secrets file found. Use the example secrets file as a last resort, unless -# building for Release. - -COULD_NOT_FIND_SECRET_MSG="Could not find secrets file at ${SECRETS_FILE}" -INTERNAL_CONTRIBUTOR_MSG="If you are an internal contributor, run \`bundle exec fastlane run configure_apply\` to update your secrets" +if [ -f "$EXTERNAL_SECRETS_FILE" ]; then + apply "$EXTERNAL_SECRETS_FILE" +fi -case "$CONFIGURATION" in - Release) - echo "error: $COULD_NOT_FIND_SECRET_MSG. Cannot continue Release build. $INTERNAL_CONTRIBUTOR_MSG and try again. External contributors should not need to perform a Release build." - exit 1 - ;; - *) - echo "warning: $COULD_NOT_FIND_SECRET_MSG. Falling back to $EXAMPLE_SECRETS_FILE. In a Release build, this would be an error. $INTERNAL_CONTRIBUTOR_MSG and try again. If you are an external contributor, you can ignore this warning." - apply "$EXAMPLE_SECRETS_FILE" - ;; -esac +echo "error: No secrets found! Internal contributors: run \`bundle exec fastlane run configure_apply\`. External contributors: copy '${TEMPLATE_SECRETS_FILE}' to '${EXTERNAL_SECRETS_FILE}', fill in your own Simperium credentials, and build again." +exit 1 diff --git a/Scripts/Build-Phases/copy-secret.xcfilelist b/Scripts/Build-Phases/copy-secret.xcfilelist index ddaf4fd87..fbed4ce90 100644 --- a/Scripts/Build-Phases/copy-secret.xcfilelist +++ b/Scripts/Build-Phases/copy-secret.xcfilelist @@ -3,7 +3,7 @@ # IntentsExtension targets — each phase writes its own SPCredentials.swift into # $(DERIVED_FILE_DIR). ${HOME}/.configure/simplenote-macos/secrets/SPCredentials.swift -${SRCROOT}/Simplenote/SPCredentials-demo.swift +${SRCROOT}/Simplenote/SPCredentials.external-contributors.swift # Add the script itself as an input, so the build system will know to run it # if it changes ${SRCROOT}/Scripts/Build-Phases/copy-secret.sh diff --git a/Simplenote/DerivedSources/README.md b/Simplenote/DerivedSources/README.md index 63e6edb0e..662da27a0 100644 --- a/Simplenote/DerivedSources/README.md +++ b/Simplenote/DerivedSources/README.md @@ -3,7 +3,8 @@ The two `SPCredentials.swift` entries here — one per consumer target, under `Simplenote/` and `IntentsExtension/` — are shown in red in Xcode and `Open Quickly` cannot find them. That is expected: they do not exist in the repository. -The `Copy Secret` build phase writes each into its own target's `$(DERIVED_FILE_DIR)`, from the decrypted credentials under `~/.configure/simplenote-macos/secrets/`, or, missing those and outside a Release build, from the committed `SPCredentials-demo.swift`. +The `Copy Secret` build phase writes each into its own target's `$(DERIVED_FILE_DIR)`, from the decrypted credentials under `~/.configure/simplenote-macos/secrets/`, or, missing those, from `SPCredentials.external-contributors.swift`. +Missing both fails the build. See `Scripts/Build-Phases/copy-secret.sh`. Do not delete the red references. The app will not compile without them! diff --git a/Simplenote/SPCredentials-demo.swift b/Simplenote/SPCredentials.template.swift similarity index 87% rename from Simplenote/SPCredentials-demo.swift rename to Simplenote/SPCredentials.template.swift index a27b459d8..758bca71e 100644 --- a/Simplenote/SPCredentials-demo.swift +++ b/Simplenote/SPCredentials.template.swift @@ -1,4 +1,7 @@ -/// Simplenote API Demo Credentials +/// Simplenote API Credentials Template +/// +/// Copy to `SPCredentials.external-contributors.swift` and fill in your own +/// credentials. That path is gitignored. /// import Foundation diff --git a/readme.md b/readme.md index 8b0e8a0c6..a15195e7f 100644 --- a/readme.md +++ b/readme.md @@ -52,8 +52,8 @@ Simplenote is powered by the [Simperium Sync'ing protocol](https://www.simperium **⚠️ Please note → We're not accepting any new Simperium accounts at this time.** -Refer to the `simperium*` properties in `SPCredentials`. -The type is generated at build time from internal secrets when available, falling back to `Simplenote/SPCredentials-demo.swift` otherwise. +Credentials live in the `simperium*` properties of the `SPCredentials` type, which is generated at build time from production credentials kept outside the checkout, falling back to user-specified ones. +When it finds neither, the build fails with instructions on how to provide them. _Note: Simplenote API features such as sharing and publishing will not work with development builds._ From 0a476b9f3fcc270ad320ef7423c66358f22d501c Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 19:43:34 +1000 Subject: [PATCH 12/17] Decrypt secrets before the CI test build The `test` lane never ran `configure_apply`, unlike `test_app_store_build`, so this job only ever built against the demo credentials the build phase fell back to. Removing that fallback left it with no credentials at all. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- .buildkite/commands/build-and-test.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.buildkite/commands/build-and-test.sh b/.buildkite/commands/build-and-test.sh index e8901c0b6..81fee19a7 100644 --- a/.buildkite/commands/build-and-test.sh +++ b/.buildkite/commands/build-and-test.sh @@ -3,5 +3,12 @@ echo "--- :rubygems: Setting up Gems" install_gems +echo "--- :closed_lock_with_key: Decrypting Secrets" +# TODO: Drop once a8c-secrets is adopted — its CLI is light enough for the +# `Copy Secret` build phase to decrypt on its own, the way Gravatar-SDK-iOS +# does. `bundle` is not readily available to a build phase, so `configure_apply` +# cannot move there. +bundle exec fastlane run configure_apply + echo "--- :hammer_and_wrench: Build and Test" bundle exec fastlane test From dc96fb70914775c18b96c3fcd943bd2fe2648526 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Thu, 30 Jul 2026 19:55:21 +1000 Subject: [PATCH 13/17] Run the CI tests as an external contributor MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the `configure_apply` added a commit ago. The unit tests never needed real credentials — the removed demo fallback was supplying them — so copying the template keeps this job out of the secret store entirely, and turns it into a regression test for the flow the readme gives external contributors. `verify-app-store-target-builds` still decrypts, so the internal path stays covered. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- .buildkite/commands/build-and-test.sh | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.buildkite/commands/build-and-test.sh b/.buildkite/commands/build-and-test.sh index 81fee19a7..91b057b6d 100644 --- a/.buildkite/commands/build-and-test.sh +++ b/.buildkite/commands/build-and-test.sh @@ -3,12 +3,11 @@ echo "--- :rubygems: Setting up Gems" install_gems -echo "--- :closed_lock_with_key: Decrypting Secrets" -# TODO: Drop once a8c-secrets is adopted — its CLI is light enough for the -# `Copy Secret` build phase to decrypt on its own, the way Gravatar-SDK-iOS -# does. `bundle` is not readily available to a build phase, so `configure_apply` -# cannot move there. -bundle exec fastlane run configure_apply +echo "--- :key: Providing Credentials" +# The unit tests do not need real credentials, so take the same path the readme +# gives external contributors. That keeps this job out of the secret store and +# makes it a regression test for the contributor flow. +cp Simplenote/SPCredentials.template.swift Simplenote/SPCredentials.external-contributors.swift echo "--- :hammer_and_wrench: Build and Test" bundle exec fastlane test From f194d6def799c045f2d8e10391b1c4843353f5f6 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 5 Aug 2026 11:03:42 +1000 Subject: [PATCH 14/17] Condense verbose AI comment --- .buildkite/commands/build-and-test.sh | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.buildkite/commands/build-and-test.sh b/.buildkite/commands/build-and-test.sh index 91b057b6d..22b2f5b39 100644 --- a/.buildkite/commands/build-and-test.sh +++ b/.buildkite/commands/build-and-test.sh @@ -3,10 +3,8 @@ echo "--- :rubygems: Setting up Gems" install_gems +# Unit tests don't need real credentials echo "--- :key: Providing Credentials" -# The unit tests do not need real credentials, so take the same path the readme -# gives external contributors. That keeps this job out of the secret store and -# makes it a regression test for the contributor flow. cp Simplenote/SPCredentials.template.swift Simplenote/SPCredentials.external-contributors.swift echo "--- :hammer_and_wrench: Build and Test" From d36a2230c37bfadb85b9bf77f7a9aa2ffe771d9d Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 5 Aug 2026 11:28:22 +1000 Subject: [PATCH 15/17] Drop the skip-if-unchanged check Xcode already skips the phase when neither the declared inputs nor the outputs changed, so `cmp` only ever runs in the cases where the copy is warranted. Same conclusion as Gravatar-SDK-iOS reached for `generate-secrets.sh`. Part of AINFRA-2731. --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- Scripts/Build-Phases/copy-secret.sh | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index 598018ad2..986d2ea92 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -88,11 +88,6 @@ SECRETS_DESTINATION_FILE="${SCRIPT_OUTPUT_FILE_0}" mkdir -p "$(dirname "$SECRETS_DESTINATION_FILE")" apply() { - if cmp --silent -- "$1" "$SECRETS_DESTINATION_FILE"; then - echo "☑️ Credentials were not modified. Skipping..." - exit 0 - fi - echo "Applying secrets from ${1}" # `cp -v` names the destination, which differs per consumer target. cp -v "$1" "$SECRETS_DESTINATION_FILE" From 53218329c0827eef776c8aa9b9c410c2ca293746 Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 5 Aug 2026 11:37:18 +1000 Subject: [PATCH 16/17] Stop advertising distributed testing credentials There are none to distribute since the demo credentials came out: internal contributors decrypt their own, external ones supply their own. https://github.com/Automattic/simplenote-macos/pull/1269#discussion_r3717332585 --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index a15195e7f..beb27fe80 100644 --- a/readme.md +++ b/readme.md @@ -48,7 +48,7 @@ You can also open the project by double clicking on `Simplenote.xcworkspace` fil ## Setup Credentials -Simplenote is powered by the [Simperium Sync'ing protocol](https://www.simperium.com). We distribute **testing credentials** that help us authenticate your application, and verify that the API calls being made are valid. +Simplenote is powered by the [Simperium Sync'ing protocol](https://www.simperium.com), which requires credentials to authenticate the application and verify that the API calls being made are valid. **⚠️ Please note → We're not accepting any new Simperium accounts at this time.** From 609051c96f0eab66d8e9cb719114c8590901016f Mon Sep 17 00:00:00 2001 From: Gio Lodi Date: Wed, 5 Aug 2026 11:37:34 +1000 Subject: [PATCH 17/17] Default the input file list count under `set -u` Without it, a run outside Xcode aborts on an unbound variable instead of printing the error the guard exists to print. https://github.com/Automattic/simplenote-macos/pull/1269#discussion_r3717332605 --- Generated with the help of Claude Code, https://claude.ai/code Co-Authored-By: Claude Code Opus 5 --- Scripts/Build-Phases/copy-secret.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Scripts/Build-Phases/copy-secret.sh b/Scripts/Build-Phases/copy-secret.sh index 986d2ea92..373fd6e50 100755 --- a/Scripts/Build-Phases/copy-secret.sh +++ b/Scripts/Build-Phases/copy-secret.sh @@ -43,7 +43,7 @@ function ensure_is_in_input_files_list() { exit 1 fi - if [ "$SCRIPT_INPUT_FILE_LIST_COUNT" -eq 0 ]; then + if [ "${SCRIPT_INPUT_FILE_LIST_COUNT:-0}" -eq 0 ]; then echo "error: No input file list given (.xcfilelist). Cannot continue." exit 1 fi