顯示具有 Ubuntu 標籤的文章。 顯示所有文章
顯示具有 Ubuntu 標籤的文章。 顯示所有文章

2022年9月28日 星期三

[E2E Testing] 如何從 cypress 9 升級 10

使用 cypress 進行 E2e Testing,針對比較常用的部分整理從 Cypress 9 升級到 10 一些異動,最大的差別是不再支援 cypress.json,並移除了 plugins 目錄。


安裝 cypress 10

npm install --save-dev cypress@10

如何升級至 cypress 10

主要針對 e2e 調整的部分進行說明,啟動 cypress 時,原本會讀取根目錄建立 cypress.json的設定,cypress 10不再支援。檔案格式可以支援 js/ts,底下範例以cypress.config.js為主,另外參數的設定也做出調整:

baseUrl

從設定在最上層,改成專屬 e2e 測試的設定,若只做 component testing 就不需要設定這個參數,讓 e2e / component testing 的設定更一目瞭然。

Before cypress.json
{
  'baseUrl': 'http://localhost:1234'
}
After cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  e2e: {
    baseUrl: 'http://localhost:1234'
  }
});

pluginsFile

這個設定不需要,直接移除即可。

Before cypress.json
{
  'pluginsFile': 'cypress/plugins/index.js'
}

setupNodeEvents

移除 pluginsFile 的設定後,原本的內容也直接移到 cypress.config.js 進行設定,過去版本9若同時使用 component / e2e testing,可以將設定直接分開來寫。

Before cypress/plugins/index.js
module.exports = (on, config) => {
  if (config.testingType === 'component') {
    // component testing dev server setup code
    // component testing node events setup code
  } else {
    // e2e testing node events setup code
  }
};
After cypress.config.js
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  component: {
    devServer(cypressConfig) {
      // component testing dev server setup code
    },
    setupNodeEvents(on, config) {
      // component testing node events setup code
    },
  },
  e2e: {
    setupNodeEvents(on, config) {
      // e2e testing node events setup code
    },
  },
});

完整內容請參考官網升級指引


相關文章:


2022年5月28日 星期六

Vue3+jest 測試 composable 範例


Vue3 的 composable 乍看下和 mixin 用途很類似,可以提供各個元件共用程式碼。但與 mixin 相比,composable 主要有三個優勢:

第一,元件可以很明確的區分使用的 composable 來源,當使用的 mixin 一多時,追朔來源相對困難,無法一眼看出由哪個 mixin 實作。

第二,多個 mixin 無法確保使用了相同的名稱,可能造成覆蓋,但 composable 即使有相同的名稱,也能透過結構式賦值、重新命名。

最後,多個 mixin 需要交互作用時,通常會使用相同的參數命名來達到這個目的,隱性的耦合使得辨識和debug難度增加,composable可藉由其一的回傳值,作為其他composable輸入的參數達到共享的目的。(參考資料)


安裝 Jest 測試 vue 時,首先要注意版本,請參考下面的對照表安裝套件:

Vue version Jest Version npm Package

Vue 2 Jest 26 and below vue-jest@4

Vue 3 Jest 26 and below vue-jest@5

Vue 2 Jest 27 and above @vue/vue2-jest@27

Vue 3 Jest 27 and above @vue/vue3-jest@27

Vue 2 Jest 28 and above @vue/vue2-jest@28

Vue 3 Jest 28 and above @vue/vue3-jest@28


composable 的測試主要分成兩種,第一種元件比較無關,可以當作普通的 js code 測試。

// counter.js
import {ref} from 'vue';

export function useCounter() {
  const count = ref(0);
  const increment = () => count.value++;

  return {
    count,
    increment,
  };
};
// counter.test.js
import {useCounter} from './counter.js';

test('useCounter', () => {
  const {count, increment} = useCounter()
  expect(count.value).toBe(0);

  increment();
  expect(count.value).toBe(1);
});

如果 composable 牽涉到元件 lifecyle hooks 或是 provide/inject 時,需要依附一個元件進行測試。

// test-utils.js
import {createApp} from 'vue';

export function withSetup(composable) {
  let result;

  const app = createApp({
    setup() {
      result = composable();
      // suppress missing template warning
      return () => {};
    },
  });

  app.mount(document.createElement('div'));
  // return the result and the app instance
  // for testing provide / unmount
  return [result, app];
};
// counter.js
import {ref, onMounted} from 'vue';

export function useCounter() {
  const count = ref(0);
  const increment = () => count.value++;
  
  onMounted(() => {
    increment();
  });

  return {
    count,
    increment,
  };
};

result是 composable 的回傳值(return),測試裡面 app.mount();,將執行 onMounted

// counter.test.js
import {withSetup} from './test-utils';
import {useCounter} from './counter.js'

test('useCounter', () => {
  const [result, app] = withSetup(() => useCounter());

  // run assertions
  expect(result.count.value).toBe(0);

  // trigger onMounted hook if needed
  app.mount();

  expect(result.count.value).toBe(1);
});


另外還有一個常犯的錯誤,先看 composable 程式碼,有一個 computed 使用 formatFn 轉換 list:

// useListFormatter.js
import {computed, unref} from 'vue';

export function useListFormatter(list, formatFn) {
  const formattedList = computed(() => (
    unref(list).map(item => formatFn(item))
  ));

  return {
    formattedList,
  };
};

測試時需要注意回傳值被使用之前,formattedList 不會被執行。

// useListFormatter.test.js
import {useListFormatter} from './useListFormatter.js';

test('useListFormatter', () => {
  const list = ['a', 'b'];
  const formatFn = jest.fn();

  formatFn.mockImplementation(value => `new-${value}`)

  const {formattedList} = useListFormatter();
  
  // Don't do it! formatFn won't be excuted until formattedList is called.
  // expect(formatFn).toHaveBeenCalled();
  
  expect(formatFn).not.toHaveBeenCalled();

  expect(formattedList.value).toEqual(['new-a', 'new-b']);
  expect(formatFn).toHaveBeenCalledTimes(2);
  expect(formatFn).toHaveBeenNthCalledWith(1, 'a');
  expect(formatFn).toHaveBeenNthCalledWith(2, 'b');
});


