name: Kanban options: bundleIdPrefix: dev.rzen.indie deploymentTarget: macOS: "26.0" xcodeVersion: "26.0" defaultConfig: Debug packages: Yams: url: https://github.com/jpsim/Yams.git from: 6.0.0 # apple/swift-markdown — the card window's Preview parser (05-card-window.md ▸ Preview). # Pinned to the next *minor* rather than the next major because the package is pre-1.0: a # `from:` range here would silently accept 0.9's breaking changes, which is not what `from:` # means for Yams at 6.x. swift-markdown: url: https://github.com/apple/swift-markdown.git minorVersion: 0.8.0 # The About window (both editions): app icon, version/build from the stamped Info.plist, and # the bundled LICENSE/CHANGELOG documents. Pre-1.0 like swift-markdown, same pinning logic. IndieAbout: url: https://git.rzen.dev/rzen/indie-about.git minorVersion: 0.2.2 settings: base: SWIFT_VERSION: "6.0" MACOSX_DEPLOYMENT_TARGET: "26.0" DEVELOPMENT_TEAM: ${APPLE_TEAM_ID} ENABLE_USER_SCRIPT_SANDBOXING: NO # The edition split (12-editions.md ▸ Targets). Two app targets compiled from one source tree: # base builds `Kanban/` alone, Pro builds `Kanban/` *plus* `KanbanPro/`. There is no build flag # and no `#if` in shared code — "why a real split and not feature flags" (12) is answered by the # file list, so an edition difference is always a file one target compiles and the other does not. # libgit2 arrives on the Pro target with pro-m1; the target, its entitlements and its source root # exist now so that milestone lands without re-splitting anything. # # YAML anchors (`&name` / `*name`) carry the genuinely identical parts across the two apps and the # two unit-test bundles. They are resolved by the YAML parser before XcodeGen sees the file, so # they cost nothing at generate time and cannot drift the way copy-paste does. targets: # MARK: - Lanework (base) Kanban: type: application platform: macOS sources: - Kanban # The bundled template store (09-templates.md ▸ Definition format): real board folders, # copied into `Contents/Resources/Templates/` verbatim as a folder reference — the same # arrangement `Fixtures/` uses below, and for the same reason. A template is a board, so it # must reach the bundle as directories on disk, not as flattened resource files. - path: Templates type: folder buildPhase: resources # The About window's documents (LICENSE, CHANGELOG) — bundled so IndieAbout can render # them in-app. Apple-inline-Markdown subset only: no `#` headings, no hard-wrapped # paragraphs (every newline renders literally). - path: LICENSE.md type: file buildPhase: resources - path: CHANGELOG.md type: file buildPhase: resources dependencies: &appDependencies - package: Yams - package: swift-markdown product: Markdown - package: IndieAbout postBuildScripts: &updateBuildInfo - script: '"${SRCROOT}/../indie-skills/skills/app-versioning/scripts/update_build_info.sh"' name: Update Build Info shell: /bin/sh basedOnDependencyAnalysis: false inputFiles: - $(TARGET_BUILD_DIR)/$(INFOPLIST_PATH) - $(DWARF_DSYM_FOLDER_PATH)/$(DWARF_DSYM_FILE_NAME)/Contents/Info.plist settings: base: # Base keeps the current identifier — it is the app that ships first, so nothing # re-wires (12 ▸ Targets, ruled 2026-07-27). PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.Kanban MARKETING_VERSION: "2.0" INFOPLIST_FILE: Kanban/Info.plist CODE_SIGN_ENTITLEMENTS: Kanban/Kanban.entitlements GENERATE_INFOPLIST_FILE: false SWIFT_STRICT_CONCURRENCY: complete ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon # MARK: - Lanework Pro KanbanPro: type: application platform: macOS sources: # The shared tree, minus the three files that are base's *identity* rather than its code. # (XcodeGen already keeps `Info.plist` and `.entitlements` out of build phases; excluding # them here keeps them out of the Pro target's file list too, so there is exactly one # plist and one entitlements file visible per app.) - path: Kanban excludes: - Info.plist - Kanban.entitlements - Assets.xcassets - Assets.xcassets/** # Base's About-box configuration carries the one-line Pro signpost (12-editions.md ▸ # Quiet signposts); Pro compiles its own `KanbanPro/Edition/EditionAbout.swift` twin # without it — the edition split's file-level doctrine, applied to a string. - App/EditionAbout.swift # Pro's own source root — reserved now, filled by pro-m1. See KanbanPro/Edition/ProEdition.swift. - path: KanbanPro excludes: - Info.plist - KanbanPro.entitlements # The same bundled template store — the inventory is the app's, not an edition's. - path: Templates type: folder buildPhase: resources # The About documents, same files as base — one license, one changelog, two bundles. - path: LICENSE.md type: file buildPhase: resources - path: CHANGELOG.md type: file buildPhase: resources dependencies: *appDependencies postBuildScripts: *updateBuildInfo settings: base: PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanPro MARKETING_VERSION: "2.0" INFOPLIST_FILE: KanbanPro/Info.plist CODE_SIGN_ENTITLEMENTS: KanbanPro/KanbanPro.entitlements GENERATE_INFOPLIST_FILE: false SWIFT_STRICT_CONCURRENCY: complete ASSETCATALOG_COMPILER_APPICON_NAME: AppIconPro # Spelled out because the whole shared-test-sources arrangement below hangs on it: the two # apps must have *different* Swift module names, since both emit `.swiftmodule` # into the same `Build/Products//` and equal names would have one app silently # overwrite the other's testable interface. PRODUCT_MODULE_NAME: KanbanPro # MARK: - Unit tests # # One suite, run twice — once hosted by each app. Every test in `KanbanTests/` is # edition-agnostic (it exercises the storage format, the live store and the UI model, none of # which differ between editions), so duplicating the *sources* would be duplicating the thing # that is identical; what is worth running twice is the suite against each *binary*, which is # what these two targets do. The Pro bundle compiles the very same files — it is the same # `sources` list, by anchor — against `KanbanPro`. KanbanTests: type: bundle.unit-test platform: macOS sources: &unitTestSources - KanbanTests - path: Fixtures type: folder buildPhase: resources dependencies: - target: Kanban settings: base: PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanTests GENERATE_INFOPLIST_FILE: true SWIFT_STRICT_CONCURRENCY: complete KanbanProTests: type: bundle.unit-test platform: macOS sources: *unitTestSources dependencies: - target: KanbanPro settings: base: PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanProTests GENERATE_INFOPLIST_FILE: true SWIFT_STRICT_CONCURRENCY: complete # The shared sources say `@testable import Kanban`, and they should: they are testing the # app, not an edition. `-module-alias` (Swift 5.7+) resolves that import to the Pro # module for this bundle only — the supported way to bind one source name to a different # module, and it keeps the 56 test files edition-blind instead of littering them with # conditional imports. OTHER_SWIFT_FLAGS: -module-alias Kanban=KanbanPro # MARK: - UI tests # # Base-only, deliberately: these tests launch the real app and drive its menu bar, which is the # slowest, most environment-dependent part of the suite, and a second copy would double it to # re-assert something the Pro unit bundle already proves (it launches the Pro app as its test host # on every run). The accessibility audits (10-accessibility.md ▸ Verification) are edition-blind — # every surface they walk is built from the shared tree. KanbanUITests: type: bundle.ui-testing platform: macOS sources: # The manual verification document lives with the tests it belongs to (10-accessibility.md # ▸ Verification: "A manual VoiceOver smoke script lives with the test plan"), so it is listed # in the project — visible where the suite is — but with no build phase: it is documentation, # not a resource the bundle should carry. - path: KanbanUITests excludes: - "**/*.md" - path: KanbanUITests/AccessibilityVerification.md buildPhase: none dependencies: - target: Kanban settings: base: PRODUCT_BUNDLE_IDENTIFIER: dev.rzen.indie.KanbanUITests GENERATE_INFOPLIST_FILE: true SWIFT_STRICT_CONCURRENCY: complete schemes: # `Kanban` is unchanged, on purpose: `xcodebuild … -scheme Kanban` is the established command # for this repo and base is the app it has always meant. Kanban: build: targets: Kanban: all run: config: Debug test: config: Debug gatherCoverageData: false targets: - KanbanTests - KanbanUITests profile: config: Release analyze: config: Debug archive: config: Release LaneworkPro: build: targets: KanbanPro: all run: config: Debug test: config: Debug gatherCoverageData: false targets: - KanbanProTests profile: config: Release analyze: config: Debug archive: config: Release