Skip to content

Commit

Permalink
Merge pull request #15 from zetkin/231205-refactor
Browse files Browse the repository at this point in the history
add test to github action
  • Loading branch information
richardolsson authored Dec 16, 2023
2 parents f8d07c8 + 83ad2bc commit 72562e2
Show file tree
Hide file tree
Showing 23 changed files with 304 additions and 133 deletions.
9 changes: 9 additions & 0 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@ on:
permissions:
contents: read

env:
# temporary workaround until move those value to API call or lyra.yml
REPO_PATH: "any value"
GITHUB_AUTH: "any value"
GITHUB_REPO: "any value"
GITHUB_OWNER: "any value"

jobs:
lint:
name: Lint
Expand All @@ -25,3 +32,5 @@ jobs:
cache-dependency-path: ./webapp/yarn.lock
- run: yarn
- run: yarn lint
- run: yarn build
- run: yarn test
20 changes: 20 additions & 0 deletions webapp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,26 @@
# Changelog
<!-- https://keepachangelog.com/en/1.0.0/ -->

## [0.2.1] 2023-12-05
### Added
- Run build and test in GitHub action
- Base Branch config to lyra.yml
### Changed
- Rename classes name YAMLTranslate to YamlTranslate and TSMessage to TsMessage to follow naming convention

## [0.2.0] 2023-12-04
### Added
- adaptor
- Unit test

## [0.1.3] 2023-12-01
### Fixed
- Bug: The case when there is more than one variable in message-id

## [0.1.2] 2023-12-01
### Changed
- in memory translate object become flat object of message id and text