參考資料:

https://vuejs.org/guide/scaling-up/testing.html#testing-composables


2022年3月5日 星期六

測試套件 Jest mock 基礎概念及 spyOn, fn 範例程式

當測試的對象有使用其他引用(import)時,我們希望能做為控制變因,設法聚焦測試的對象。Jest是常用的js單元測試套件,我們會 mock 方法spyOnfn來達成這個目的,Jest 安裝可以參考「[JavaScript] 安裝Jest單元測試」,底下說明幾種常用的情境。

jest.spyOn(object, methodName)

1. [基本用法] 監聽
單純測試某個method被呼叫的次數,或(官方說明),舉例下面範例是輸入兩個數字,並隨機輸出這個範圍內的數字。
// getRandonInt.js

export const getRandonInt = (min, max) => (
    Math.floor(Math.random(max) * (max - min) + min)
);
例如 Math.floor
jest.spyOn(Math, 'floor')
同理 Math.random ,所以可以將測試寫成:
import {getRandonInt} from './getRandonInt.js';

test('Should get random number between input min and max', () => {
    const spyFloor = jest.spyOn(Math, 'floor');
    const spyRandom = jest.spyOn(Math, 'random');

    const result = getRandonInt(1, 100);

    expect(result).toBeGreaterThanOrEqual(1);
    expect(result).toBeLessThanOrEqual(100);

    expect(spyFloor).toHaveBeenCalledTimes(1);

    expect(spyRandom).toHaveBeenCalledTimes(1);
    expect(spyRandom).toHaveBeenCalledWith(100);
});

因此可看到我們監聽的兩個method,第一 toHaveBeenCalledTimes 分別被呼叫的次數,及 toHaveBeenCalledWith 傳入的參數。


2. [進階] 替換回傳值
改變某個method的回傳值,以下面的範例是希望輸入一個值後,可以隨機產生一個數字並得到兩者相乘的結果
// multiplyNumber.js

export const multiplyNumber = num => (
    Math.random(10) * num
);
如果希望控制隨機產生的數字,可以使用 mockImplementation
import {multiplyNumber} from './multiplyNumber.js';

test('Should get the multiple of random number and input num', () => {
    const spyRandom = jest.spyOn(Math, 'random')
        .mockImplementation(() => 5);

    expect(Math.random(10)).toBe(5);
    expect(multiplyNumber(1000)).toBe(5000);

    spyRandom.mockRestore();

    expect(Math.random(10)).not.toBe(5);
});
可以理解成實際我們已經將 Math.random 變成:
Math['random'] = () => 5;

// you could definitely get parameter when method was called
// so implementation would be like ...
// Math['random'] = a => (a + 1);

mockRestore()可以恢復原本method的功能,因此直到我們呼叫 mockRestore()為止,無論我們呼叫幾次 Math.random ,都會按照我們的設定回傳數字 5。


假設希望這個設定是一次性的,則可以改用 mockImplementationOnce,這個寫法比較不容易出錯,是比較推薦的用法。

jest.spyOn(Math, 'random')
    .mockImplementationOnce(() => 5);
    .mockImplementationOnce(() => 20);

expect(Math.random(10)).toBe(5);
expect(Math.random(10)).toBe(20);


jest.fn(implementation)

用法和 spyOn非常類似,可以用來驗證呼叫次數及傳入參數等

const mockFn = jest.fn();

mockFn(100);

expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn).toHaveBeenCalledWith(100);
驗證的部分,toHaveBeenCalledTimestoHaveBeenCalledWith這兩個還有另一個寫法:
expect(mockFn).toHaveBeenCalledTimes(1);
expect(mockFn.mock.calls.length).toBe(1);

expect(mockFn).toHaveBeenCalledWith(100);
expect(mockFn.mock.calls[0][0]).toBe(100);

calls是一個陣列,長度等同於呼叫的次數,陣列內每一個位置也都是一個陣列,每個位置則對應到呼叫時的參數內容。可以實際印出得到 calls = [[100]],所以calls[0][0]代表的是第一次呼叫時第一個參數是什麼


依照 multiplyNumber.js 的範例,可以使用fn達到同樣的效果,測試結束前也要記得呼叫mockRestore免得影響其他測試‧。

const spyRandom = jest.fn(() => 5);
Math['random'] = spyRandom;
const spyRandom = jest.fn()
    .mockImplementation(() => 5);
Math['random'] = spyRandom;
const spyRandom = jest.fn()
    .mockReturnValueOnce(5);
Math['random'] = spyRandom;


jest.mock(module)

當測試有引入(import)其他模組、函式時,可以使用mock來替換回傳內容,我們稍微改寫剛剛的 multiplyNumber

import {getRandonInt} from './combineArray.js';

export const multiplyNumber = (min, max, num) => (
    getRandonInt(min, max) * num
);

為了清楚此時的getRandonInt已經被替換掉,我們刻意重新命名,在測試裡將回傳值設定為數字 20

import {getRandonInt as mockGetRandonInt} from './getRandonInt';
import {multiplyNumber} from './multiplyNumber';

jest.mock('./getRandonInt');

test('Should get the multiple of random number and input num', () => {
    mockGetRandonInt.mockReturnValueOnce(20);

    expect(multiplyNumber(1, 2, 3)).toBe(60);

    expect(mockGetRandonInt).toHaveBeenCalledTimes(1);
    expect(mockGetRandonInt).toHaveBeenCalledWith(1, 2);
});

另外可以利用requireActual可以取得原始的功能,當我們只需要部份取代時,可以藉此替換部分的內容,其餘皆按照原設定

const {getRandonInt} = jest.requireActual('../src/libs/combineArray');

mockGetRandonInt.mockImplementationOnce((a, b) => a + b);

expect(getRandonInt(1, 2)).toBeLessThanOrEqual(2);

expect(mockGetRandonInt(1, 2)).toBe(3);
expect(mockGetRandonInt).toHaveBeenCalledTimes(1);

