diff --git a/.changeset/quiet-dancers-cover.md b/.changeset/quiet-dancers-cover.md new file mode 100644 index 00000000..c6c832ae --- /dev/null +++ b/.changeset/quiet-dancers-cover.md @@ -0,0 +1,5 @@ +--- +"lingo.dev": patch +--- + +init github/bitbucket/gitlab action diff --git a/packages/cli/src/cli/cmd/init.ts b/packages/cli/src/cli/cmd/init.ts index 2970886f..4fca1837 100644 --- a/packages/cli/src/cli/cmd/init.ts +++ b/packages/cli/src/cli/cmd/init.ts @@ -13,6 +13,7 @@ import { createAuthenticator } from "../utils/auth"; import findLocaleFiles from "../utils/find-locale-paths"; import { ensurePatterns } from "../utils/ensure-patterns"; import updateGitignore from "../utils/update-gitignore"; +import initCICD from "../utils/init-ci-cd"; const openUrl = (path: string) => { const settings = getSettings(undefined); @@ -118,9 +119,9 @@ export default new InteractiveCommand() }; } else { let selectedPatterns: string[] = []; - const { found, patterns } = findLocaleFiles(options.bucket); + const { patterns, defaultPatterns } = findLocaleFiles(options.bucket); - if (found) { + if (patterns.length > 0) { spinner.succeed("Found existing locale files:"); selectedPatterns = await checkbox({ @@ -135,11 +136,11 @@ export default new InteractiveCommand() if (selectedPatterns.length === 0) { const useDefault = await confirm({ - message: `Use (and create) default path ${patterns.join(", ")}?`, + message: `Use (and create) default path ${defaultPatterns.join(", ")}?`, }); - ensurePatterns(patterns, options.source); if (useDefault) { - selectedPatterns = patterns; + ensurePatterns(defaultPatterns, options.source); + selectedPatterns = defaultPatterns; } } @@ -162,6 +163,8 @@ export default new InteractiveCommand() spinner.succeed("Lingo.dev project initialized"); if (isInteractive) { + await initCICD(spinner); + const openDocs = await confirm({ message: "Would you like to see our docs?" }); if (openDocs) { openUrl("/go/docs"); diff --git a/packages/cli/src/cli/utils/find-locale-paths.spec.ts b/packages/cli/src/cli/utils/find-locale-paths.spec.ts index 5669a73a..a34c6ede 100644 --- a/packages/cli/src/cli/utils/find-locale-paths.spec.ts +++ b/packages/cli/src/cli/utils/find-locale-paths.spec.ts @@ -29,8 +29,8 @@ describe("findLocaleFiles", () => { const result = findLocaleFiles("json"); expect(result).toEqual({ - found: true, patterns: ["src/i18n/[locale].json", "src/translations/[locale].json"], + defaultPatterns: ["i18n/[locale].json"], }); }); @@ -40,8 +40,8 @@ describe("findLocaleFiles", () => { const result = findLocaleFiles("yaml"); expect(result).toEqual({ - found: true, patterns: ["locales/[locale].yml", "translations/[locale].yml"], + defaultPatterns: ["i18n/[locale].yml"], }); }); @@ -51,8 +51,8 @@ describe("findLocaleFiles", () => { const result = findLocaleFiles("flutter"); expect(result).toEqual({ - found: true, patterns: ["lib/l10n/[locale].arb", "lib/translations/[locale].arb"], + defaultPatterns: ["i18n/[locale].arb"], }); }); @@ -80,7 +80,6 @@ describe("findLocaleFiles", () => { const result = findLocaleFiles("json"); expect(result).toEqual({ - found: true, patterns: [ "src/locales/[locale]/messages.json", "src/i18n/[locale]/strings.json", @@ -94,17 +93,18 @@ describe("findLocaleFiles", () => { "bar/[locale]/baz/[locale].json", "bar/[locale]/[locale].json", ], + defaultPatterns: ["i18n/[locale].json"], }); }); - it("should return default pattern when no files found", () => { + it("should return no patterns when no files found", () => { vi.mocked(glob.sync).mockReturnValue([]); const result = findLocaleFiles("json"); expect(result).toEqual({ - found: false, - patterns: ["i18n/[locale].json"], + patterns: [], + defaultPatterns: ["i18n/[locale].json"], }); }); @@ -119,23 +119,23 @@ describe("findLocaleFiles", () => { const result = findLocaleFiles("xcode-xcstrings"); expect(result).toEqual({ - found: true, patterns: [ "ios/MyApp/Localizable.xcstrings", "ios/MyApp/Onboarding/Localizable.xcstrings", "ios/MyApp/xx/Localizable.xcstrings", ], + defaultPatterns: ["Localizable.xcstrings"], }); }); - it("should return default pattern for xcode-xcstrings when no files found", () => { + it("should return no patterns for xcode-xcstrings when no files found", () => { vi.mocked(glob.sync).mockReturnValue([]); const result = findLocaleFiles("xcode-xcstrings"); expect(result).toEqual({ - found: false, - patterns: ["Localizable.xcstrings"], + patterns: [], + defaultPatterns: ["Localizable.xcstrings"], }); }); diff --git a/packages/cli/src/cli/utils/find-locale-paths.ts b/packages/cli/src/cli/utils/find-locale-paths.ts index 6e8a2df1..8a309003 100644 --- a/packages/cli/src/cli/utils/find-locale-paths.ts +++ b/packages/cli/src/cli/utils/find-locale-paths.ts @@ -71,12 +71,13 @@ function findLocaleFilesWithExtension(ext: string) { const grouppedFilesAndPatterns = _.groupBy(localeFilesAndPatterns, "pattern"); const patterns = Object.keys(grouppedFilesAndPatterns); + const defaultPatterns = [`i18n/[locale]${ext}`]; if (patterns.length > 0) { - return { found: true, patterns }; + return { patterns, defaultPatterns }; } - return { found: false, patterns: [`i18n/[locale]${ext}`] }; + return { patterns: [], defaultPatterns }; } function findLocaleFilesForFilename(fileName: string) { @@ -91,10 +92,11 @@ function findLocaleFilesForFilename(fileName: string) { })); const grouppedFilesAndPatterns = _.groupBy(localeFilesAndPatterns, "pattern"); const patterns = Object.keys(grouppedFilesAndPatterns); + const defaultPatterns = [fileName]; if (patterns.length > 0) { - return { found: true, patterns }; + return { patterns, defaultPatterns }; } - return { found: false, patterns: [fileName] }; + return { patterns: [], defaultPatterns }; } diff --git a/packages/cli/src/cli/utils/init-ci-cd.ts b/packages/cli/src/cli/utils/init-ci-cd.ts new file mode 100644 index 00000000..dc740ef8 --- /dev/null +++ b/packages/cli/src/cli/utils/init-ci-cd.ts @@ -0,0 +1,145 @@ +import { checkbox, confirm } from "@inquirer/prompts"; +import fs from "fs"; +import { Ora } from "ora"; +import path from "path"; + +type Platform = "github" | "bitbucket" | "gitlab"; + +const platforms: Platform[] = ["github", "bitbucket", "gitlab"]; + +export default async function initCICD(spinner: Ora) { + const initializers = getPlatformInitializers(spinner); + + const init = await confirm({ + message: "Would you like to use Lingo.dev in your CI/CD?", + }); + + if (!init) { + spinner.warn("CI/CD not initialized. To set it up later, see docs: https://docs.lingo.dev/ci-action/overview"); + return; + } + + const selectedPlatforms: Platform[] = await checkbox({ + message: "Please select CI/CD platform(s) you want to use:", + choices: platforms.map((platform) => ({ + name: initializers[platform].name, + value: platform, + checked: initializers[platform].isEnabled(), + })), + }); + + for (const platform of selectedPlatforms) { + await initializers[platform].init(); + } +} + +function getPlatformInitializers(spinner: Ora) { + return { + github: makeGithubInitializer(spinner), + bitbucket: makeBitbucketInitializer(spinner), + gitlab: makeGitlabInitializer(spinner), + }; +} + +type PlatformConfig = { + name: string; + checkPath: string; + ciConfigPath: string; + ciConfigContent: string; +}; + +function makePlatformInitializer(config: PlatformConfig, spinner: Ora) { + return { + name: config.name, + isEnabled: () => { + const filePath = path.join(process.cwd(), config.checkPath); + return fs.existsSync(filePath); + }, + init: async () => { + const filePath = path.join(process.cwd(), config.ciConfigPath); + const dirPath = path.dirname(filePath); + if (!fs.existsSync(dirPath)) { + fs.mkdirSync(dirPath, { recursive: true }); + } + let canWrite = true; + if (fs.existsSync(filePath)) { + canWrite = await confirm({ + message: `File ${filePath} already exists. Do you want to overwrite it?`, + default: false, + }); + } + if (canWrite) { + fs.writeFileSync(filePath, config.ciConfigContent); + spinner.succeed(`CI/CD initialized for ${config.name}`); + } else { + spinner.warn(`CI/CD not initialized for ${config.name}`); + } + }, + }; +} + +function makeGithubInitializer(spinner: Ora) { + return makePlatformInitializer( + { + name: "GitHub Action", + checkPath: ".github", + ciConfigPath: ".github/workflows/i18n.yml", + ciConfigContent: `name: Lingo.dev i18n + +on: + push: + branches: + - main + +permissions: + contents: write + pull-requests: write + +jobs: + i18n: + name: Run i18n + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: lingodotdev/lingo.dev@main + with: + api-key: \${{ secrets.LINGODOTDEV_API_KEY }} +`, + }, + spinner, + ); +} + +function makeBitbucketInitializer(spinner: Ora) { + return makePlatformInitializer( + { + name: "Bitbucket Pipeline", + checkPath: "bitbucket-pipelines.yml", + ciConfigPath: "bitbucket-pipelines.yml", + ciConfigContent: `pipelines: + branches: + main: + - step: + name: Run i18n + script: + - pipe: lingodotdev/lingo.dev:main`, + }, + spinner, + ); +} + +function makeGitlabInitializer(spinner: Ora) { + return makePlatformInitializer( + { + name: "Gitlab CI", + checkPath: ".gitlab-ci.yml", + ciConfigPath: ".gitlab-ci.yml", + ciConfigContent: `lingodotdev: + image: lingodotdev/ci-action:latest + script: + - echo "Done" +`, + }, + spinner, + ); +}