|
12 | 12 |
|
13 | 13 | const { |
14 | 14 | SCAFFOLDER_MARKER, |
| 15 | + SCAFFOLDER_VERSION, |
15 | 16 | emitScaffoldedPackageSwift, |
16 | 17 | scaffoldAll, |
17 | 18 | scaffoldPackageSwiftForDep, |
@@ -256,9 +257,12 @@ describe('emitScaffoldedPackageSwift', () => { |
256 | 257 | }; |
257 | 258 | } |
258 | 259 |
|
259 | | - it('starts with the SCAFFOLDER marker (and NOT the autolinker AUTOGEN marker)', () => { |
| 260 | + it('contains the SCAFFOLDER marker (after the line-1 swift-tools-version directive) and NOT the autolinker AUTOGEN marker', () => { |
260 | 261 | const out = emitScaffoldedPackageSwift(baseSpec()); |
261 | | - expect(out.startsWith(SCAFFOLDER_MARKER)).toBe(true); |
| 262 | + // Line 1 is reserved for the swift-tools-version directive — SPM ignores |
| 263 | + // it elsewhere. The scaffolder marker lives on a subsequent line. |
| 264 | + expect(out.split('\n', 1)[0]).toMatch(/^\/\/ swift-tools-version: /); |
| 265 | + expect(out).toContain(SCAFFOLDER_MARKER); |
262 | 266 | // The autolinker's marker — must be absent so isSelfManagedPackage |
263 | 267 | // treats this file as self-managed. |
264 | 268 | expect(out).not.toContain( |
|
415 | 419 | path.join(depRoot, 'Package.swift'), |
416 | 420 | 'utf8', |
417 | 421 | ); |
418 | | - expect(content.startsWith(SCAFFOLDER_MARKER)).toBe(true); |
| 422 | + // Line 1 is the swift-tools-version directive; the scaffolder marker |
| 423 | + // appears immediately after (still detectable by `isScaffolded` checks |
| 424 | + // that scan the whole file). |
| 425 | + expect(content.split('\n', 1)[0]).toMatch(/^\/\/ swift-tools-version: /); |
| 426 | + expect(content).toContain(SCAFFOLDER_MARKER); |
419 | 427 | }); |
420 | 428 |
|
421 | 429 | it('reports previouslyExisted=false for first-time scaffolds (so the CLI can prompt)', () => { |
|
502 | 510 |
|
503 | 511 | it('skips re-scaffolding when the existing file carries the scaffolder marker AND the same cache slot', () => { |
504 | 512 | makePodspec(); |
505 | | - // Pre-existing scaffold from same slot |
| 513 | + // Pre-existing scaffold from same slot AND current generator version |
| 514 | + // — otherwise the version-bump skip-bypass kicks in. |
506 | 515 | const prior = |
507 | | - SCAFFOLDER_MARKER + '\n// Cache slot: 0.87.0-X/debug\n// rest unchanged'; |
| 516 | + SCAFFOLDER_MARKER + |
| 517 | + `\n// AUTO-SCAFFOLDED-VERSION: ${SCAFFOLDER_VERSION}` + |
| 518 | + '\n// Cache slot: 0.87.0-X/debug\n// rest unchanged'; |
508 | 519 | fs.writeFileSync(path.join(depRoot, 'Package.swift'), prior); |
509 | 520 | const result = scaffoldPackageSwiftForDep( |
510 | 521 | makeDep(), |
@@ -629,3 +640,119 @@ describe('scaffoldAll', () => { |
629 | 640 | ); |
630 | 641 | }); |
631 | 642 | }); |
| 643 | + |
| 644 | +// --------------------------------------------------------------------------- |
| 645 | +// SCAFFOLDER_VERSION — auto-regen when the emitter's output format changes |
| 646 | +// |
| 647 | +// Without versioning, a Package.swift scaffolded by an older generator stays |
| 648 | +// on disk indefinitely (skip-on-marker), even when our template has since |
| 649 | +// been fixed. Bumping SCAFFOLDER_VERSION triggers a one-time regeneration |
| 650 | +// on next scaffold. Edits are persisted via patch-package per the marker |
| 651 | +// comment, so destructive regen here aligns with the documented workflow. |
| 652 | +// --------------------------------------------------------------------------- |
| 653 | + |
| 654 | +describe('SCAFFOLDER_VERSION', () => { |
| 655 | + it('is a positive integer', () => { |
| 656 | + expect(Number.isInteger(SCAFFOLDER_VERSION)).toBe(true); |
| 657 | + expect(SCAFFOLDER_VERSION).toBeGreaterThanOrEqual(1); |
| 658 | + }); |
| 659 | + |
| 660 | + it('emitter writes the current version to the file', () => { |
| 661 | + const out = emitScaffoldedPackageSwift({ |
| 662 | + swiftName: 'foo', |
| 663 | + sources: [], |
| 664 | + headerSearchPaths: [], |
| 665 | + coreReactNative: false, |
| 666 | + siblingNames: [], |
| 667 | + extraFrameworks: [], |
| 668 | + weakFrameworks: [], |
| 669 | + compilerFlags: [], |
| 670 | + publicHeadersPath: null, |
| 671 | + resources: [], |
| 672 | + warnings: [], |
| 673 | + }); |
| 674 | + expect(out).toMatch( |
| 675 | + new RegExp(`^// AUTO-SCAFFOLDED-VERSION: ${SCAFFOLDER_VERSION}$`, 'm'), |
| 676 | + ); |
| 677 | + }); |
| 678 | +}); |
| 679 | + |
| 680 | +describe('scaffoldPackageSwiftForDep — version-based regen', () => { |
| 681 | + let tempDir; |
| 682 | + let depRoot; |
| 683 | + |
| 684 | + beforeEach(() => { |
| 685 | + tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'spm-scaffold-version-')); |
| 686 | + depRoot = path.join(tempDir, 'node_modules', 'react-native-foo'); |
| 687 | + fs.mkdirSync(depRoot, {recursive: true}); |
| 688 | + fs.writeFileSync( |
| 689 | + path.join(depRoot, 'package.json'), |
| 690 | + JSON.stringify({name: 'react-native-foo', version: '1.0.0'}), |
| 691 | + ); |
| 692 | + fs.writeFileSync( |
| 693 | + path.join(depRoot, 'react-native-foo.podspec'), |
| 694 | + "Pod::Spec.new do |s|\n s.name = 'react-native-foo'\n s.version = '1.0'\n s.source_files = 'ios/**/*.{h,m,mm}'\nend\n", |
| 695 | + ); |
| 696 | + }); |
| 697 | + |
| 698 | + afterEach(() => { |
| 699 | + fs.rmSync(tempDir, {recursive: true, force: true}); |
| 700 | + }); |
| 701 | + |
| 702 | + function makeDep() { |
| 703 | + return { |
| 704 | + name: 'react-native-foo', |
| 705 | + root: depRoot, |
| 706 | + platforms: {ios: {}}, |
| 707 | + }; |
| 708 | + } |
| 709 | + |
| 710 | + function makeCtx(overrides = {}) { |
| 711 | + return { |
| 712 | + appRoot: tempDir, |
| 713 | + reactNativeRoot: depRoot, |
| 714 | + force: false, |
| 715 | + dryRun: false, |
| 716 | + cacheSlotLabel: 'SLOT-A/debug', |
| 717 | + skipDeps: new Set(), |
| 718 | + ...overrides, |
| 719 | + }; |
| 720 | + } |
| 721 | + |
| 722 | + it('regenerates a file scaffolded under an older version, even without --force', () => { |
| 723 | + const olderVersion = Math.max(1, SCAFFOLDER_VERSION - 1); |
| 724 | + fs.writeFileSync( |
| 725 | + path.join(depRoot, 'Package.swift'), |
| 726 | + `${SCAFFOLDER_MARKER}\n// AUTO-SCAFFOLDED-VERSION: ${olderVersion}\n// Cache slot: SLOT-A/debug\n`, |
| 727 | + ); |
| 728 | + const result = scaffoldPackageSwiftForDep(makeDep(), makeCtx()); |
| 729 | + expect(result.status).toBe('written'); |
| 730 | + const after = fs.readFileSync(path.join(depRoot, 'Package.swift'), 'utf8'); |
| 731 | + expect(after).toContain( |
| 732 | + `// AUTO-SCAFFOLDED-VERSION: ${SCAFFOLDER_VERSION}`, |
| 733 | + ); |
| 734 | + }); |
| 735 | + |
| 736 | + it('regenerates a marker-tagged file with NO version line (treats as v1)', () => { |
| 737 | + fs.writeFileSync( |
| 738 | + path.join(depRoot, 'Package.swift'), |
| 739 | + `${SCAFFOLDER_MARKER}\n// Cache slot: SLOT-A/debug\n// pre-versioning scaffold\n`, |
| 740 | + ); |
| 741 | + const result = scaffoldPackageSwiftForDep(makeDep(), makeCtx()); |
| 742 | + expect(result.status).toBe('written'); |
| 743 | + const after = fs.readFileSync(path.join(depRoot, 'Package.swift'), 'utf8'); |
| 744 | + expect(after).not.toContain('pre-versioning scaffold'); |
| 745 | + expect(after).toContain( |
| 746 | + `// AUTO-SCAFFOLDED-VERSION: ${SCAFFOLDER_VERSION}`, |
| 747 | + ); |
| 748 | + }); |
| 749 | + |
| 750 | + it('skips when the existing file is already at the current version and slot', () => { |
| 751 | + fs.writeFileSync( |
| 752 | + path.join(depRoot, 'Package.swift'), |
| 753 | + `${SCAFFOLDER_MARKER}\n// AUTO-SCAFFOLDED-VERSION: ${SCAFFOLDER_VERSION}\n// Cache slot: SLOT-A/debug\n`, |
| 754 | + ); |
| 755 | + const result = scaffoldPackageSwiftForDep(makeDep(), makeCtx()); |
| 756 | + expect(result.status).toBe('skipped-scaffolder-marker'); |
| 757 | + }); |
| 758 | +}); |
0 commit comments