最後一個重點,建立和初始化一個class的物件時,可以透過instances來得到建立的物件,它是一個陣列,會依照建立順序放在對應的位置上。

const myMock = jest.fn();

const a = new myMock();

expect(a).toBe(myMock.mock.instances[0]);

參考資料:



2022年1月25日 星期二

js 單元測試套件 Macha+Chai (es6 語法 Babel 設定)

安裝

首先安裝 mocha (官網)和 chai (官網)

npm install mocha chai --save-dev

chai 是一種斷言庫(Assertion Library),mocha 不像是jest已經內建,可以自行決定要使用哪一種套件,chai是其中最常見的一種。

package.json加入測試指令

"scripts": {
  "test": "npx mocha"
}


設定 Babel

安裝 Babel,再來的說明都會直接使用es6的語法,如果不需要,可以略過這部分。

npm install @babel/core @babel/preset-env @babel/register --save-dev

接著在根目錄新增 babel.config.js

// babel.config.js

module.exports = {
    presets: [
        '@babel/preset-env',
    ],
};

接著在根目錄新增 .mocharc.jsspec是設定 mocha 執行測試的檔案,預設為 test/ ,這裡設定成自動讀取 src/test/ 底下所有以 spec.js 為結尾的檔案。

require可以接陣列設定所需的 module

// .mocharc.js

module.exports = {
    spec: ['src/test/*.spec.js'],
    require: ['@babel/register'],
};

.mocharc.js 是 mocha 執行時會自動讀取的設定檔,也支援json 或 yaml(說明),可以選擇喜歡的格式新增,設定檔的內容可以參考:https://github.com/mochajs/mocha/tree/master/example/config

設定參考資料:
https://github.com/mochajs/mocha-examples/tree/master/packages/babel



建立測試

新增一個簡單的測試

// src/test/array.spec.js

import chai from 'chai';

describe('Array', () => {
  describe('#indexOf()', () => {
    it('should return -1 when the value is not present', function() {
      chai.assert.equal([1, 2, 3].indexOf(4), -1);
    });
  });
});

馬上執行試試看

npm run test

應該會看到下面的資訊,

> vue3-webpack5-template@1.0.0 test
> npx mocha


  Array
      #indexOf()
          should return -1 when the value is not present

1 passing (8ms)

完成第一個測試囉!



支援 async/await

請安裝套件

npm install @babel/plugin-transform-runtime --save-dev

把套件加到 babel.config.js的 plugin 設定

module.exports = {
    require: ['@babel/register'],
    plugins: ['@babel/plugin-transform-runtime'],
};


Root Hooks

mocha 提供的 hook 包含 before(), after(), beforeEach(), and afterEach(),從語意應該不難看出用途,前兩個只會執行一次,後兩個則是每項測試都會執行。

若要在所有的測試上加上 Hook,則是有這四種 beforeAll(), afterAll(), beforeEach(), and afterEach(),執行時機不變,請根據情境加上執行內容:

// src/test/hooks.js

export const mochaHooks = {
  beforeEach: () => {
    // do something before every test

  }
};

接著在 .mocharc.js 加上設定

module.exports = {
    spec: ['src/test/*.spec.js'],
    require: [
      '@babel/register',
      'src/test/hooks.js',
    ],
};

https://mochajs.org/#hooks
https://mochajs.org/#defining-a-root-hook-plugin



測試結果

mocha 是個可愛的名稱,測試結果也可以換成很多有趣的模式:

npx mocha --report landing

https://mochajs.org/#reporters



支援瀏覽器介面

加上指定的路徑執行下面的指令

npx mocha init [PATH]
  • index.html
  • mocha.css
  • mocha.js
  • test.spec.js

目錄底下會多了上述這些檔案,此時只要將 test.spec.js 加入你的測試,再重新整理 index.html,就能看到測試結果已經輸出在畫面上。


可以將 chai 加入 index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Mocha</title>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <link rel="stylesheet" href="mocha.css" />
    <script src="https://unpkg.com/chai/chai.js"></script>
  </head>
  <body>
    <div id="mocha"></div>
    <script src="mocha.js"></script>
    <script>
      mocha.setup('bdd');
    </script>
    <script src="tests.spec.js"></script>
    <script>
      mocha.run();
    </script>
  </body>
</html>

再來可以直接將前面新增的 src/test/array.spec.js 內容貼過去,index.html 已經載入 mocha 和 chai ,記得把第一行的 import 拿掉。



2022年1月23日 星期日

webpack 常用 plugin 介紹 - HtmlWebpackPlugin 自動產生 Html


根據「[Vue.js] 如何建立 Vue3 + webpack5 專案範例」的內容,封裝時已經先建立目錄 dist/ ,新增 index.html,預先將 main.js 加到 script 中。

接著,以下要介紹 HtmlWebpackPlugin ,這個 webpack plugin 可以自動產生 Html,並自動將所有的 js 檔加入 script 中。下面的操作會用 [vue3-webpack5-template] 這個專案操作,可以先 clone下來並 npm install。

一、安裝
npm install html-webpack-plugin --save-dev


二、新增模板

新增 index.html ,並保留一些區域方便我們可以自訂。

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8"/>
        <title>
            <%= htmlWebpackPlugin.options.title %>
        </title>
    </head>
    <body>
        <div id="<%= id %>"></div>
    </body>
</html>


三、新增 Webpack 設定

為了突顯 HtmlWebpackPlugin 功能的強大,首先刻意將輸出的 js 檔案名稱改為 [name].[contenthash].js,這時變得無法預期編譯後的檔案名稱。

再來將 HtmlWebpackPlugin 加入 plugin,完整檔案請點連結

// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  output: {
    filename: '[name].[contenthash].js',
    path: path.resolve(__dirname, 'dist'),
  },

  ...

  plugins: [
    new HtmlWebpackPlugin({
      template: 'template/index.html',
      title: 'Getting Started',
      templateParameters: {
        id: 'app',
      },
    }),
  ],
};

上面共使用了三個參數:

