Skip to content

Commit

Permalink
feat(cli): init github/bitbucket/gitlab action (#505)
Browse files Browse the repository at this point in the history
  • Loading branch information
mathio authored Feb 26, 2025
1 parent c037d95 commit 1fc204b
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/quiet-dancers-cover.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"lingo.dev": patch
---

init github/bitbucket/gitlab action
13 changes: 8 additions & 5 deletions packages/cli/src/cli/cmd/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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({
Expand All @@ -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;
}
}

Expand All @@ -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");
Expand Down
22 changes: 11 additions & 11 deletions packages/cli/src/cli/utils/find-locale-paths.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
});
});

Expand All @@ -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"],
});
});

Expand All @@ -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"],
});
});

Expand Down Expand Up @@ -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",
Expand All @@ -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"],
});
});

Expand All @@ -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"],
});
});

Expand Down
10 changes: 6 additions & 4 deletions packages/cli/src/cli/utils/find-locale-paths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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 };
}
145 changes: 145 additions & 0 deletions packages/cli/src/cli/utils/init-ci-cd.ts
Original file line number Diff line number Diff line change
@@ -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,
);
}

0 comments on commit 1fc204b

Please # to comment.