From d41ae249b6347ad6f40d06ac1a612392ded06293 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Tue, 4 Feb 2025 22:02:42 -0500 Subject: [PATCH 1/8] comment up goreleaser flow --- cli/combined-shim.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/cli/combined-shim.yml b/cli/combined-shim.yml index 67d9914a3269c..29bff222b46f1 100644 --- a/cli/combined-shim.yml +++ b/cli/combined-shim.yml @@ -2,6 +2,9 @@ project_name: turbo dist: dist +# We just call `npm-native-packages.js` once for each item in goos x goarch +# the scrip generates a scaffold version of each npm package +# goreleaser just copies binary from dist-{os}-{arch}/turbo(.exe) to bin/turbo builds: - id: turbo builder: prebuilt @@ -23,11 +26,15 @@ builds: pre: - cmd: ./scripts/npm-native-packages/npm-native-packages.js {{ .Os }} {{ .Arch }} {{ .Version }} binary: bin/turbo +# goreleaser creates a checksums.txt +# don't think this is in use anywhere? could be in GH release, but not going anywhere checksum: name_template: "checksums.txt" +# feature we do not use snapshot: name_template: "{{ incpatch .Version }}" archives: + # we do nothing with this archive - id: github name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" wrap_in_directory: true @@ -47,6 +54,7 @@ archives: amd64: 64 format: tar.gz files: + # copy only these files into - LICENSE - src: "scripts/npm-native-packages/build/{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}/package.json" dst: "workaround/.." @@ -57,12 +65,14 @@ archives: - src: "scripts/npm-native-packages/build/{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}/bin/*" dst: "bin/" strip_parent: true +# used for GH releases only (which are disabled) changelog: sort: asc filters: exclude: - "^docs:" - "^test:" +# we do nothing with the release section since disabled release: github: owner: vercel @@ -75,4 +85,5 @@ publishers: - name: npm ids: - npm + # all this does is run this command for each archive we made above cmd: "npm publish{{ if .Prerelease }} --tag canary{{ end }} {{ abs .ArtifactPath }}" From 7f09f7e32f20b171981e5ada8fc0f0c8621d772d Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Wed, 5 Feb 2025 15:23:00 -0500 Subject: [PATCH 2/8] feat(releaser): add @turbo/releaser --- packages/turbo-releaser/.eslintrc.js | 26 ++++ packages/turbo-releaser/cli/index.cjs | 20 +++ packages/turbo-releaser/package.json | 32 +++++ packages/turbo-releaser/src/index.ts | 45 +++++++ packages/turbo-releaser/src/native.test.ts | 126 ++++++++++++++++++ packages/turbo-releaser/src/native.ts | 65 +++++++++ .../turbo-releaser/src/operations.test.ts | 70 ++++++++++ packages/turbo-releaser/src/operations.ts | 54 ++++++++ packages/turbo-releaser/src/packager.test.ts | 55 ++++++++ packages/turbo-releaser/src/packager.ts | 35 +++++ packages/turbo-releaser/src/types.ts | 8 ++ packages/turbo-releaser/src/version.test.ts | 29 ++++ packages/turbo-releaser/src/version.ts | 11 ++ packages/turbo-releaser/template/README.md | 3 + packages/turbo-releaser/template/bin/turbo | 15 +++ packages/turbo-releaser/tsconfig.json | 8 ++ packages/turbo-releaser/tsup.config.ts | 9 ++ packages/turbo-releaser/turbo.json | 9 ++ pnpm-lock.yaml | 41 +++++- 19 files changed, 654 insertions(+), 7 deletions(-) create mode 100644 packages/turbo-releaser/.eslintrc.js create mode 100755 packages/turbo-releaser/cli/index.cjs create mode 100644 packages/turbo-releaser/package.json create mode 100644 packages/turbo-releaser/src/index.ts create mode 100644 packages/turbo-releaser/src/native.test.ts create mode 100644 packages/turbo-releaser/src/native.ts create mode 100644 packages/turbo-releaser/src/operations.test.ts create mode 100644 packages/turbo-releaser/src/operations.ts create mode 100644 packages/turbo-releaser/src/packager.test.ts create mode 100644 packages/turbo-releaser/src/packager.ts create mode 100644 packages/turbo-releaser/src/types.ts create mode 100644 packages/turbo-releaser/src/version.test.ts create mode 100644 packages/turbo-releaser/src/version.ts create mode 100644 packages/turbo-releaser/template/README.md create mode 100644 packages/turbo-releaser/template/bin/turbo create mode 100644 packages/turbo-releaser/tsconfig.json create mode 100644 packages/turbo-releaser/tsup.config.ts create mode 100644 packages/turbo-releaser/turbo.json diff --git a/packages/turbo-releaser/.eslintrc.js b/packages/turbo-releaser/.eslintrc.js new file mode 100644 index 0000000000000..1ea2c1f97c3c6 --- /dev/null +++ b/packages/turbo-releaser/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + extends: ["@turbo/eslint-config/library"], + overrides: [ + { + files: ["src/*.ts", "cli/index.cjs"], + rules: { + "no-console": "off", + }, + }, + { + files: ["src/native.ts", "src/operations.ts"], + rules: { + "import/no-default-export": "off", + }, + }, + { + files: ["src/*.test.ts"], + rules: { + // https://github.com/nodejs/node/issues/51292 + "@typescript-eslint/no-floating-promises": "off", + "@typescript-eslint/no-unsafe-member-access": "off", + "@typescript-eslint/no-unsafe-argument": "off", + }, + }, + ], +}; diff --git a/packages/turbo-releaser/cli/index.cjs b/packages/turbo-releaser/cli/index.cjs new file mode 100755 index 0000000000000..d635b1dc6e50c --- /dev/null +++ b/packages/turbo-releaser/cli/index.cjs @@ -0,0 +1,20 @@ +#!/usr/bin/env node + +const { spawnSync } = require("node:child_process"); +const path = require("node:path"); + +const PATH_TO_DIST = path.resolve(__dirname, "../dist"); + +// Define the path to the CLI file +const cliPath = path.resolve(__dirname, PATH_TO_DIST, "index.js"); + +try { + const result = spawnSync("node", [cliPath, ...process.argv.slice(2)], { + stdio: "inherit", + }); + + process.exit(result.status); +} catch (error) { + console.error("Error loading turboreleaser CLI, please re-install", error); + process.exit(1); +} diff --git a/packages/turbo-releaser/package.json b/packages/turbo-releaser/package.json new file mode 100644 index 0000000000000..6917aaa0b75fd --- /dev/null +++ b/packages/turbo-releaser/package.json @@ -0,0 +1,32 @@ +{ + "name": "@turbo/releaser", + "private": true, + "version": "0.0.1", + "bin": { + "turboreleaser": "cli/index.cjs" + }, + "files": [ + "dist", + "template" + ], + "scripts": { + "build": "tsup", + "check-types": "tsc --noEmit", + "test": "node --import tsx --test src/*.test.ts", + "lint": "eslint src/", + "lint:prettier": "prettier -c . --cache --ignore-path=../../.prettierignore" + }, + "dependencies": { + "commander": "^11.0.0", + "tar": "6.1.13" + }, + "devDependencies": { + "@turbo/eslint-config": "workspace:*", + "@turbo/tsconfig": "workspace:*", + "@types/node": "^20", + "@types/tar": "^6.1.4", + "typescript": "5.5.4", + "tsup": "^6.7.0", + "tsx": "4.19.1" + } +} diff --git a/packages/turbo-releaser/src/index.ts b/packages/turbo-releaser/src/index.ts new file mode 100644 index 0000000000000..7b4e01d5c8516 --- /dev/null +++ b/packages/turbo-releaser/src/index.ts @@ -0,0 +1,45 @@ +import { Command } from "commander"; +import { packAndPublish } from "./packager"; +import type { Platform } from "./types"; +import { getVersionInfo } from "./version"; + +const supportedPlatforms: Array = [ + { os: "darwin", arch: "x64" }, + { os: "darwin", arch: "arm64" }, + { os: "linux", arch: "x64" }, + { os: "linux", arch: "arm64" }, + { os: "windows", arch: "x64" }, + { os: "windows", arch: "arm64" }, +]; + +const turboReleaser = new Command(); +turboReleaser + .requiredOption("--version-path ", "Path to the version.txt file") + .option("--skip-publish", "Skip publishing to NPM") + .action(main); + +async function main(options: { skipPublish: boolean; versionPath: string }) { + console.log("Command line options:", options); + console.log("Supported platforms:", supportedPlatforms); + + try { + const { version, npmTag } = await getVersionInfo(options.versionPath); + console.log(`Using version: ${version}, NPM tag: ${npmTag}`); + + await packAndPublish({ + platforms: supportedPlatforms, + version, + skipPublish: options.skipPublish as boolean, + npmTag, + }); + console.log("Packaging and publishing completed successfully"); + } catch (error) { + console.error("Error during packaging and publishing:", error); + process.exit(1); + } +} + +turboReleaser.parseAsync().catch((reason) => { + console.error("Unexpected error. Please report it as a bug:", reason); + process.exit(1); +}); diff --git a/packages/turbo-releaser/src/native.test.ts b/packages/turbo-releaser/src/native.test.ts new file mode 100644 index 0000000000000..0801a67101707 --- /dev/null +++ b/packages/turbo-releaser/src/native.test.ts @@ -0,0 +1,126 @@ +import { describe, it, mock } from "node:test"; +import assert from "node:assert/strict"; +import path from "node:path"; +import fs from "node:fs/promises"; +import native from "./native"; +import type { Platform } from "./types"; + +describe("generateNativePackage", () => { + const outputDir = "/path/to/output"; + + it("should generate package correctly for non-Windows platform", async (t) => { + const mockRm = mock.fn((_path: string) => Promise.resolve()); + const mockMkdir = mock.fn((_path: string) => Promise.resolve()); + const mockCopyFile = mock.fn((_src: string, _dst: string) => + Promise.resolve() + ); + const mockWriteFile = mock.fn((_path: string, _data: string) => + Promise.resolve() + ); + + t.mock.method(fs, "rm", mockRm); + t.mock.method(fs, "mkdir", mockMkdir); + t.mock.method(fs, "copyFile", mockCopyFile); + t.mock.method(fs, "writeFile", mockWriteFile); + + const platform: Platform = { os: "darwin", arch: "x64" }; + const version = "1.0.0"; + await native.generateNativePackage({ platform, version, outputDir }); + + // Assert rm was called correctly + assert.equal(mockRm.mock.calls.length, 1); + assert.equal(mockRm.mock.calls[0].arguments[0], outputDir); + + // Assert mkdir was called correctly + assert.equal(mockMkdir.mock.calls.length, 1); + assert.equal( + mockMkdir.mock.calls[0].arguments[0], + path.join(outputDir, "bin") + ); + + // Assert copyFile was called correctly + assert.equal(mockCopyFile.mock.calls.length, 1); + assert.ok( + mockCopyFile.mock.calls[0].arguments[0].endsWith("template/README.md") + ); + assert.equal( + mockCopyFile.mock.calls[0].arguments[1], + path.join(outputDir, "README.md") + ); + + // Assert writeFile was called correctly + assert.equal(mockWriteFile.mock.calls.length, 1); + const [filePath, content] = mockWriteFile.mock.calls[0].arguments; + assert.equal(filePath, path.join(outputDir, "package.json")); + + const packageJson = JSON.parse(content) as { + name: string; + version: string; + description: string; + os: Array; + cpu: Array; + }; + assert.equal(packageJson.name, `turbo-darwin-${native.archToHuman.x64}`); + assert.equal(packageJson.version, version); + assert.equal( + packageJson.description, + "The darwin-x64 binary for turbo, a monorepo build system." + ); + assert.deepEqual(packageJson.os, ["darwin"]); + assert.deepEqual(packageJson.cpu, ["x64"]); + }); + + it("should handle Windows platform correctly", async (t) => { + const mockRm = mock.fn((_path: string) => Promise.resolve()); + const mockMkdir = mock.fn((_path: string) => Promise.resolve()); + const mockCopyFile = mock.fn((_src: string, _dst: string) => + Promise.resolve() + ); + const mockWriteFile = mock.fn((_path: string, _data: string) => + Promise.resolve() + ); + + t.mock.method(fs, "rm", mockRm); + t.mock.method(fs, "mkdir", mockMkdir); + t.mock.method(fs, "copyFile", mockCopyFile); + t.mock.method(fs, "writeFile", mockWriteFile); + + await native.generateNativePackage({ + platform: { os: "windows", arch: "x64" }, + version: "1.0.0", + outputDir, + }); + + assert.equal(mockCopyFile.mock.calls.length, 2); + assert.ok( + mockCopyFile.mock.calls[0].arguments[0].endsWith("template/bin/turbo") + ); + assert.equal( + mockCopyFile.mock.calls[0].arguments[1], + path.join(outputDir, "bin", "turbo") + ); + }); + + it("should propagate errors", async (t) => { + const mockRm = mock.fn(() => { + throw new Error("Failed to remove directory"); + }); + t.mock.method(fs, "rm", mockRm); + + await assert.rejects( + native.generateNativePackage({ + platform: { os: "linux", arch: "x64" }, + version: "1.2.0", + outputDir, + }), + { message: "Failed to remove directory" } + ); + }); +}); + +describe("archToHuman", () => { + it("should map architectures correctly", () => { + assert.equal(native.archToHuman.x64, "64"); + assert.equal(native.archToHuman.arm64, "arm64"); + }); +}); diff --git a/packages/turbo-releaser/src/native.ts b/packages/turbo-releaser/src/native.ts new file mode 100644 index 0000000000000..5bb75b66dff91 --- /dev/null +++ b/packages/turbo-releaser/src/native.ts @@ -0,0 +1,65 @@ +import { rm, mkdir, copyFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import type { SupportedArch, HumanArch, Platform } from "./types"; + +export const archToHuman: Record = { + x64: "64", + arm64: "arm64", +}; + +const templateDir = path.join(__dirname, "..", "template"); + +async function generateNativePackage({ + platform, + version, + outputDir, +}: { + platform: Platform; + version: string; + outputDir: string; +}) { + const { os, arch } = platform; + console.log(`Generating native package for ${os}-${arch}...`); + + console.log(`Cleaning output directory: ${outputDir}`); + await rm(outputDir, { recursive: true, force: true }); + await mkdir(path.join(outputDir, "bin"), { recursive: true }); + + if (os === "windows") { + console.log("Copying Windows-specific files..."); + await copyFile( + path.join(templateDir, "bin", "turbo"), + path.join(outputDir, "bin", "turbo") + ); + } + + console.log("Copying README.md..."); + await copyFile( + path.join(templateDir, "README.md"), + path.join(outputDir, "README.md") + ); + + console.log("Generating package.json..."); + const packageJson = { + name: `turbo-${os}-${archToHuman[arch]}`, + version, + description: `The ${os}-${arch} binary for turbo, a monorepo build system.`, + repository: "https://github.com/vercel/turborepo", + bugs: "https://github.com/vercel/turborepo/issues", + homepage: "https://turbo.build/repo", + license: "MIT", + os: [os], + cpu: [arch], + preferUnplugged: true, + }; + await writeFile( + path.join(outputDir, "package.json"), + JSON.stringify(packageJson, null, 2) + ); + + console.log(`Native package generated successfully in ${outputDir}`); +} + +// Exported asn an object instead of export keyword, so that these functions +// can be mocked in tests. +export default { generateNativePackage, archToHuman }; diff --git a/packages/turbo-releaser/src/operations.test.ts b/packages/turbo-releaser/src/operations.test.ts new file mode 100644 index 0000000000000..b00602ed415a2 --- /dev/null +++ b/packages/turbo-releaser/src/operations.test.ts @@ -0,0 +1,70 @@ +import { describe, it, mock } from "node:test"; +import fs from "node:fs/promises"; +import assert from "node:assert"; +import path from "node:path"; +import tar from "tar"; +import native from "./native"; +import type { Platform } from "./types"; +import operations from "./operations"; + +describe("packPlatform", () => { + it("should pack a platform correctly", async (t) => { + const mockGenerateNativePackage = mock.fn(); + const mockMkdir = mock.fn(); + const mockCopyFile = mock.fn(); + const mockTarCreate = mock.fn(); + + t.mock.method(native, "generateNativePackage", mockGenerateNativePackage); + t.mock.method(fs, "mkdir", mockMkdir); + t.mock.method(fs, "copyFile", mockCopyFile); + t.mock.method(tar, "create", mockTarCreate); + + const platform: Platform = { os: "darwin", arch: "x64" }; + const version = "1.0.0"; + + const result = await operations.packPlatform(platform, version); + + assert.equal(mockGenerateNativePackage.mock.calls.length, 1); + assert.equal(mockMkdir.mock.calls.length, 1); + assert.equal(mockCopyFile.mock.calls.length, 1); + assert.equal(mockTarCreate.mock.calls.length, 1); + + assert.ok(result.endsWith("darwin-x64-1.0.0.tar.gz")); + assert.ok(path.isAbsolute(result)); + }); + + it("should pack a Windows with .exe", async (t) => { + const mockGenerateNativePackage = mock.fn(); + const mockMkdir = mock.fn(); + const mockCopyFile = mock.fn((_src: string, _dst: string) => + Promise.resolve() + ); + const mockTarCreate = mock.fn(); + + t.mock.method(native, "generateNativePackage", mockGenerateNativePackage); + t.mock.method(fs, "mkdir", mockMkdir); + t.mock.method(fs, "copyFile", mockCopyFile); + t.mock.method(tar, "create", mockTarCreate); + + const platform: Platform = { os: "windows", arch: "x64" }; + const version = "1.0.0"; + + const result = await operations.packPlatform(platform, version); + + assert.ok( + mockCopyFile.mock.calls[0].arguments[0].endsWith("turbo.exe"), + "source ends with .exe" + ); + assert.ok( + mockCopyFile.mock.calls[0].arguments[1].endsWith("turbo.exe"), + "destination ends with .exe" + ); + assert.equal(mockGenerateNativePackage.mock.calls.length, 1); + assert.equal(mockMkdir.mock.calls.length, 1); + assert.equal(mockCopyFile.mock.calls.length, 1); + assert.equal(mockTarCreate.mock.calls.length, 1); + + assert.ok(result.endsWith("windows-x64-1.0.0.tar.gz")); + assert.ok(path.isAbsolute(result)); + }); +}); diff --git a/packages/turbo-releaser/src/operations.ts b/packages/turbo-releaser/src/operations.ts new file mode 100644 index 0000000000000..b65e399643404 --- /dev/null +++ b/packages/turbo-releaser/src/operations.ts @@ -0,0 +1,54 @@ +import path from "node:path"; +import fs from "node:fs/promises"; +import { execSync } from "node:child_process"; +import tar from "tar"; +import native from "./native"; +import type { Platform } from "./types"; + +async function packPlatform( + platform: Platform, + version: string +): Promise { + const { os, arch } = platform; + console.log(`Packing platform: ${os}-${arch}`); + const scaffoldDir = path.join("dist", `${os}-${arch}-${version}`); + + console.log("Generating native package..."); + await native.generateNativePackage({ + platform, + version, + outputDir: scaffoldDir, + }); + + console.log("Moving prebuilt binary..."); + const binaryName = os === "windows" ? "turbo.exe" : "turbo"; + const sourcePath = path.join(`dist-${os}-${arch}`, binaryName); + const destPath = path.join(scaffoldDir, "bin", binaryName); + await fs.mkdir(path.dirname(destPath), { recursive: true }); + await fs.copyFile(sourcePath, destPath); + + console.log("Creating tar.gz..."); + const tarName = `${os}-${arch}-${version}.tar.gz`; + const tarPath = path.join("dist", tarName); + await tar.create( + { + gzip: true, + file: tarPath, + cwd: scaffoldDir, + }, + ["package.json", "README.md", "bin"] + ); + + console.log(`Artifact created: ${tarPath}`); + return path.resolve(tarPath); +} + +function publishArtifacts(artifacts: Array, npmTag: string) { + for (const artifact of artifacts) { + const publishCommand = `npm publish "${artifact}" --tag ${npmTag}`; + console.log(`Executing: ${publishCommand}`); + execSync(publishCommand, { stdio: "inherit" }); + } +} + +export default { packPlatform, publishArtifacts }; diff --git a/packages/turbo-releaser/src/packager.test.ts b/packages/turbo-releaser/src/packager.test.ts new file mode 100644 index 0000000000000..c9568d838e2e3 --- /dev/null +++ b/packages/turbo-releaser/src/packager.test.ts @@ -0,0 +1,55 @@ +import { describe, it, mock } from "node:test"; +import assert from "node:assert/strict"; +import { packAndPublish } from "./packager"; +import type { Platform } from "./types"; +import operations from "./operations"; + +describe("packager", () => { + describe("packAndPublish", () => { + it("should pack and publish for all platforms when skipPublish is false", async (t) => { + const mockPackPlatform = mock.fn(() => + Promise.resolve("/path/to/artifact.tgz") + ); + const mockPublishArtifacts = mock.fn((_paths: Array) => + Promise.resolve() + ); + t.mock.method(operations, "packPlatform", mockPackPlatform); + t.mock.method(operations, "publishArtifacts", mockPublishArtifacts); + + const platforms: Array = [ + { os: "darwin", arch: "x64" }, + { os: "linux", arch: "arm64" }, + ]; + const version = "1.0.0"; + const npmTag = "latest"; + + await packAndPublish({ platforms, version, skipPublish: false, npmTag }); + + assert.equal(mockPackPlatform.mock.calls.length, 2); + assert.equal(mockPublishArtifacts.mock.calls.length, 1); + assert.deepEqual(mockPublishArtifacts.mock.calls[0].arguments, [ + ["/path/to/artifact.tgz", "/path/to/artifact.tgz"], + "latest", + ]); + }); + + it("should pack but not publish when skipPublish is true", async (t) => { + const mockPackPlatform = mock.fn(() => + Promise.resolve("/path/to/artifact.tgz") + ); + const mockPublishArtifacts = mock.fn(); + + t.mock.method(operations, "packPlatform", mockPackPlatform); + t.mock.method(operations, "publishArtifacts", mockPublishArtifacts); + + const platforms: Array = [{ os: "darwin", arch: "x64" }]; + const version = "1.0.0"; + const npmTag = "latest"; + + await packAndPublish({ platforms, version, skipPublish: true, npmTag }); + + assert.equal(mockPackPlatform.mock.calls.length, 1); + assert.equal(mockPublishArtifacts.mock.calls.length, 0); + }); + }); +}); diff --git a/packages/turbo-releaser/src/packager.ts b/packages/turbo-releaser/src/packager.ts new file mode 100644 index 0000000000000..bc4fa98f5b17d --- /dev/null +++ b/packages/turbo-releaser/src/packager.ts @@ -0,0 +1,35 @@ +import type { Platform } from "./types"; +import operations from "./operations"; + +interface PackAndPublishOptions { + platforms: Array; + version: string; + skipPublish: boolean; + npmTag: string; +} + +export async function packAndPublish({ + platforms, + version, + skipPublish, + npmTag, +}: PackAndPublishOptions) { + console.log("Starting packAndPublish process..."); + const artifacts: Array = []; + + for (const platform of platforms) { + console.log(`Processing platform: ${platform.os}-${platform.arch}`); + // eslint-disable-next-line no-await-in-loop -- We trade of slightly faster releases with more legible logging + const artifact = await operations.packPlatform(platform, version); + artifacts.push(artifact); + } + + console.log("All platforms processed. Artifacts:", artifacts); + + if (!skipPublish) { + console.log("Publishing artifacts..."); + operations.publishArtifacts(artifacts, npmTag); + } else { + console.log("Skipping publish step."); + } +} diff --git a/packages/turbo-releaser/src/types.ts b/packages/turbo-releaser/src/types.ts new file mode 100644 index 0000000000000..f75d9bba2c69c --- /dev/null +++ b/packages/turbo-releaser/src/types.ts @@ -0,0 +1,8 @@ +export type SupportedOS = "darwin" | "linux" | "windows"; +export type SupportedArch = "x64" | "arm64"; +export type HumanArch = "64" | "arm64"; + +export interface Platform { + os: SupportedOS; + arch: SupportedArch; +} diff --git a/packages/turbo-releaser/src/version.test.ts b/packages/turbo-releaser/src/version.test.ts new file mode 100644 index 0000000000000..0319c23b2adb1 --- /dev/null +++ b/packages/turbo-releaser/src/version.test.ts @@ -0,0 +1,29 @@ +import { describe, it, mock } from "node:test"; +import assert from "node:assert"; +import fs from "node:fs/promises"; +import { getVersionInfo } from "./version"; + +describe("getVersionInfo", () => { + it("should read version and npm tag from version.txt", async (t) => { + const mockReadFile = mock.fn((_path, _encoding) => { + return Promise.resolve("1.0.0\nbeta\n"); + }); + t.mock.method(fs, "readFile", mockReadFile); + const result = await getVersionInfo("some-path/version.txt"); + assert.deepStrictEqual(result, { version: "1.0.0", npmTag: "beta" }); + assert.equal( + mockReadFile.mock.calls[0].arguments[0], + "some-path/version.txt" + ); + }); + + it("should throw an error if version.txt is not found", async (t) => { + const mockReadFile = mock.fn((_path, _encoding) => { + return Promise.reject(new Error("File not found")); + }); + t.mock.method(fs, "readFile", mockReadFile); + await assert.rejects(() => getVersionInfo("version.txt"), { + message: "File not found", + }); + }); +}); diff --git a/packages/turbo-releaser/src/version.ts b/packages/turbo-releaser/src/version.ts new file mode 100644 index 0000000000000..a727355f54c2b --- /dev/null +++ b/packages/turbo-releaser/src/version.ts @@ -0,0 +1,11 @@ +import fs from "node:fs/promises"; + +export async function getVersionInfo(versionPath: string): Promise<{ + version: string; + npmTag: string; +}> { + const versionFile = await fs.readFile(versionPath, "utf-8"); + const [version, npmTag] = versionFile.trim().split("\n"); + console.log(`Version: ${version}, NPM Tag: ${npmTag}`); + return { version, npmTag }; +} diff --git a/packages/turbo-releaser/template/README.md b/packages/turbo-releaser/template/README.md new file mode 100644 index 0000000000000..27416bc7b03fd --- /dev/null +++ b/packages/turbo-releaser/template/README.md @@ -0,0 +1,3 @@ +# `turbo` + +This is a platform-specific binary for Turborepo, a monorepo build system. See https://github.com/vercel/turborepo for details. diff --git a/packages/turbo-releaser/template/bin/turbo b/packages/turbo-releaser/template/bin/turbo new file mode 100644 index 0000000000000..4557a07e212c5 --- /dev/null +++ b/packages/turbo-releaser/template/bin/turbo @@ -0,0 +1,15 @@ +#!/usr/bin/env node + +// Unfortunately even though npm shims "bin" commands on Windows with auto- +// generated forwarding scripts, it doesn't strip the ".exe" from the file name +// first. So it's possible to publish executables via npm on all platforms +// except Windows. I consider this a npm bug. +// +// My workaround is to add this script as another layer of indirection. It'll +// be slower because node has to boot up just to shell out to the actual exe, +// but Windows is somewhat of a second-class platform to npm so it's the best +// I can do I think. +const path = require('path'); +const turbo_exe = path.join(__dirname, 'turbo.exe'); +const child_process = require('child_process'); +child_process.spawnSync(turbo_exe, process.argv.slice(2), { stdio: 'inherit' }); diff --git a/packages/turbo-releaser/tsconfig.json b/packages/turbo-releaser/tsconfig.json new file mode 100644 index 0000000000000..e6f9fc6aa70b6 --- /dev/null +++ b/packages/turbo-releaser/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@turbo/tsconfig/library.json", + "compilerOptions": { + "rootDir": ".", + "module": "ESNext", + "lib": ["ESNext"] + } +} diff --git a/packages/turbo-releaser/tsup.config.ts b/packages/turbo-releaser/tsup.config.ts new file mode 100644 index 0000000000000..b78e5518aef2a --- /dev/null +++ b/packages/turbo-releaser/tsup.config.ts @@ -0,0 +1,9 @@ +import { defineConfig, Options } from "tsup"; + +export default defineConfig((options: Options) => ({ + entry: ["src/index.ts"], + format: ["cjs"], + clean: true, + minify: true, + ...options, +})); diff --git a/packages/turbo-releaser/turbo.json b/packages/turbo-releaser/turbo.json new file mode 100644 index 0000000000000..07dd354a7722d --- /dev/null +++ b/packages/turbo-releaser/turbo.json @@ -0,0 +1,9 @@ +{ + "extends": ["//"], + "tasks": { + "build": { + "dependsOn": ["^build"], + "outputs": ["dist"] + } + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9c6f595b5617c..e492f4ff58b24 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -560,6 +560,37 @@ importers: specifier: 5.5.4 version: 5.5.4 + packages/turbo-releaser: + dependencies: + commander: + specifier: ^11.0.0 + version: 11.0.0 + tar: + specifier: 6.1.13 + version: 6.1.13 + devDependencies: + '@turbo/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@turbo/tsconfig': + specifier: workspace:* + version: link:../tsconfig + '@types/node': + specifier: ^20 + version: 20.11.30 + '@types/tar': + specifier: ^6.1.4 + version: 6.1.4 + tsup: + specifier: ^6.7.0 + version: 6.7.0(ts-node@10.9.2)(typescript@5.5.4) + tsx: + specifier: 4.19.1 + version: 4.19.1 + typescript: + specifier: 5.5.4 + version: 5.5.4 + packages/turbo-repository: devDependencies: '@napi-rs/cli': @@ -4188,7 +4219,6 @@ packages: /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - dev: true /ci-info@3.8.0: resolution: {integrity: sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==} @@ -6360,7 +6390,6 @@ packages: engines: {node: '>= 8'} dependencies: minipass: 3.3.6 - dev: true /fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -8893,14 +8922,12 @@ packages: engines: {node: '>=8'} dependencies: yallist: 4.0.0 - dev: true /minipass@4.0.0: resolution: {integrity: sha512-g2Uuh2jEKoht+zvO6vJqXmYpflPqzRBT+Th2h01DKh5z7wbY/AZ2gCQ78cP70YoHPyFdY30YBV5WxgLOEwOykw==} engines: {node: '>=8'} dependencies: yallist: 4.0.0 - dev: true /minipass@7.1.2: resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} @@ -8913,7 +8940,6 @@ packages: dependencies: minipass: 3.3.6 yallist: 4.0.0 - dev: true /mixin-deep@1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} @@ -8932,7 +8958,7 @@ packages: /mkdirp@1.0.4: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} - dev: true + hasBin: true /ms@2.0.0: resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} @@ -10720,7 +10746,6 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 - dev: true /test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} @@ -11140,6 +11165,7 @@ packages: /tsx@4.19.1: resolution: {integrity: sha512-0flMz1lh74BR4wOvBjuh9olbnwqCPc35OOlfyzHba0Dc+QNUeWX/Gq2YTbnwcWPO3BMd8fkzRVrHcsR+a7z7rA==} engines: {node: '>=18.0.0'} + hasBin: true dependencies: esbuild: 0.23.1 get-tsconfig: 4.7.6 @@ -11288,6 +11314,7 @@ packages: /typescript@5.5.4: resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} + hasBin: true /uglify-js@3.17.4: resolution: {integrity: sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==} From 3692b94e9c9755d4b0f815177918ff7c24662ccc Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Wed, 5 Feb 2025 16:34:44 -0500 Subject: [PATCH 3/8] chore(release): swap out goreleaser for turboreleaser --- .github/workflows/turborepo-release.yml | 10 ---------- cli/Makefile | 3 +-- cli/package.json | 6 +++++- cli/turbo.json | 4 ++++ pnpm-lock.yaml | 6 +++++- 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/.github/workflows/turborepo-release.yml b/.github/workflows/turborepo-release.yml index 654a8f9d2a741..19aab7530867b 100644 --- a/.github/workflows/turborepo-release.yml +++ b/.github/workflows/turborepo-release.yml @@ -214,15 +214,6 @@ jobs: git config --global user.name 'Turbobot' git config --global user.email 'turbobot@vercel.com' - - name: Install GoReleaser - uses: goreleaser/goreleaser-action@v6 - with: - distribution: goreleaser-pro - version: v1.18.2 - install-only: true - env: - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} - - name: Download Rust artifacts uses: actions/download-artifact@v4 with: @@ -240,7 +231,6 @@ jobs: - name: Perform Release run: cd cli && make publish-turbo SKIP_PUBLISH=${{ inputs.dry_run && '--skip-publish' || '' }} env: - GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} # Upload published artifacts in case they are needed for debugging later diff --git a/cli/Makefile b/cli/Makefile index 77658cbf29ea5..b5d56caad51d6 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -54,8 +54,7 @@ publish-turbo: build npm config set --location=project "//registry.npmjs.org/:_authToken" $(NPM_TOKEN) # Publishes the native npm modules. - # TODO: do this without goreleaser. - goreleaser release --rm-dist -f combined-shim.yml $(SKIP_PUBLISH) + turbo release-native -- $(SKIP_PUBLISH) # Split packing from the publish step so that npm locates the correct .npmrc file. cd $(CLI_DIR)/../packages/turbo && pnpm pack --pack-destination=$(CLI_DIR)/../ diff --git a/cli/package.json b/cli/package.json index deff9108f9185..197d0c130731e 100644 --- a/cli/package.json +++ b/cli/package.json @@ -5,6 +5,10 @@ "scripts": { "clean": "cargo clean --package turbo", "build": "cargo build --package turbo", - "build:release": "cargo build --package turbo --profile release-turborepo" + "build:release": "cargo build --package turbo --profile release-turborepo", + "release-native": "turboreleaser --version-path ../version.txt" + }, + "dependencies": { + "@turbo/releaser": "workspace:*" } } diff --git a/cli/turbo.json b/cli/turbo.json index 92b936b9c0dcc..9e1e5aa12ac4a 100644 --- a/cli/turbo.json +++ b/cli/turbo.json @@ -39,6 +39,10 @@ "AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY" ] + }, + "release-native": { + "dependsOn": ["@turbo/releaser#build"], + "cache": false } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e492f4ff58b24..92a9dc7791a66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -46,7 +46,11 @@ importers: specifier: ^0.36.0 version: 0.36.0 - cli: {} + cli: + dependencies: + '@turbo/releaser': + specifier: workspace:* + version: link:../packages/turbo-releaser docs: dependencies: From 5edf9dbaf007277e9df364fece2ac1d96f8d19b1 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Wed, 5 Feb 2025 16:35:15 -0500 Subject: [PATCH 4/8] chore(release): delete goreleaser workflow --- cli/combined-shim.yml | 89 ------------------------------------------- 1 file changed, 89 deletions(-) delete mode 100644 cli/combined-shim.yml diff --git a/cli/combined-shim.yml b/cli/combined-shim.yml deleted file mode 100644 index 29bff222b46f1..0000000000000 --- a/cli/combined-shim.yml +++ /dev/null @@ -1,89 +0,0 @@ -project_name: turbo - -dist: dist - -# We just call `npm-native-packages.js` once for each item in goos x goarch -# the scrip generates a scaffold version of each npm package -# goreleaser just copies binary from dist-{os}-{arch}/turbo(.exe) to bin/turbo -builds: - - id: turbo - builder: prebuilt - tags: - - rust - - staticbinary - goos: - - linux - - windows - - darwin - goarch: - - amd64 - - arm64 - goamd64: - - v1 - prebuilt: - path: dist-{{ .Os }}-{{ .Arch }}/turbo{{ .Ext }} - hooks: - pre: - - cmd: ./scripts/npm-native-packages/npm-native-packages.js {{ .Os }} {{ .Arch }} {{ .Version }} - binary: bin/turbo -# goreleaser creates a checksums.txt -# don't think this is in use anywhere? could be in GH release, but not going anywhere -checksum: - name_template: "checksums.txt" -# feature we do not use -snapshot: - name_template: "{{ incpatch .Version }}" -archives: - # we do nothing with this archive - - id: github - name_template: "{{ .ProjectName }}-{{ .Version }}-{{ .Os }}-{{ .Arch }}" - wrap_in_directory: true - replacements: - amd64: 64 - format: tar.gz - format_overrides: - - goos: windows - format: zip - files: - - LICENSE - - README.md - - id: npm - name_template: "{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}" - wrap_in_directory: true - replacements: - amd64: 64 - format: tar.gz - files: - # copy only these files into - - LICENSE - - src: "scripts/npm-native-packages/build/{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}/package.json" - dst: "workaround/.." - strip_parent: true - - src: "scripts/npm-native-packages/build/{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}/README.md" - dst: "workaround/.." - strip_parent: true - - src: "scripts/npm-native-packages/build/{{ .ProjectName }}-{{ .Os }}-{{ .Arch }}/bin/*" - dst: "bin/" - strip_parent: true -# used for GH releases only (which are disabled) -changelog: - sort: asc - filters: - exclude: - - "^docs:" - - "^test:" -# we do nothing with the release section since disabled -release: - github: - owner: vercel - name: turborepo - ids: - - github - prerelease: auto - disable: true -publishers: - - name: npm - ids: - - npm - # all this does is run this command for each archive we made above - cmd: "npm publish{{ if .Prerelease }} --tag canary{{ end }} {{ abs .ArtifactPath }}" From 4108f830953d2c9a7676af3cfc25dce218d9d433 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Wed, 5 Feb 2025 18:39:53 -0500 Subject: [PATCH 5/8] chore(release): remove goreleaser arch naming from rust artifacts --- .github/workflows/turborepo-release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/turborepo-release.yml b/.github/workflows/turborepo-release.yml index 19aab7530867b..328b2fdc93912 100644 --- a/.github/workflows/turborepo-release.yml +++ b/.github/workflows/turborepo-release.yml @@ -224,9 +224,9 @@ jobs: mv rust-artifacts/turbo-aarch64-apple-darwin cli/dist-darwin-arm64 mv rust-artifacts/turbo-aarch64-unknown-linux-musl cli/dist-linux-arm64 cp -r rust-artifacts/turbo-x86_64-pc-windows-msvc cli/dist-windows-arm64 - mv rust-artifacts/turbo-x86_64-unknown-linux-musl cli/dist-linux-amd64 - mv rust-artifacts/turbo-x86_64-apple-darwin cli/dist-darwin-amd64 - mv rust-artifacts/turbo-x86_64-pc-windows-msvc cli/dist-windows-amd64 + mv rust-artifacts/turbo-x86_64-unknown-linux-musl cli/dist-linux-x64 + mv rust-artifacts/turbo-x86_64-apple-darwin cli/dist-darwin-x64 + mv rust-artifacts/turbo-x86_64-pc-windows-msvc cli/dist-windows-x64 - name: Perform Release run: cd cli && make publish-turbo SKIP_PUBLISH=${{ inputs.dry_run && '--skip-publish' || '' }} From 152a48a6b86da174e6854f8067b70ceefabe7a2a Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Thu, 6 Feb 2025 08:28:27 -0500 Subject: [PATCH 6/8] chore(release): delete npm-native-packages script/templates --- cli/scripts/npm-native-packages/.gitignore | 1 - .../npm-native-packages.js | 51 ------------------- .../npm-native-packages/template/README.md | 3 -- .../npm-native-packages/template/bin/turbo | 15 ------ .../template/template.package.json | 12 ----- 5 files changed, 82 deletions(-) delete mode 100644 cli/scripts/npm-native-packages/.gitignore delete mode 100755 cli/scripts/npm-native-packages/npm-native-packages.js delete mode 100644 cli/scripts/npm-native-packages/template/README.md delete mode 100644 cli/scripts/npm-native-packages/template/bin/turbo delete mode 100644 cli/scripts/npm-native-packages/template/template.package.json diff --git a/cli/scripts/npm-native-packages/.gitignore b/cli/scripts/npm-native-packages/.gitignore deleted file mode 100644 index 84c048a73cc2e..0000000000000 --- a/cli/scripts/npm-native-packages/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build/ diff --git a/cli/scripts/npm-native-packages/npm-native-packages.js b/cli/scripts/npm-native-packages/npm-native-packages.js deleted file mode 100755 index 52b7463b40d1b..0000000000000 --- a/cli/scripts/npm-native-packages/npm-native-packages.js +++ /dev/null @@ -1,51 +0,0 @@ -#!/usr/bin/env node - -const fs = require("fs"); -const path = require("path"); - -// Map to node os and arch names. -const nodeOsLookup = { - darwin: "darwin", - linux: "linux", - windows: "win32", -}; - -const nodeArchLookup = { - amd64: "x64", - arm64: "arm64", -}; - -const humanizedArchLookup = { - amd64: "64", - arm64: "arm64", -}; - -const template = require("./template/template.package.json"); -const os = process.argv[2]; -const arch = process.argv[3]; -const version = process.argv[4]; - -template.name = `turbo-${os}-${humanizedArchLookup[arch]}`; -template.description = `The ${os}-${humanizedArchLookup[arch]} binary for turbo, a monorepo build system.`; -template.os = [nodeOsLookup[os]]; -template.cpu = [nodeArchLookup[arch]]; -template.version = version; - -const outputPath = path.join(__dirname, "build", template.name); -fs.rmSync(outputPath, { recursive: true, force: true }); -fs.mkdirSync(path.join(outputPath, "bin"), { recursive: true }); - -if (os === "windows") { - fs.copyFileSync( - path.join(__dirname, "template", "bin", "turbo"), - path.join(outputPath, "bin", "turbo") - ); -} -fs.copyFileSync( - path.join(__dirname, "template", "README.md"), - path.join(outputPath, "README.md") -); -fs.writeFileSync( - path.join(outputPath, "package.json"), - JSON.stringify(template, null, 2) -); diff --git a/cli/scripts/npm-native-packages/template/README.md b/cli/scripts/npm-native-packages/template/README.md deleted file mode 100644 index 27416bc7b03fd..0000000000000 --- a/cli/scripts/npm-native-packages/template/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# `turbo` - -This is a platform-specific binary for Turborepo, a monorepo build system. See https://github.com/vercel/turborepo for details. diff --git a/cli/scripts/npm-native-packages/template/bin/turbo b/cli/scripts/npm-native-packages/template/bin/turbo deleted file mode 100644 index 4557a07e212c5..0000000000000 --- a/cli/scripts/npm-native-packages/template/bin/turbo +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env node - -// Unfortunately even though npm shims "bin" commands on Windows with auto- -// generated forwarding scripts, it doesn't strip the ".exe" from the file name -// first. So it's possible to publish executables via npm on all platforms -// except Windows. I consider this a npm bug. -// -// My workaround is to add this script as another layer of indirection. It'll -// be slower because node has to boot up just to shell out to the actual exe, -// but Windows is somewhat of a second-class platform to npm so it's the best -// I can do I think. -const path = require('path'); -const turbo_exe = path.join(__dirname, 'turbo.exe'); -const child_process = require('child_process'); -child_process.spawnSync(turbo_exe, process.argv.slice(2), { stdio: 'inherit' }); diff --git a/cli/scripts/npm-native-packages/template/template.package.json b/cli/scripts/npm-native-packages/template/template.package.json deleted file mode 100644 index c6f9700b183e7..0000000000000 --- a/cli/scripts/npm-native-packages/template/template.package.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "turbo-{{Os}}-{{Arch}}", - "version": "{{Version}", - "description": "The {{Os}}-{{Arch}} binary for turbo, a monorepo build system.", - "repository": "https://github.com/vercel/turborepo", - "bugs": "https://github.com/vercel/turborepo/issues", - "homepage": "https://turbo.build/repo", - "license": "MIT", - "os": ["{{Os}}"], - "cpu": ["{{Arch}}"], - "preferUnplugged": true -} From f390193e3ded4197d64e87de16034f08705d74d2 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Thu, 6 Feb 2025 11:07:53 -0500 Subject: [PATCH 7/8] fix(releaser): include LICENSE in native packages --- packages/turbo-releaser/src/native.test.ts | 11 +++++++++-- packages/turbo-releaser/src/native.ts | 19 ++++++++++--------- packages/turbo-releaser/template/LICENSE | 7 +++++++ 3 files changed, 26 insertions(+), 11 deletions(-) create mode 100644 packages/turbo-releaser/template/LICENSE diff --git a/packages/turbo-releaser/src/native.test.ts b/packages/turbo-releaser/src/native.test.ts index 0801a67101707..b0a1de655495f 100644 --- a/packages/turbo-releaser/src/native.test.ts +++ b/packages/turbo-releaser/src/native.test.ts @@ -39,7 +39,7 @@ describe("generateNativePackage", () => { ); // Assert copyFile was called correctly - assert.equal(mockCopyFile.mock.calls.length, 1); + assert.equal(mockCopyFile.mock.calls.length, 2); assert.ok( mockCopyFile.mock.calls[0].arguments[0].endsWith("template/README.md") ); @@ -47,6 +47,13 @@ describe("generateNativePackage", () => { mockCopyFile.mock.calls[0].arguments[1], path.join(outputDir, "README.md") ); + assert.ok( + mockCopyFile.mock.calls[1].arguments[0].endsWith("template/LICENSE") + ); + assert.equal( + mockCopyFile.mock.calls[1].arguments[1], + path.join(outputDir, "LICENSE") + ); // Assert writeFile was called correctly assert.equal(mockWriteFile.mock.calls.length, 1); @@ -91,7 +98,7 @@ describe("generateNativePackage", () => { outputDir, }); - assert.equal(mockCopyFile.mock.calls.length, 2); + assert.equal(mockCopyFile.mock.calls.length, 3); assert.ok( mockCopyFile.mock.calls[0].arguments[0].endsWith("template/bin/turbo") ); diff --git a/packages/turbo-releaser/src/native.ts b/packages/turbo-releaser/src/native.ts index 5bb75b66dff91..1ea2924ee0745 100644 --- a/packages/turbo-releaser/src/native.ts +++ b/packages/turbo-releaser/src/native.ts @@ -25,19 +25,20 @@ async function generateNativePackage({ await rm(outputDir, { recursive: true, force: true }); await mkdir(path.join(outputDir, "bin"), { recursive: true }); - if (os === "windows") { - console.log("Copying Windows-specific files..."); + const copyFromTemplate = async (part: string, ...parts: Array) => { + console.log("Copying ", path.join(part, ...parts)); await copyFile( - path.join(templateDir, "bin", "turbo"), - path.join(outputDir, "bin", "turbo") + path.join(templateDir, part, ...parts), + path.join(outputDir, part, ...parts) ); + }; + + if (os === "windows") { + await copyFromTemplate("bin", "turbo"); } - console.log("Copying README.md..."); - await copyFile( - path.join(templateDir, "README.md"), - path.join(outputDir, "README.md") - ); + await copyFromTemplate("README.md"); + await copyFromTemplate("LICENSE"); console.log("Generating package.json..."); const packageJson = { diff --git a/packages/turbo-releaser/template/LICENSE b/packages/turbo-releaser/template/LICENSE new file mode 100644 index 0000000000000..5c3db8bb6f857 --- /dev/null +++ b/packages/turbo-releaser/template/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2024 Vercel, Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From cfe0c5ab96a0872d378f2ec96ae69a420d2285d9 Mon Sep 17 00:00:00 2001 From: Chris Olszewski Date: Thu, 6 Feb 2025 11:35:08 -0500 Subject: [PATCH 8/8] fix(releaser): tarball directory not loose files --- packages/turbo-releaser/src/operations.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/turbo-releaser/src/operations.ts b/packages/turbo-releaser/src/operations.ts index b65e399643404..822c37a2d46ed 100644 --- a/packages/turbo-releaser/src/operations.ts +++ b/packages/turbo-releaser/src/operations.ts @@ -11,7 +11,9 @@ async function packPlatform( ): Promise { const { os, arch } = platform; console.log(`Packing platform: ${os}-${arch}`); - const scaffoldDir = path.join("dist", `${os}-${arch}-${version}`); + const npmDirName = `turbo-${os}-${arch}`; + const tarballDir = path.join("dist", `${os}-${arch}-${version}`); + const scaffoldDir = path.join(tarballDir, npmDirName); console.log("Generating native package..."); await native.generateNativePackage({ @@ -34,9 +36,9 @@ async function packPlatform( { gzip: true, file: tarPath, - cwd: scaffoldDir, + cwd: tarballDir, }, - ["package.json", "README.md", "bin"] + [npmDirName] ); console.log(`Artifact created: ${tarPath}`);