1. template - 顧名思義是模板的路徑
2. title - html 檔案的 <title>
也說明為什麼在 index.html 有這一段

<%= htmlWebpackPlugin.options.title %>

3. templateParameters - 這裡傳入一個物件,將 id 訂為 app,因此模板這段程式碼,將會使用這裡的設定複寫

<div id="<%= id %>"></div>

若要了解更多參數的使用,可以參考文件



四、執行

執行下面的指令開始打包

npm run build

程式碼已經 push 到分支test-webpack-plugin,或者參考變更可以按這裡


2021年10月1日 星期五

如何將 Github 專案部屬到 Heroku (Creating a 'Deploy to Heroku' Button)

先前「[django] 將Django專案部署到Heroku」提到如何一步一步建立 Django 專案,並部屬到 Heroku。若是Github現有的專案已經滿足 Heroku 基本所需的設定,可以透過網頁操作,將現有專案快速地複製到 Heroku 上。

操作說明使用 heroku-startup-settings 這個專案 ,可以到 Github 查看完整的程式碼。首先,Github專案必須在根目錄先建立 app.json 這個檔案:

{
  "name": "Heroku startup settings",
  "description": "Getting started with Django project",
  "repository": "https://github.com/chenuin/heroku-startup-settings",
  "logo": "https://node-js-sample.herokuapp.com/node.png",
  "keywords": ["heroku", "python", "django"]
}
(app.json)

內容很好理解,檔案沒有必填的欄位,通常會寫namedescriptionlogo有助於其他人理解這個專案的內容或目的,更多的設定可以參考 app.json schema

其中設定環境參數 env 應該是最常使用的,可以依照需求調整部屬。


app.json 正確建立後,就可以開啟這個連結。
https://heroku.com/deploy?[REPO_WEB_URL]/tree/[BRANCH_NAME]
請替換專案的路徑和分支名稱,因此連結為:

接者可以在 README.md 新增這段文字,
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy?template=https://github.com/chenuin/heroku-startup-settings/tree/master)

請記得替換成自己的連結,按鈕效果就像這樣:

Deploy

點選按鈕開啟連結並填寫專案名稱,按下「Deploy app」,就會自動在 Heroku 建立一個一樣的新專案。


相關文章:

[django] 將Django專案部署到Heroku

參考資料:

https://devcenter.heroku.com/articles/heroku-button https://devcenter.heroku.com/articles/app-json-schema

2021年8月17日 星期二

Github Actions 自動 Jekyll 專案部屬 Github Pages

自從完成「Travis CI 自動 Jekyll 專案部屬 Github Pages 完整範例」很久沒有更新 Github Pages,近期上傳時卻發現CI執行失敗,並不是腳本有問題或編譯過程有錯誤,而是request被拒,所以即使換了Github Token依舊無法解決。

直到我發現 Travis CI 帳戶設定突然多了「Plan」,有免費的方案可以選擇,選擇之後再讓Job重跑居然就成功了,所謂的免費方案是給 10000 available credits,每次執行Job時就會扣點直到用完為止,之後可以一次性購買或者選擇月費方案。


Github Actions 是 Github 內建的功能,可以定義Repo自動化、執行CI/CD相關工作,因此決定將 Travis CI 的功能由 Github Actions 取代,以下將說明如何移除 myblog 原本 .travis.yml 的設定 ,以 Github Actions 自動部屬至 Github Pages。


一、選擇 Workflow 建立方式

方法一:

在專案根目錄

新增目錄 .github/workflow/,並在 workflow/ 新增副檔名為 .yml的檔案。

方法二:

到 Github repo上設定

可更改檔名和底下的內容


二、新增 Workflow

name 命名,可自行決定名稱

name: website

on 必填的設定,Github Action提供各種事件供選擇觸發時機,這邊設定為 push 時執行,也可以針對多個事件指定分支。

# Triggered when code is pushed to any branch in a repository
on: push


# Or ....
on:
  # Trigger the workflow on push,
  # but only for the main branch
  push:
    branches:
      - main

jobs 接下來是最重要的部分,workflow是由一個或多的Job組成,預設情況下會同時執行,每個 Job 必須給一個唯一的 job_id,是由數字、字母或 _- 組成的字串,且字首只能由字母或是 _。因此我們第一個發布 - publish 的動作:

jobs:
  publish:

再來是 publish 內需要哪些設定:

runs-on 必填的設定,使用哪種機器執行 workflow,像是 Ubuntu、macOS、windows server等 Github 提供的虛擬環境。

runs-on: ubuntu-latest

steps 定義有哪些任務,Job 由一連串的任務組成,可以替任務命名,會在 Github 上顯示。


uses 指定動作(Action),通常是一些重複性的執行動作,被打包成一個套件,可以在這裡直接使用,更多相關的套件可以到 Github marketplace 搜尋 。底下使用的是checkout一個新的分支並安裝Ruby。

steps:
  # Reference the major version of a release
  - uses: actions/checkout@v2   # Downloads a prebuilt ruby
  - uses: ruby/setup-ruby@v1

run 在虛擬機器 shell 上執行 command-line

steps:
  - run: bundle install
  - run: chmod +x ./script/cibuild
  - run: ./script/cibuild

with 執行動作時可以加入參數,利用 github-pages 可以快速設定,我們指定將網站部署到 chenuin/chenuin.github.io 這個 repo 的 master 分支,需要上傳的目錄為 ./_site ,也就是Jekyll編譯好的檔案目錄。

steps:
  - uses: crazy-max/ghaction-github-pages@v2
    with:
      repo: chenuin/chenuin.github.io
      target_branch: master
      build_dir: ./_site
    env:
      GITHUB_TOKEN: ${{ secrets.GH_PAT }}

env 這裡分為 GITHUB_TOKENGH_PAT兩種,前者不需要任何設定、僅限於同一個 repo 內的操作,後者則是部署到其他 repo 使用的。(說明)

GH_PAT 設定方式,請先至 Settings => Developer settings => Personal access tokens (或下方連結),按右上角新增Token,scopes 可直接勾選 repo 並將Token複製。