## [0.1.1] 2023-11-24
### Changed
- fix PR name and commit message
12 changes: 12 additions & 0 deletions webapp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,15 @@ GITHUB_AUTH= <<TOKEN>>
GITHUB_OWNER=zetkin
GITHUB_REPO=app.zetkin.org
```

also the project reposotory needs to be cloned locally. and has in the root folder config file `lyra.yml` with the example content:
```yaml
baseBranch: main
projects:
- path: .
messages:
format: yaml
path: locale
translations:
path: locale
```
3 changes: 3 additions & 0 deletions webapp/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,7 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
moduleNameMapper: {
'@/(.*)': '<rootDir>/src/$1',
}
};
2 changes: 1 addition & 1 deletion webapp/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "lyra-webapp",
"version": "0.1.1",
"version": "0.2.1",
"private": true,
"scripts": {
"dev": "next dev",
Expand Down
28 changes: 15 additions & 13 deletions webapp/src/Store.ts
Original file line number Diff line number Diff line change
@@ -1,44 +1,46 @@
/* global globalThis */

import { debug } from '@/utils/log';
import { envVarNotFound } from '@/utils/util';
import { LanguageNotFound } from '@/errors';
import LyraConfig from './utils/config';
import YAMLTranslationAdapter from './utils/adapters/YAMLTranslationAdapter';
import { envVarNotFound, logDebug } from '@/utils/util';
import YamlTranslationAdapter from './utils/adapters/YamlTranslationAdapter';
import { simpleGit, SimpleGit, SimpleGitOptions } from 'simple-git';

const REPO_PATH = process.env.REPO_PATH ?? envVarNotFound('REPO_PATH');
const MAIN_BRANCH = process.env.MAIN_BRANCH ?? envVarNotFound('MAIN_BRANCH');

export class Store {
public static async getLanguage(lang: string) {
let languages: Map<string, Record<string, string>>;
debug('read lyra.yml from project root...');
const lyraConfig = await LyraConfig.readFromDir(REPO_PATH);
if (!globalThis.languages) {
logDebug('Initializing languages');
debug('Initializing languages');
const options: Partial<SimpleGitOptions> = {
baseDir: REPO_PATH,
binary: 'git',
maxConcurrentProcesses: 1,
trimmed: false,
};
const git: SimpleGit = simpleGit(options);
logDebug('git checkout main pull...');
await git.checkout(MAIN_BRANCH);
logDebug('git pull...');
debug('git checkout main pull...');
await git.checkout(lyraConfig.baseBranch);
debug('git pull...');
await git.pull();
logDebug('git done checkout main branch and pull');
debug('git done checkout main branch and pull');
languages = new Map<string, Record<string, string>>();
globalThis.languages = languages;
} else {
logDebug('find languages in Memory');
debug('find languages in Memory');
languages = globalThis.languages;
}

let translation: Record<string, string> = {};

if (!languages.has(lang)) {
logDebug('read language[' + lang + '] from file');
const config = await LyraConfig.readFromDir(REPO_PATH);
const adapter = new YAMLTranslationAdapter(config.translationsPath);
debug('read language[' + lang + '] from file');
// TODO: make it multi projects
const adapter = new YamlTranslationAdapter(lyraConfig.projects[0].translationsPath);
const translationsForAllLanguages = await adapter.getTranslations();

Object.entries(translationsForAllLanguages[lang]).forEach(([id, obj]) => {
Expand All @@ -47,7 +49,7 @@ export class Store {

languages.set(lang, translation);
} else {
logDebug('read language [' + lang + '] from Memory');
debug('read language [' + lang + '] from Memory');
translation = languages.get(lang) ?? Store.throwLangNotFound(lang);
}

Expand Down
2 changes: 1 addition & 1 deletion webapp/src/app/[lang]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { MessageData } from '@/utils/readTypedMessages';
import { type MessageData } from '@/utils/adapters';
import MessageForm from '@/components/MessageForm';
import { Box, Button, Link, Typography } from '@mui/joy';
import { useEffect, useState } from 'react';
Expand Down
27 changes: 18 additions & 9 deletions webapp/src/app/api/messages/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,23 @@ import { NextResponse } from 'next/server';
const REPO_PATH = process.env.REPO_PATH ?? envVarNotFound('REPO_PATH');

export async function GET() {
const config = await LyraConfig.readFromDir(REPO_PATH);
const msgAdapter = MessageAdapterFactory.createAdapter(config);
const messages = await msgAdapter.getMessages();
try {
const config = await LyraConfig.readFromDir(REPO_PATH);
const msgAdapter = MessageAdapterFactory.createAdapter(config);
const messages = await msgAdapter.getMessages();

// TODO: change data instruction to be a map of key to value, instead of object
// message id is the key, and value is an object with default and params
// example: { 'key1.key2.key3': { default: 'default text', params: [] }}
return NextResponse.json({
data: messages,
});
// TODO: change data instruction to be a map of key to value, instead of object
// message id is the key, and value is an object with default and params
// example: { 'key1.key2.key3': { default: 'default text', params: [] }}
return NextResponse.json({
data: messages,
});
} catch (e) {
return NextResponse.json(
// TODO: error message include e which contain a local path which is not of consumer interest
// remove it later, leave it for now for debugging purpose
{ message: `error reading messages error: ${e}` },
{ status: 500 }
);
}
}
33 changes: 18 additions & 15 deletions webapp/src/app/api/pull-request/route.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
/* global globalThis */

import { envVarNotFound } from '@/utils/util';
import fs from 'fs/promises';
import LyraConfig from '@/utils/config';
import { NextResponse } from 'next/server';
import { Octokit } from '@octokit/rest';
import packageJson from '@/../package.json';
import { stringify } from 'yaml';
import { unflatten } from 'flat';
import { version } from '@/../package.json';
import { envVarNotFound, logError, logWarn } from '@/utils/util';
import { err, warn } from '@/utils/log';
import { simpleGit, SimpleGit, SimpleGitOptions } from 'simple-git';

const REPO_PATH = process.env.REPO_PATH ?? envVarNotFound('REPO_PATH');
const GITHUB_AUTH = process.env.GITHUB_AUTH ?? envVarNotFound('GITHUB_AUTH');
const GITHUB_REPO = process.env.GITHUB_REPO ?? envVarNotFound('GITHUB_REPO');
const GITHUB_OWNER = process.env.GITHUB_OWNER ?? envVarNotFound('GITHUB_OWNER');

const MAIN_BRANCH = 'main';

/** used to prevent multiple requests from running at the same time */
let syncLock = false;

Expand All @@ -29,18 +29,21 @@ export async function POST() {

try {
syncLock = true;
const lyraconfig = await LyraConfig.readFromDir(REPO_PATH);
const options: Partial<SimpleGitOptions> = {
baseDir: REPO_PATH,
binary: 'git',
maxConcurrentProcesses: 1,
trimmed: false,
};
const git: SimpleGit = simpleGit(options);
await git.checkout(MAIN_BRANCH);
await git.checkout(lyraconfig.baseBranch);
await git.pull();
const languages = globalThis.languages;
for (const lang of languages.keys()) {
const yamlPath = REPO_PATH + `/src/locale/${lang}.yml`;
// TODO: 1. make it multi projects
// 2. use path to avoid double slash
const yamlPath = REPO_PATH + lyraconfig.projects[0].translationsPath + `/${lang}.yml`;
const yamlOutput = stringify(unflatten(languages.get(lang)), {
doubleQuotedAsJSON: true,
singleQuote: true,
Expand All @@ -56,45 +59,45 @@ export async function POST() {
}
const nowIso = new Date().toISOString().replace(/:/g, '').split('.')[0];
const branchName = 'lyra-translate-' + nowIso;
await git.checkoutBranch(branchName, MAIN_BRANCH);
await git.checkoutBranch(branchName, lyraconfig.baseBranch);
await git.add('.');
await git.commit('Lyra Translate: ' + nowIso);
await git.push(['-u', 'origin', branchName]);
const pullRequestUrl = await createPR(branchName, nowIso);
await git.checkout(MAIN_BRANCH);
const pullRequestUrl = await createPR(branchName, lyraconfig.baseBranch, nowIso);
await git.checkout(lyraconfig.baseBranch);
await git.pull();
return NextResponse.json({
branchName,
pullRequestUrl,
});
} catch (e) {
logError(e);
err(e);
throw e;
} finally {
syncLock = false;
}

async function createPR(branchName: string, nowIso: string): Promise<string> {
async function createPR(branchName: string, baseBranch: string, nowIso: string): Promise<string> {
const octokit = new Octokit({
auth: GITHUB_AUTH,
baseUrl: 'https://api.github.com',
log: {
debug: () => {},
error: logError,
error: err,
info: () => {},
warn: logWarn,
warn: warn,
},
request: {
agent: undefined,
fetch: undefined,
timeout: 0,
},
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
userAgent: 'Lyra v' + version,
userAgent: 'Lyra v' + packageJson.version,
});

const response = await octokit.rest.pulls.create({
base: MAIN_BRANCH,
base: baseBranch,
body: 'Created by LYRA at: ' + nowIso,
head: branchName,
owner: GITHUB_OWNER,
Expand Down
2 changes: 1 addition & 1 deletion webapp/src/components/MessageForm.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MessageData } from '@/utils/readTypedMessages';
import { type MessageData } from '@/utils/adapters';
import {
Box,
Button,
Expand Down
6 changes: 6 additions & 0 deletions webapp/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ export class LanguageNotFound extends Error {
super(`Language ${lang} not found`);
}
}

export class LyraConfigReadingError extends Error {
constructor(filename: string) {
super(`Error reading file: [${filename}]`);
}
}
11 changes: 6 additions & 5 deletions webapp/src/utils/adapters/MessageAdapterFactory.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import TSMessageAdapter from './TSMessageAdapter';
import YAMLMessageAdapter from './YAMLMessageAdapter';
import TsMessageAdapter from './TsMessageAdapter';
import YamlMessageAdapter from './YamlMessageAdapter';
import LyraConfig, { MessageKind } from '../config';

export default class MessageAdapterFactory {
static createAdapter(config: LyraConfig) {
if (config.messageKind == MessageKind.TS) {
return new TSMessageAdapter(config.messagesPath);
// TODO: make it multi projects
if (config.projects[0].messageKind == MessageKind.TS) {
return new TsMessageAdapter(config.projects[0].messagesPath);
} else {
return new YAMLMessageAdapter(config.messagesPath);
return new YamlMessageAdapter(config.projects[0].messagesPath);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { MessageData } from '.';
import { type MessageData } from '.';
import mock from 'mock-fs';
import TSMessageAdapter from './TSMessageAdapter';
import TsMessageAdapter from './TsMessageAdapter';
import { describe, expect, it } from '@jest/globals';

describe('TSMessageAdapter', () => {
describe('TsMessageAdapter', () => {
describe('getMessages()', () => {
it('Finds messageIds.ts file and parses it', async () => {
mock({
Expand All @@ -15,7 +15,7 @@ describe('TSMessageAdapter', () => {
].join('\n'),
});

const msgAdapter = new TSMessageAdapter();
const msgAdapter = new TsMessageAdapter();
const messages = await msgAdapter.getMessages();

expect(messages).toEqual(<MessageData[]>[
Expand Down Expand Up @@ -48,7 +48,7 @@ describe('TSMessageAdapter', () => {
].join('\n'),
});

const msgAdapter = new TSMessageAdapter();
const msgAdapter = new TsMessageAdapter();
const messages = await msgAdapter.getMessages();

expect(messages).toEqual(<MessageData[]>[
Expand All @@ -75,7 +75,7 @@ describe('TSMessageAdapter', () => {
].join('\n'),
});

const msgAdapter = new TSMessageAdapter('/path/to/src');
const msgAdapter = new TsMessageAdapter('/path/to/src');
const messages = await msgAdapter.getMessages();

expect(messages).toEqual(<MessageData[]>[
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import fs from 'fs/promises';
import path from 'path';
import readTypedMessages from '../readTypedMessages';
import { IMessageAdapter, MessageData } from '.';
import { IMessageAdapter, type MessageData } from '.';

export default class TSMessageAdapter implements IMessageAdapter {
export default class TsMessageAdapter implements IMessageAdapter {
private basePath: string;

constructor(basePath: string = 'src') {
Expand Down
Loading

0 comments on commit 72562e2

Please sign in to comment.