Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
changeKind: fix
packages:
- "@typespec/compiler"
---

`tsp init` template `compilerVersion` field now supports semver ranges (e.g., `^0.50.0`). Plain versions like `1.2.3` continue to work as `>=1.2.3` for backward compatibility.
16 changes: 11 additions & 5 deletions packages/compiler/src/init/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -319,12 +319,18 @@ async function promptTemplateSelection(templates: Record<string, any>): Promise<
return templateName;
}

function isTemplateCompatibleWithTspVersion(template: InitTemplate): boolean {
/** @internal */
export function isTemplateCompatibleWithTspVersion(template: InitTemplate): boolean {
const currentCompilerVersion = MANIFEST.version;
return (
template.compilerVersion === undefined ||
semver.gte(currentCompilerVersion, template.compilerVersion)
);
if (template.compilerVersion === undefined) {
return true;
}
// If it's a plain version (e.g., "1.2.3"), treat it as >= for backward compatibility
if (semver.valid(template.compilerVersion)) {
return semver.gte(currentCompilerVersion, template.compilerVersion);
}
// Otherwise treat it as a semver range (e.g., "^1.2.3", ">=1.2.3")
return semver.satisfies(currentCompilerVersion, template.compilerVersion);
}

async function validateTemplate(template: any, index: LoadedTemplateIndex): Promise<boolean> {
Expand Down
45 changes: 43 additions & 2 deletions packages/compiler/test/cli/init.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { afterAll, afterEach, beforeEach, expect, it, vi } from "vitest";
import { afterAll, afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { CliCompilerHost } from "../../src/core/cli/types.js";
import { resolvePath } from "../../src/core/path-utils.js";
import { LogSink } from "../../src/index.js";
import { initTypeSpecProject, InitTypeSpecProjectOptions } from "../../src/init/init.js";
import { InitTemplate } from "../../src/init/init-template.js";
import {
initTypeSpecProject,
InitTypeSpecProjectOptions,
isTemplateCompatibleWithTspVersion,
} from "../../src/init/init.js";
import { UriTemplateSource } from "../../src/init/template-source/index.js";
import { createTestFileSystem } from "../../src/testing/fs.js";
import { TestFileSystem } from "../../src/testing/types.js";
Expand Down Expand Up @@ -308,3 +313,39 @@ it("should fallback to 'latest' when npm registry is unreachable", async () => {
const packageJson = await parseJson(compilerHost, "/tmp/test-project/package.json");
expect(packageJson.dependencies["@typespec/compiler"]).toBe("latest");
});

describe("isTemplateCompatibleWithTspVersion", () => {
function makeTemplate(compilerVersion: string | undefined): InitTemplate {
return {
title: "Test",
description: "Test template",
compilerVersion,
};
}

it("returns true when compilerVersion is undefined", () => {
expect(isTemplateCompatibleWithTspVersion(makeTemplate(undefined))).toBe(true);
});

it("returns true when plain version is less than or equal to current (backward compat: plain version means >=)", () => {
// Current version is 1.14.0; a plain version <= current should be compatible
expect(isTemplateCompatibleWithTspVersion(makeTemplate("0.44.0"))).toBe(true);
expect(isTemplateCompatibleWithTspVersion(makeTemplate("1.0.0"))).toBe(true);
});

it("returns false when plain version is greater than current", () => {
expect(isTemplateCompatibleWithTspVersion(makeTemplate("99.0.0"))).toBe(false);
});

it("returns true when semver range is satisfied by current version", () => {
expect(isTemplateCompatibleWithTspVersion(makeTemplate("^1.0.0"))).toBe(true);
expect(isTemplateCompatibleWithTspVersion(makeTemplate(">=1.0.0"))).toBe(true);
expect(isTemplateCompatibleWithTspVersion(makeTemplate("^1.14.0"))).toBe(true);
});

it("returns false when semver range is not satisfied by current version", () => {
expect(isTemplateCompatibleWithTspVersion(makeTemplate("^2.0.0"))).toBe(false);
expect(isTemplateCompatibleWithTspVersion(makeTemplate(">=99.0.0"))).toBe(false);
expect(isTemplateCompatibleWithTspVersion(makeTemplate("^0.50.0"))).toBe(false);
});
});
Loading