https://github.com/settings/tokens


再到專案的 Settings => Secrets => Actions (或下方連結),按右上角新增,命名 GH_PAT 並將剛剛複製的Token貼上。

https://github.com/[USER_NAME]/[REPO_NAME]/settings/secrets/actions

關於 GITHUB_TOKEN 可以看這部影片了解更多:


完整檔案如下:

name: website

on: push

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
      - name: Set up Ruby
        uses: ruby/setup-ruby@v1
        with:
          ruby-version: 2.6
      - name: Install dependencies
        run: bundle install
      - name: Before srcipt
        run: chmod +x ./script/cibuild
      - name: Srcipt
        run: ./script/cibuild
      - name: Deploy to GitHub Pages
        if: success()
        uses: crazy-max/ghaction-github-pages@v2
        with:
          repo: chenuin/chenuin.github.io
          target_branch: master
          build_dir: ./_site
        env:
          GITHUB_TOKEN: ${{ secrets.GH_PAT }}

檔案異動內容已經上傳至 Github ,可點選連結查看。


三、查看執行結果

https://github.com/[USER_NAME]/[REPO_NAME]/actions



相關文章:

參考連結:


2021年8月5日 星期四

安裝 Caddy 發生 ca-certificates 錯誤解決方式



按照「Ubuntu 安裝 web server Caddy 2」遇到的安裝問題:

echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \ | sudo tee -a /etc/apt/sources.list.d/caddy-fury.list
sudo apt update

執行第二行 update 後會跳出錯誤訊息:

"Certificate verification failed: The certificate is NOT trusted. The certificate issuer is unknown. Could not handshake: Error in the certificate verification. [IP: XX.XXX.XX.XXX XXX] Reading package lists... Done W: https://apt.fury.io/caddy/InRelease: No system certificates available. Try installing ca-certificates.



代表需要先安裝 ca-certificates

sudo apt install ca-certificates

安裝後重新再一次指令 sudo apt update ,就不會出現錯誤了。



參考資料:
https://caddy.community/t/solved-failed-to-install-caddy-with-official-auto-installation-command/9877

2020年11月25日 星期三

Ubuntu 安裝 web server Caddy 2


安裝

按照指令安裝 Caddy 2

echo "deb [trusted=yes] https://apt.fury.io/caddy/ /" \ | sudo tee -a /etc/apt/sources.list.d/caddy-fury.list
sudo apt update
sudo apt install caddy

安裝完成後,執行 caddy 應該會跑出相關的指令提示

caddy



常用指令

執行 caddy

caddy run

等同於 caddy run,在背景程式運作

caddy start

將 Caddyfile 轉成 json config

caddy adapt
caddy adapt --config [PATH_TO_FILE]

重新載入 caddy,可使新的設定生效

caddy reload

關閉 caddy

caddy stop


2020年10月16日 星期五

Ubuntu 20 安裝及建立 Angular 專案

請先安裝 node.js
sudo apt update
sudo apt install build-essential libssl-dev
curl https://raw.githubusercontent.com/creationix/nvm/v0.36.0/install.sh | sh
source ~/.profile
nvm install v12.19.0


安裝 Angular CLI 套件
npm install -g @angular/cli


建立專案
新增一個 my-app 的專案,執行指令後會詢問是否安裝 Angular routing 及樣式語言(stylesheet),不選擇的話可以直接按enter使用預設值。
ng new my-app
執行需要一點時間,完成後會在目前的目錄中出現一個 my-app/ 的資料夾。

執行
cd my-app
ng serve

請用瀏覽器開啟頁面
# run `ng serve --open` to open your browser automatically
http://localhost:4200/



相關文章:

2020年6月25日 星期四

Travis CI 自動 Jekyll 專案部屬 Github Pages 完整範例


了解如何「[Github] 在github.io建立免費的網站」,搭配使用 Jekyll 可以讓部落格或靜態網站的撰寫更加便利,完成內容的更新後,可以透過指令編譯 _site 的目錄,轉成瀏覽器可讀的格式,最後將目錄上傳到web server。

這些流程可以使用 Travis CI 自動化,當 github repo 收到新的 Request時,自動編譯上傳到 github.io。 若未有 Jekyll 專案,請先參考「靜態網頁部落格工具 Ruby + Jekyll 安裝教學」,建立一個新專案。

一、新增編譯腳本
mkdir script
vim script/cibuild
script/cibuild 內容如下(參考檔案):
#!/usr/bin/env bash
set -e # halt script on error

JEKYLL_ENV=production bundle exec jekyll build
# bundle exec htmlproofer ./_site

二、新增 Travis CI 設定檔
.travis.yml 內容如下(參考檔案):
language: ruby
rvm:
- 2.6
script: ./script/cibuild
before_script:
- chmod +x ./script/cibuild
deploy:
  provider: pages
  skip_cleanup: true
  github_token: $GITHUB_TOKEN
  repo: chenuin/chenuin.github.io
  local_dir: ./_site
  target_branch: master
  on:
    branch: master
TOKEN 設定(參考)
  1. 開啟頁面 https://github.com/settings/tokens/new
  2. 填寫備註(可以自行決定),並選擇 public_repo
  3. 送出
將畫面顯示的 token 複製保存好,到 Travis CI 設定環境變數,新增一個參數 GITHUB_TOKEN,內容就是剛剛複製的這一串字串 。 

※操作畫面可參考「Travis CI 快速部屬 Jekyll + Asciidoctor」的第四步驟 - 新增 github token 。

三、上傳
git add .
git commit -m 'init commit'
git push origin master

專案連結: 


Travis CI 快速部屬 Jekyll + Asciidoctor

Github.io可以支援markdown,若要 asciidoctor 進行編輯,可以透過 Jeykll 在 github 上提供的專案,免去複雜的設定,快速完成布署。內容是參考 README,作業系統為 Ubuntu,統整成下面的步驟:

一、安裝

二、下載原始碼
請先 fork jekyll-asciidoc-quickstart ,再下載專案 jekyll-asciidoc-quickstart
git clone https://github.com/[github-account]/jekyll-asciidoc-quickstart

