diff --git a/.changeset/sync-cli-release-dependencies.md b/.changeset/sync-cli-release-dependencies.md new file mode 100644 index 000000000..a7e0ad374 --- /dev/null +++ b/.changeset/sync-cli-release-dependencies.md @@ -0,0 +1,5 @@ +--- +"@fluojs/cli": patch +--- + +Refresh CLI starter dependency ranges after Changesets versioning so released scaffolds use the updated package versions. diff --git a/tooling/release/cli-published-dependencies.test.ts b/tooling/release/cli-published-dependencies.test.ts index 24658c8f2..ad7d8b6f7 100644 --- a/tooling/release/cli-published-dependencies.test.ts +++ b/tooling/release/cli-published-dependencies.test.ts @@ -4,6 +4,7 @@ import { tmpdir } from 'node:os'; import { dirname, join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { afterEach, describe, expect, it } from 'vitest'; +import { runVersionPackages } from './version-packages.mjs'; const repositoryRoot = fileURLToPath(new URL('../../', import.meta.url)); const releaseVersions: Readonly> = JSON.parse(readFileSync( @@ -35,6 +36,57 @@ function createReleaseFixture() { } describe('CLI release metadata generation', () => { + it('refreshes metadata from manifests changed during versioning', () => { + const fixture = createReleaseFixture(); + execFileSync(process.execPath, [fixture.script], { timeout: 10_000 }); + const nextVersions = { ...releaseVersions, '@fluojs/config': '9.8.7', '@fluojs/react': '0.12.3' }; + + runVersionPackages({ + workspacePackageManifests: () => [], + runChangesetsVersion: () => { + for (const [name, version] of Object.entries(nextVersions)) { + writeFileSync( + join(fixture.root, 'packages', name.slice('@fluojs/'.length), 'package.json'), + JSON.stringify({ name, version, type: 'module' }), + ); + } + }, + execFileSync: (command, args) => { + expect(command).toBe(process.execPath); + expect(args).toEqual([join(repositoryRoot, 'packages/cli/scripts/generate-published-internal-dependencies.mjs')]); + // Execute only the copied generator: its import.meta.url confines all writes to the fixture. + execFileSync(process.execPath, [fixture.script], { timeout: 10_000 }); + }, + }); + + const output = execFileSync(process.execPath, [ + '--input-type=module', '--eval', + `import { PUBLISHED_INTERNAL_DEPENDENCIES } from ${JSON.stringify(pathToFileURL(fixture.output).href)}; console.log(JSON.stringify(PUBLISHED_INTERNAL_DEPENDENCIES));`, + ], { encoding: 'utf8', timeout: 10_000 }); + expect(JSON.parse(output)).toEqual(Object.fromEntries( + Object.entries(nextVersions).filter(([name]) => name !== '@fluojs/cli') + .map(([name, version]) => [name, `^${version}`]), + )); + }); + + it('propagates generator failure after versioning without replacing metadata', () => { + const fixture = createReleaseFixture(); + const previousOutput = readFileSync(fixture.output, 'utf8'); + + expect(() => runVersionPackages({ + workspacePackageManifests: () => [], + runChangesetsVersion: () => { + writeFileSync(join(fixture.root, 'packages/config/package.json'), JSON.stringify({ + name: '@fluojs/config', version: '', + })); + }, + execFileSync: () => { + execFileSync(process.execPath, [fixture.script], { stdio: 'pipe', timeout: 10_000 }); + }, + })).toThrowError('Command failed:'); + expect(readFileSync(fixture.output, 'utf8')).toBe(previousOutput); + }); + it('refreshes stale metadata using independent release manifest versions', () => { // Given: a fixture copies stale generated metadata before applying new release manifests. const fixture = createReleaseFixture(); diff --git a/tooling/release/version-packages.d.mts b/tooling/release/version-packages.d.mts index a305dcf7c..f9710d16d 100644 --- a/tooling/release/version-packages.d.mts +++ b/tooling/release/version-packages.d.mts @@ -28,6 +28,11 @@ export type ChangesetsVersionDependencies = { }; export type VersionPackagesDependencies = { + readonly execFileSync?: ( + command: string, + args: readonly string[], + options: { readonly stdio: 'inherit' }, + ) => unknown; readonly existsSync?: (targetPath: string) => boolean; readonly readFileSync?: (targetPath: string, encoding: 'utf8') => string; readonly runChangesetsVersion?: (dependencies?: ChangesetsVersionDependencies) => void; diff --git a/tooling/release/version-packages.mjs b/tooling/release/version-packages.mjs index 9e8e4b840..7e77b7aa0 100644 --- a/tooling/release/version-packages.mjs +++ b/tooling/release/version-packages.mjs @@ -1,4 +1,4 @@ -import { spawnSync } from 'node:child_process'; +import { execFileSync, spawnSync } from 'node:child_process'; import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -90,6 +90,7 @@ export function runChangesetsVersion(dependencies = {}) { export function runVersionPackages(dependencies = {}) { const { + execFileSync: executeFile = execFileSync, existsSync: pathExists = existsSync, readFileSync: readFile = readFileSync, runChangesetsVersion: runVersion = runChangesetsVersion, @@ -102,6 +103,9 @@ export function runVersionPackages(dependencies = {}) { ); runVersion(); + executeFile(process.execPath, [join(repoRoot, 'packages/cli/scripts/generate-published-internal-dependencies.mjs')], { + stdio: 'inherit', + }); const normalizedChangelogPaths = []; const pendingWrites = []; diff --git a/tooling/release/version-packages.test.ts b/tooling/release/version-packages.test.ts index e2efdf2d6..1f8d6be22 100644 --- a/tooling/release/version-packages.test.ts +++ b/tooling/release/version-packages.test.ts @@ -1,4 +1,4 @@ -import { describe, expect, it } from 'vitest'; +import { describe, expect, it, vi } from 'vitest'; import { normalizePackageChangelog, runChangesetsVersion, runVersionPackages } from './version-packages.mjs'; describe('runChangesetsVersion', () => { @@ -130,6 +130,7 @@ describe('runVersionPackages', () => { const writes: string[] = []; const result = runVersionPackages({ + execFileSync: () => {}, existsSync: (targetPath) => changelogs.has(targetPath), readFileSync: (targetPath) => { const changelog = changelogs.get(targetPath); @@ -187,6 +188,7 @@ describe('runVersionPackages', () => { expect(() => runVersionPackages({ + execFileSync: () => {}, existsSync: (targetPath) => changelogs.has(targetPath), readFileSync: (targetPath) => { const changelog = changelogs.get(targetPath); @@ -225,4 +227,16 @@ describe('runVersionPackages', () => { ).toThrowError('Package CHANGELOG.md must contain at most one `## [Unreleased]` section.'); expect(writes).toEqual([]); }); + + it('propagates versioning failure without running the CLI dependency generator', () => { + const failure = new Error('versioning failed'); + const generate = vi.fn(); + + expect(() => runVersionPackages({ + execFileSync: generate, + runChangesetsVersion: () => { throw failure; }, + workspacePackageManifests: () => [], + })).toThrow(failure); + expect(generate).not.toHaveBeenCalled(); + }); });