三、設定 Travis CI
Travic CI 先後推出 travis-ci.org、travis-ci.com,兩者的介面很相近,以下都是使用 travis-ci.com 進行操作。
請先到 github settings,將這個專案同步到 Travis CI上。 



 

四、新增 github token
  1. 開啟頁面 https://github.com/settings/tokens/new
  2. 填寫備註(可以自行決定),並選擇 public_repo
  3. 送出
請將畫面顯示的 token 複製保存好,一旦離開此頁面,就不會再看到這串 token。若遺失 token,只能再另外產生一組新的。
 


完成後,到 Travis CI 設定環境變數(Environment Variables) 
Name:  GH_TOKEN
Value: [token]  (剛剛在github複製的字串)

按下 Add 完成設定。 


 (連結參考 https://travis-ci.com/github/[github-account]/jekyll-asciidoc-quickstart/settings)
  
直接在網頁上新增更方便,原始文件提供的方法是安裝 travis 的套件,用指令加密 token 後,直接新增到 .travis.yml,也可以參考試試看。

五、編輯 .travis.yml
原始專案設定 rvm 版本已經太舊了,請把 2.2 改成 2.6,否則 CI 執行會失敗。
# .travis.yml
rvm:
  - 2.6
完整檔案請看: .travis.yml
git add .travis.yml
git commit -m 'update rvm version'

六、上傳
git push -u origin master
可以到 Travis CI 上確認執行結果,若正確設定的話,將自動把 master 的內容編譯後,自動上傳到分支 gh-pages 。 

開啟頁面 https://[github-account].github.io/jekyll-asciidoc-quickstart/ ,順利看到畫面就完成了。

完整專案範例


2020年5月21日 星期四

Asciidoctor 安裝及入門範例


Asciidoctor 是使用純文字編輯的文檔格式,與 Markdown 用途相近(用法比較),利用特定的標記可以呈現為HTML、PDF等其他格式,可以根據排版的需求搭配css或模板進行輸出。上一篇「ubuntu18 + python2 文件編輯 AsciiDoc 安裝說明」提到的 AsciDoc 的進化版,AsciDoc 已經停止更新,目前 Asciidoctor 更活耀,常用在電子書或Git專案文件的撰寫。[使用手冊]

安裝
安裝 Asciidoctor(作業系統 Ubuntu 18)
sudo apt-get install -y asciidoctor

完成後可以確認安裝版本
asciidoctor --version
asciidoctor -V


簡易範例
1. 建立檔案
新增一個檔案 sample.adoc,內容:
.Sample document
====
Here's a sample AsciiDoc document:

[listing]
....
= Title of Document
Doc Writer
:toc:

This guide provides...
....

The document header is useful, but not required.
====

2. 輸出 html
asciidoctor sample.adoc
在同一個目錄下會產生同檔名的HTML檔案 sample.html

預設情況都是轉成html5,並搭配css3,如果想要轉成其他格式,可以加上 -b(--backend) 來指定:
// for *.xml
asciidoctor -b docbook sample.adoc


Custom Theme
預覽剛剛的 sample.html ,發現除了標題、內文等會自動加上顏色或背景,是預設套用的 css3成果:

可利用自訂的 css 來美化頁面,以 asciidoctor-skins 寫好的主題套用作為範例。下載:
git clone https://github.com/darshandsoni/asciidoctor-skins.git

重新輸出時,-a(--attribute) 加上 css 的路徑:
asciidoctor -a stylesheet=./asciidoctor-skins/css/notebook.css sample.adoc

// or
asciidoctor -a stylesheet=notebook.css -a stylesdir=./asciidoctor-skins/css sample.adoc

開啟畫面會套用新的 stylesheet,如下:


常用參數
除了上面提到的 -a-b ,其他常見的選項:
1. 輸出檔名 -o
// -o, --out-file
asciidoctor sample.adoc -o mypage.html

2. 輸出路徑 -D
// -D, --destination-dir
asciidoctor sample.adoc -D pages
輸出檔案會在資料夾 /pages 內。

3. 僅輸出內容 -s
// -s, --no-header-footer
asciidoctor sample.adoc -o mypage.html
只會輸出html中 <body> 標籤裡的內容,方便用於嵌入式頁面。

更多參數可參考:
https://asciidoctor.org/docs/user-manual/#document-conversion

2020年5月1日 星期五

ubuntu18 + python2 文件編輯 AsciiDoc 安裝說明


基本上 AsciiDoc 已經停止更新,2017年已經發布了最終版本,並表示不再更新,當然 asciidoc 仍可繼續使用,直到停止支援 python2。既然都明寫建議用戶轉移使用 Asciidoctor(底層是Ruby) 或支援 Python3 的 asciidoc-py3,以下安裝步驟僅作為紀錄。

安裝之前請先確保主機已經安裝了Python(版本2.4以上),因為安裝的腳本是用Python寫的,多數的linux系統都已經內建,所以可以直接開始按照指令安裝asciidoc。

以下操作環境為ubuntu 18.04(server),目前ubuntu 20.04 LTS (2020/04 - 2025/04)為最新 LTS 版,但不影響下面的操作:

方法一
Step1. 下載原始碼
透過git下載原始碼,選用的版本為8.6.9
cd /bin
git clone https://github.com/asciidoc/asciidoc asciidoc-8.6.9

cd asciidoc-8.6.9
git checkout 8.6.9

Step2. 綁定指令
哈囉
(A) 綁定指令
ln -s /bin/asciidoc-8.6.9/asciidoc.py /bin/asciidoc
ln -s /bin/asciidoc-8.6.9/a2x.py /bin/a2x
(B) 編譯
autoconf
./configure
make
sudo make install


方法二
更快速的方法,可直接透過AsciiDoc Debian package安裝,指令:
apt install asciidoc

Official Site: http://asciidoc.org/

2020年3月31日 星期二

[Git] 手動安裝 git 2.6 + 新指令 sparse-checkout


sparse checkout 的功能可以讓使用者追蹤儲存庫裡特定的資料夾或檔案,不需要clone整個專案。新版本 git 2.6 有新指令 sparse-checkout 讓操作更簡單方便,關於 sparse-checkout 的設定方法可以參考「[Git] 如何使用 sparse checkout 下載 repository 內特定資料夾或檔案」進行操作,以下主要介紹如何手動安裝 git以及常用的 sparse-checkout 指令。

安裝

1. 安裝相關套件
安裝編譯 Git 所需的函式庫:curl, zlib, openssl, expat 和 libiconv
sudo apt-get install libcurl4-gnutls-dev libexpat1-dev gettext \
  libz-dev libssl-dev

2. 下載 git 原始碼
先下載 git 原始碼,接著解壓縮並進入目錄。
wget https://mirrors.edge.kernel.org/pub/software/scm/git/git-2.26.0.tar.gz
tar zxvf git-2.26.0.tar.gz
git-2.26.0
※如果需要其他 git 版本 [https://mirrors.edge.kernel.org/pub/software/scm/git/]

3. 編譯與安裝
編譯的時間稍微比較久
./configure --prefix=/usr \
            --with-gitconfig=/etc/gitconfig \
            --with-python=python3 && make
安裝
sudo make install

安裝後並沒有明顯的成功訊息,所以很難確定到底有沒有裝好,所以可以透過指令查看版本:
git --version
git version 2.26.0


指令說明

◆ sparse checkout 設定初始化
git sparse-checkout init
開啟設定之後,在目錄會自動新增檔案 sparse-checkout ,預設內容是:
/*
!/*/
代表根目錄內第一層內所有的檔案都列入追蹤;換句話說,根目錄裡任何資料夾內的檔案(包含資料夾)都會被忽略。

必須先確定檔案 .git/info/sparse-checkout 已經存在,使用下列的指令才不會報錯:
◆ 列出目前追蹤的檔案
git sparse-checkout list
◆ 新增指令資料夾或檔案
語法與 .gitignore 相似,最簡單是直接列出完整的檔案路徑,根據需求可以列出複雜的規則,像是追蹤特定的檔案附檔名,或是排除特定的項目。
git sparse-checkout add [folderPath/filePath]
◆ 更新及應用
將資料夾或檔案路徑規則寫入 sparse-checkout ,並馬上採用。
git sparse-checkout set [folderPath/filePath]
◆ 取消
git sparse-checkout disable

參考資料:
https://git-scm.com/download/linux
https://git-scm.com/book/zh-tw/v2/開始-Git-安裝教學
http://www.linuxfromscratch.org/blfs/view/svn/general/git.html

[Git] 如何使用 sparse checkout 下載 repository 內特定資料夾或檔案


一般下載專案的指令會將整個 repository 下載
git clone [URL]
有時候你只需要專案部分資料,git clone 無法只下載其中一個資料夾或檔案,但是 sparse checkout 可以達到!當專案非常龐大時,既能節省硬碟容量,也能提升工作效率。

1. 在現有資料夾中初始化倉儲
先決定要把資料放在哪個目錄,並執行初始化。
mkdir myproject
cd myproject
git init

2. 新增遠端版本庫
git remote add origin [URL]

3. 開啟 sparse checkout 設定
git config core.sparseCheckout true

4. 設定指定資料夾或檔案
根據你的需求,把資料夾或檔案的路徑寫進去。
方法一
echo "path/to/your/folder/" >> .git/info/sparse-checkout
echo "myFile.example" >> .git/info/sparse-checkout
方法二
vim .git/info/sparse-checkout
# .git/info/sparse-checkout
# Please list your directory and file path here
path/to/your/folder/
myFile.example

5. 取得儲存庫分支 master 的更新
最後一個步驟,git pull下載資料。
git pull origin origin master

2020年2月28日 星期五

[Git] 客製化Git Hooks管理專案



所謂的Git Hooks指的是以腳本等方式設定執行動作,當git指令執行時,會自動觸發這些腳本的機制,時機可能是在git指令之前或是之後。根據git指令又可以分為server side和client slide兩大類:commit和merge觸發的是client side,server side則是在網路上收到推上去的commit(pushed commit)。

hooks 存放在目錄 /.git/hooks/ 裡,執行 git init 初始化時,git自動在目錄裡產生一些shell script範本,你也可以用Ruby或python等實作。範例可以直接使用,但要記得把副檔名的 .sample 拿掉。



如果需要自己新建hook,請確認一下檔案的執行權限已經開啟。
chmod +x [filePath]

常見的Hooks
Client Side
以下為4個與commit相關的hook:
  • 在commit之前會執行 pre-commit,可以運用在執行功能驗證、單元測試或是coding style的檢查,若有任何錯誤發生時,這個commit會被捨棄不執行。也可以下指令 git commit --no-verify 略過hook的執行。
  • prepare-commit-msg,這個hook對於一般commit比較不實用,多在commit訊息模板、merge commit、squash commits、amended commits等自動產生commit訊息的類型。
  • commit-msg需要commit訊息的模板檔案路徑作為參數,如果錯誤發生,git會停止進行這個commit,來確保所有的commit訊息都符合需要的格式。
  • commit結束後,會執行 post-commit ,不需要任何參數,你可以用 git log -1 HEAD 來確認剛剛commit的內容,或者通知其他類似的訊息。


Server Side
  • server端收到push時,首先執行 pre-receive ,如果錯誤發生,所有的commit將不會被受理。可以用來檢查有沒有任何non-fast-forwards。
  • updatepre-receive 有點相似。
  • post-receive 會在整個push過程結束後執行,可以用來通知專案成員或是其他服務的更新等。這個hook不會影響push過程,但在hook執行完之前,用戶端和伺服器端會持續連線,因此要避免在腳本中執行時間較長的工作。

參考資料:
https://git-scm.com/book/zh-tw/v2/Customizing-Git-Git-Hooks

2019年8月14日 星期三

MySQL/MariaDB遠端連線設定



使用DbVisualizer(版本10.0.21)來連線VMWare開的虛擬機資料庫,卻顯示下面的錯誤:
MySQL Connection Error: (1130) Host 'xxx.xx.x.x' is not allowed to connect to this MySQL server


環境

  • MySQL 5.7.27
  • Ubuntu 18.04


步驟

1. 開放防火牆
預設的埠號(port number)3306。
sudo ufw allow mysql/tcp

sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
3306/tcp                   ALLOW       Anywhere
3306/tcp (v6)              ALLOW       Anywhere (v6)

2. 修改綁定的連線位址
請找到指定檔案
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
把這一行刪掉或註解
bind-address=127.0.0.1

3. 新增遠端連線的使用者
預設的情況下,用戶應該都只能從localhost進行連線,查詢方式:
SELECT User,Host FROM mysql.user;
+--------------+------------------+
| host         | User             |
+--------------+------------------+
| localhost    | debian-sys-maint |
| localhost    | mysql.session    |
| localhost    | mysql.sys        |
| localhost    | root             |
+--------------+------------------+
可以看到有4個使用者,允許從localhost進行連線。

方法一、新增指定IP的連線用戶
CREATE USER 'username'@'ip_address' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON *.* TO 'username'@'ip_address';
方法二、新增任意IP的連線用戶
如果是%就是不指定IP
CREATE USER 'username'@'%' IDENTIFIED BY 'mypassword';
GRANT ALL PRIVILEGES ON *.* TO 'username'@'%';
新增完成之後請執行:
FLUSH PRIVILEGES;

可以確認一下使用者有沒有新增成功,也可以查詢使用者權限:
SHOW GRANTS FOR username@ip_address;

// 查詢現在使用者的權限
SHOW GRANTS;
SHOW GRANTS FOR CURRENT_USER;
SHOW GRANTS FOR CURRENT_USER();

步驟到此就完成了!試著用剛剛的帳密登入mysql囉


DbVisualizer連線步驟

1.設定專案名稱

2. 資料庫類型

3. 填寫資料庫位址、port number、使用者帳密

參考資料:
https://docs.bitnami.com/virtual-machine/apps/reviewboard/administration/connect-remotely/

其他:
DbVisualizer 10.0.21 [安裝]
phpmyadmin的替代方案 https://alternative.me/phpmyadmin

2019年7月26日 星期五

Ubuntu18+PHP7.2 安裝Laravel 5.8


安裝composer

Composer是PHP套件管理工具,請參考『[Symfony] ubuntu18安裝symfony 4.2教學』第二部分提供兩種安裝方式。[官方安裝說明]


建立專案

提供兩個方式來建立一個專案,命名為 blog
方法一、
composer create-project --prefer-dist laravel/laravel blog
指令執行後會自動 composer install 讀取 composer.json 安裝所需及相依的套件。

方法二、
composer global require "laravel/installer"
2-A. 把 $HOME/.composer/vendor/bin 路徑放置於環境變數 $PATH 裡。
export PATH="~/.config/composer/vendor/bin:$PATH"
laravel new blog
2-B. 如果沒有設定環境變數,可能會有錯誤訊息laravel: command not found,也可以用決定路徑來執行。
# "沒有"設定環境變數
.config/composer/vendor/bin/laravel new blog


兩個方法二擇一,成功後目錄應該會多一個 blog/ 的專案目錄,進入專案並執行:
php artisan key:generate

# Example oupout
Application key set successfully.
如果沒有執行這一段,執行專案可能會跳出錯誤訊息 No application encryption key has been specified.


執行專案

開發時可以執行下面的指令讓專案運作,預設路徑為http://localhost:8000
php artisan serve
也可以指定 HOSTPORT
php artisan serve --port=8080 --host=192.168.88.139


打開瀏覽器看到下面的畫面,就可以進入開發囉!



查詢Laravel安裝版本
php artisan -V
Laravel Framework 5.8.29


參考資料:
https://laravel.com/docs/5.8

2019年4月23日 星期二

[Symfony] 如何將專案部屬到Heroku(apache/nginx)


之前寫過一篇『[django] 將Django專案部署到Heroku』,可以參考安裝heroku[官網說明]的方式,『[Symfony] ubuntu18安裝symfony 4.2教學』則可以了解composer和symfony的安裝方式,這篇就不再重複說明。

步驟一、新增Symfony專案
首先新增一個專案 symfony_heroku ,也可以根據需求指定專案版本,目前版本是3.4。
composer create-project symfony/framework-standard-edition symfony_heroku/

# 指定Symfony版本 3.0
composer create-project symfony/framework-standard-edition:^3.0 symfony_heroku/


步驟二、新增Procfile
進到專案目錄裡symfony_heroku/,以apache為例,新增檔案Procfile做為執行網站的依據。
cd symfony_heroku
echo 'web: $(composer config bin-dir)/heroku-php-apache2 web/' > Procfile

相當於:
vim Procfile
檔案Procfile內容:
web: $(composer config bin-dir)/heroku-php-apache2 web/
參數$(composer config bin-dir)是考慮到版本差異,可以動態的指到正確的路徑。


步驟三、部屬專案
使用git將所有檔案加入追蹤並commit。
git init
git add .
git commit -m "initial commit"

輸入指令 heroku login 登入後,建立一個Heroku專案,新增相關設定。
heroku create
heroku config:set SYMFONY_ENV=prod
上傳Heroku
git push heroku master


可以打開瀏覽器就可以看到symfony預設的網頁。
https://[APP_NAME].herokuapp.com

查詢Heroku專案名稱(顯示網址)
heroku open
關閉網頁伺服器
heroku ps:scale web=0
開啟網頁伺服器
heroku ps:scale web=1



[nginx]
假設你希望使用nginx作為網頁伺服器,請將Procfile改成:
web: $(composer config bin-dir)/heroku-php-nginx web/
預設/,沒有任何頁面,請看/app.php確定有沒有建立成功。
https://[APP_NAME].herokuapp.com/app.php


參考資料:
https://devcenter.heroku.com/articles/getting-started-with-symfony
https://devcenter.heroku.com/articles/getting-started-with-php

相關文章: