| @@ -0,0 +1,6 @@ | |||
| KINTONE_BASE_URL= | |||
| KINTONE_USERNAME= | |||
| KINTONE_PASSWORD= | |||
| MYPAGE_BASE_URL= | |||
| MYPAGE_TOKEN= | |||
| @@ -0,0 +1,54 @@ | |||
| module.exports = { | |||
| // -------------------------------- | |||
| // 呼び出したいルール(パッケージ) | |||
| // -------------------------------- | |||
| // ES5 & kintone の場合 | |||
| // extends: "@cybozu/eslint-config/presets/kintone-customize-es5", | |||
| // ES6以上 & kintone の場合 | |||
| extends: ["@cybozu", "@cybozu/eslint-config/globals/kintone"], | |||
| // node & kintone の場合 | |||
| // extends: ["@cybozu/eslint-config/presets/node", "@cybozu/eslint-config/globals/kintone"], | |||
| // -------------------------------- | |||
| // グローバル変数の定義 | |||
| // -------------------------------- | |||
| globals: { | |||
| garoon: "readonly", | |||
| }, | |||
| // -------------------------------- | |||
| // ルール | |||
| // -------------------------------- | |||
| rules: { | |||
| quotes: ["error", "single"], | |||
| "require-atomic-updates": "off", | |||
| }, | |||
| // -------------------------------- | |||
| // TypeScript用の設定 | |||
| // -------------------------------- | |||
| overrides: [ | |||
| { | |||
| files: ["*.ts", "*.tsx"], | |||
| extends: [ | |||
| "plugin:@typescript-eslint/eslint-recommended", | |||
| "plugin:@typescript-eslint/recommended", | |||
| "prettier/@typescript-eslint", | |||
| "@cybozu", | |||
| "@cybozu/eslint-config/globals/kintone", | |||
| ], | |||
| plugins: ["@typescript-eslint"], | |||
| parser: "@typescript-eslint/parser", | |||
| parserOptions: { | |||
| sourceType: "module", | |||
| }, | |||
| rules: { | |||
| quotes: ["error", "single"], | |||
| "require-atomic-updates": "off", | |||
| "no-undef": "off", | |||
| }, | |||
| }, | |||
| ], | |||
| }; | |||
| @@ -0,0 +1,18 @@ | |||
| # Logs | |||
| logs | |||
| *.log | |||
| npm-debug.log* | |||
| yarn-debug.log* | |||
| yarn-error.log* | |||
| # Dependency directories | |||
| node_modules/ | |||
| # webpack build directory | |||
| dist/ | |||
| # secret | |||
| .env | |||
| .vscode | |||
| @@ -0,0 +1,10 @@ | |||
| #!/bin/bash | |||
| cd $(dirname ${0}) | |||
| cd .. | |||
| # 環境変数の読み込み | |||
| # export $(cat .env.development | grep -v ^# | sed -e s/^DEV_//g | xargs) | |||
| echo "env-cmd kintone-dts-gen --app-id ${1} -o ./types/fields.d.ts" | |||
| npx env-cmd kintone-dts-gen --app-id ${1} -o ./types/fields.d.ts | |||
| @@ -0,0 +1,9 @@ | |||
| #!/bin/bash | |||
| cd $(dirname ${0}) | |||
| cd .. | |||
| # 環境変数の読み込み | |||
| export $(cat .env.development | grep -v ^# | sed -e s/^DEV_//g | xargs) | |||
| yarn upload | |||
| @@ -0,0 +1,19 @@ | |||
| import { execSync } from 'child_process'; | |||
| const file = | |||
| process.argv[2] | |||
| if (file !== undefined && file.length === 0) { | |||
| throw new Error("引数不正") | |||
| } | |||
| if (!file.endsWith('customize-manifest.json')) { | |||
| throw new Error("ファイル名不正 customize-manifest.jsonを指定してください") | |||
| } | |||
| console.log('\nuploading... ', file); | |||
| const command = `npx kintone-customize-uploader `; | |||
| const result = execSync(command + file); | |||
| console.log('\n' + result); | |||
| @@ -0,0 +1,50 @@ | |||
| { | |||
| "name": "kt-kintone", | |||
| "version": "1.0.0", | |||
| "description": "", | |||
| "main": "index.js", | |||
| "scripts": { | |||
| "test": "echo \"Error: no test specified\" && exit 1", | |||
| "build": "webpack --mode production", | |||
| "dev": "webpack --mode development", | |||
| "watch": "webpack -w --mode development", | |||
| "lint": "eslint src/**/*.js", | |||
| "fix": "eslint src/**/*.js --fix", | |||
| "upload": "cross-env NODE_ENV=development babel-node --presets @babel/env -r dotenv/config uploader.js", | |||
| "dev-upload": "cross-env NODE_ENV=development webpack && babel-node --presets @babel/env -r dotenv/config dev-uploader.js" | |||
| }, | |||
| "author": "sosuke iwabuchi", | |||
| "license": "MIT", | |||
| "devDependencies": { | |||
| "@babel/core": "^7.11.6", | |||
| "@babel/node": "^7.8.7", | |||
| "@babel/plugin-proposal-class-properties": "^7.10.4", | |||
| "@babel/preset-env": "^7.11.5", | |||
| "@babel/preset-typescript": "^7.10.4", | |||
| "@cybozu/eslint-config": "^8.0.0", | |||
| "@kintone/customize-uploader": "^3.0.1", | |||
| "@kintone/dts-gen": "^5.0.1", | |||
| "@types/react": "^17.0.3", | |||
| "@types/react-dom": "^17.0.3", | |||
| "@typescript-eslint/eslint-plugin": "^4.2.0", | |||
| "@typescript-eslint/parser": "^4.2.0", | |||
| "babel-loader": "^8.1.0", | |||
| "cross-env": "^7.0.3", | |||
| "dotenv": "^8.2.0", | |||
| "dotenv-webpack": "^8.0.1", | |||
| "env-cmd": "^10.1.0", | |||
| "eslint": "^6.8.0", | |||
| "fork-ts-checker-webpack-plugin": "^5.2.0", | |||
| "glob": "^7.1.6", | |||
| "ts-loader": "^8.0.17", | |||
| "tsconfig-paths-webpack-plugin": "^4.1.0", | |||
| "typescript": "^4.1.2", | |||
| "webpack": "^5.51.2", | |||
| "webpack-cli": "^4.8.0", | |||
| "webpack-dev-server": "^4.1.0" | |||
| }, | |||
| "dependencies": { | |||
| "@kintone/rest-api-client": "^2.0.17", | |||
| "core-js": "^3.6.4" | |||
| } | |||
| } | |||
| @@ -0,0 +1,12 @@ | |||
| { | |||
| "app": "301", | |||
| "scope": "ALL", | |||
| "desktop": { | |||
| "js": ["dist/定期予約選考.js"], | |||
| "css":[] | |||
| }, | |||
| "mobile": { | |||
| "js": [], | |||
| "css":[] | |||
| } | |||
| } | |||
| @@ -0,0 +1,97 @@ | |||
| import { AppID } from "@/common/appids"; | |||
| import { setHeaderButton } from "@/common/header-button"; | |||
| import { makeRecordData } from "@/common/rest-api-client"; | |||
| import { noticeToCandidates } from "@/mypage/定期予約選考"; | |||
| import { | |||
| 定期予約選考, | |||
| 定期予約選考フィールド名, | |||
| 選考ステータスDropdown, | |||
| } from "@/types/定期予約選考"; | |||
| import { 定期申込予約フィールド名, 状態Dropdown } from "@/types/定期申込予約"; | |||
| import { KintoneRestAPIClient } from "@kintone/rest-api-client"; | |||
| const getCallBackHandleNoticeTocandidates = (currentRecord: 定期予約選考) => { | |||
| return async () => { | |||
| const confirm = window.confirm("対象者へメール送信しますか?"); | |||
| if (!confirm || !currentRecord) return; | |||
| const result = await noticeToCandidates( | |||
| Number(currentRecord.レコード番号.value) | |||
| ); | |||
| if (result) { | |||
| window.alert("通知しました"); | |||
| location.reload(); | |||
| } else { | |||
| window.alert("失敗しました"); | |||
| } | |||
| }; | |||
| }; | |||
| const getCallBackHandleFinish = (currentRecord: 定期予約選考) => { | |||
| return async () => { | |||
| const confirm = window.confirm("確定しますか"); | |||
| if (!confirm || !currentRecord) return; | |||
| const client = new KintoneRestAPIClient(); | |||
| // 選考のステータスを終了にする | |||
| const param = { | |||
| app: AppID.定期予約選考, | |||
| id: kintone.app.record.getId() ?? "", | |||
| record: makeRecordData({ | |||
| [定期予約選考フィールド名.選考ステータス]: 選考ステータスDropdown.終了, | |||
| }), | |||
| }; | |||
| // 申込のステータスを選考当選にする | |||
| const entries = { | |||
| app: AppID.定期申込予約, | |||
| records: currentRecord.選考結果一覧.value.map((row) => { | |||
| return { | |||
| id: row.value.選考結果一覧_申込レコード番号.value, | |||
| record: makeRecordData({ | |||
| [定期申込予約フィールド名.状態]: 状態Dropdown.選考当選, | |||
| }), | |||
| }; | |||
| }), | |||
| }; | |||
| try { | |||
| await client.record.updateRecord(param); | |||
| await client.record.updateRecords(entries); | |||
| } catch (e) { | |||
| // 失敗 | |||
| window.alert("失敗しました"); | |||
| throw e; | |||
| } | |||
| window.alert("成功しました。当選者の承認処理を行ってください"); | |||
| location.reload(); | |||
| }; | |||
| }; | |||
| (() => { | |||
| const events = ["app.record.create.show", "app.record.edit.show"]; | |||
| kintone.events.on(events, (event) => { | |||
| const record = event.record as 定期予約選考; | |||
| }); | |||
| kintone.events.on("app.record.detail.show", (event) => { | |||
| const currentRecord: 定期予約選考 = event.record; | |||
| // 各種ボタンの設置 | |||
| if ( | |||
| currentRecord.選考ステータス.value === 選考ステータスDropdown.通知者選択中 | |||
| ) { | |||
| setHeaderButton( | |||
| "通知メール送信", | |||
| getCallBackHandleNoticeTocandidates(currentRecord) | |||
| ); | |||
| } | |||
| if ( | |||
| currentRecord.選考ステータス.value === 選考ステータスDropdown.候補者仮決定 | |||
| ) { | |||
| setHeaderButton("当選者確定", getCallBackHandleFinish(currentRecord)); | |||
| } | |||
| }); | |||
| })(); | |||
| @@ -0,0 +1,6 @@ | |||
| export const AppID = { | |||
| 定期申込予約: 271, | |||
| 定期予約選考: 301, | |||
| 問い合わせ: 291, | |||
| } as const; | |||
| export type AppID = (typeof AppID)[keyof typeof AppID]; | |||
| @@ -0,0 +1,39 @@ | |||
| export const setHeaderButton = (title: string, onClick: VoidFunction) => { | |||
| const interval = setInterval(() => { | |||
| const wrapper = document.getElementsByClassName( | |||
| "gaia-app-statusbar-actionmenu-wrapper" | |||
| )[0]; | |||
| if (wrapper) { | |||
| // メニューが存在しなければ作成する | |||
| let menu = wrapper.querySelector(".gaia-app-statusbar-actionmenu"); | |||
| if (!menu) { | |||
| menu = document.createElement("div"); | |||
| menu.classList.add(".gaia-app-statusbar-actionmenu"); | |||
| wrapper.appendChild(menu); | |||
| } | |||
| // ステータスバーの表示が無効になっていれば解除する | |||
| const statusbar = document.querySelector<HTMLDivElement>( | |||
| ".gaia-app-statusbar" | |||
| ); | |||
| if (statusbar) { | |||
| statusbar.style.display = "unset"; | |||
| } | |||
| const button = document.createElement("span"); | |||
| button.classList.add("gaia-app-statusbar-action"); | |||
| const label = document.createElement("span"); | |||
| label.setAttribute("title", title); | |||
| label.innerText = title; | |||
| label.classList.add("gaia-app-statusbar-action-label"); | |||
| button.onclick = onClick; | |||
| button.appendChild(label); | |||
| menu.appendChild(button); | |||
| clearInterval(interval); | |||
| } | |||
| }, 50); | |||
| }; | |||
| @@ -0,0 +1,20 @@ | |||
| export const setFieldValue = (fieldCode: string, value: string | object) => { | |||
| return { | |||
| [fieldCode]: { | |||
| value, | |||
| }, | |||
| }; | |||
| }; | |||
| export const makeRecordData = (data: { | |||
| [fieldCode: string]: string | object; | |||
| }) => { | |||
| let ret = {}; | |||
| Object.keys(data).forEach((fieldCode) => { | |||
| ret = { | |||
| ...ret, | |||
| ...setFieldValue(fieldCode, data[fieldCode]), | |||
| }; | |||
| }); | |||
| return ret; | |||
| }; | |||
| @@ -0,0 +1,5 @@ | |||
| export const MyPageApiID = { | |||
| メール送信依頼: "email/send", | |||
| 定期選考一斉通知: "season-ticket-contract-selection/notice-to-candidates", | |||
| } as const; | |||
| export type MyPageApiID = (typeof MyPageApiID)[keyof typeof MyPageApiID]; | |||
| @@ -0,0 +1,34 @@ | |||
| import { MyPageApiID } from "."; | |||
| type MyPageApiResponse = { | |||
| result: "SUCCESS" | "FAILED"; | |||
| }; | |||
| export const getUrl = (apiId: MyPageApiID) => { | |||
| return [process.env.MYPAGE_BASE_URL ?? "", "api-from-kintone", apiId].join( | |||
| "/" | |||
| ); | |||
| }; | |||
| export const send = async (apiId: MyPageApiID, data: object) => { | |||
| const url = getUrl(apiId); | |||
| const sendData = { | |||
| ...data, | |||
| token: process.env.MYPAGE_TOKEN ?? "", | |||
| }; | |||
| console.info("MyPageAPICall", url, data); | |||
| const res: MyPageApiResponse = await kintone.proxy( | |||
| url, | |||
| "POST", | |||
| { "Content-Type": "application/json" }, | |||
| sendData | |||
| ); | |||
| if (res?.result === "SUCCESS") { | |||
| return true; | |||
| } else { | |||
| console.error(res); | |||
| return false; | |||
| } | |||
| }; | |||
| @@ -0,0 +1,24 @@ | |||
| import { MyPageApiID } from "."; | |||
| import { send } from "./lib"; | |||
| export const EmailID = { | |||
| 定期選考_一斉通知: "定期選考_一斉通知", | |||
| } as const; | |||
| export type EmailID = (typeof EmailID)[keyof typeof EmailID]; | |||
| export const sendEmail = ( | |||
| id: EmailID, | |||
| key: { | |||
| season_ticket_contract_entry_record_no?: string; | |||
| application_no?: string; | |||
| }, | |||
| data: object | |||
| ) => { | |||
| const sendData = { | |||
| email_id: id, | |||
| ...data, | |||
| ...key, | |||
| token: process.env.MYPAGE_TOKEN ?? "", | |||
| }; | |||
| return send(MyPageApiID.メール送信依頼, sendData); | |||
| }; | |||
| @@ -0,0 +1,9 @@ | |||
| import { MyPageApiID } from "."; | |||
| import { send } from "./lib"; | |||
| export const noticeToCandidates = (recordNo: number) => { | |||
| const sendData = { | |||
| record_no: recordNo, | |||
| }; | |||
| return send(MyPageApiID.定期選考一斉通知, sendData); | |||
| }; | |||
| @@ -0,0 +1,10 @@ | |||
| import { KintoneRecordField } from "@kintone/rest-api-client"; | |||
| export type AppRecord = { | |||
| $id: KintoneRecordField.ID; | |||
| $revision: KintoneRecordField.Revision; | |||
| 作成者: KintoneRecordField.Creator; | |||
| レコード番号: KintoneRecordField.RecordNumber; | |||
| 更新日時: KintoneRecordField.UpdatedTime; | |||
| 作成日時: KintoneRecordField.CreatedTime; | |||
| }; | |||
| @@ -0,0 +1,66 @@ | |||
| import { KintoneRecordField } from "@kintone/rest-api-client"; | |||
| import { AppRecord } from "."; | |||
| const F = { | |||
| 選考ステータス: "選考ステータス", | |||
| } as const; | |||
| export const 選考ステータスDropdown = { | |||
| 起票: "起票", | |||
| 通知者選択中: "通知者選択中", | |||
| 契約希望者受付中: "契約希望者受付中", | |||
| 候補者仮決定: "候補者仮決定", | |||
| 選考不調: "選考不調", | |||
| 終了: "終了", | |||
| } as const; | |||
| export type 選考ステータスDropdown = | |||
| (typeof 選考ステータスDropdown)[keyof typeof 選考ステータスDropdown]; | |||
| export const 定期予約選考フィールド名 = F; | |||
| export type 定期予約選考 = AppRecord & { | |||
| 利用開始日: KintoneRecordField.Date; | |||
| 検索用_契約希望者_レコード番号: KintoneRecordField.SingleLineText; | |||
| 自動選考メッセージ: KintoneRecordField.MultiLineText; | |||
| [F.選考ステータス]: KintoneRecordField.Dropdown; | |||
| 選考締切日: KintoneRecordField.Date; | |||
| 検索用_申込一覧_レコード番号: KintoneRecordField.SingleLineText; | |||
| 駐車場名: KintoneRecordField.SingleLineText; | |||
| 検索用_選考結果_レコード番号: KintoneRecordField.SingleLineText; | |||
| 申込者一覧: KintoneRecordField.Subtable<{ | |||
| 申込者一覧_申込レコード番号: KintoneRecordField.Number; | |||
| 申込者一覧_メールアドレス: KintoneRecordField.SingleLineText; | |||
| 申込者一覧_台数: KintoneRecordField.SingleLineText; | |||
| 申込者一覧_氏名: KintoneRecordField.SingleLineText; | |||
| 申込者一覧_受付日時: KintoneRecordField.DateTime; | |||
| 申込者一覧_プラン: KintoneRecordField.SingleLineText; | |||
| 申込者一覧_申込番号: KintoneRecordField.SingleLineText; | |||
| 申込者一覧_利用開始希望日: KintoneRecordField.Date; | |||
| 申込者一覧_状態: KintoneRecordField.SingleLineText; | |||
| 申込者一覧_通知対象: KintoneRecordField.CheckBox; | |||
| }>; | |||
| 対象車室一覧: KintoneRecordField.Subtable<{ | |||
| 対象車室一覧_車室タイプ: KintoneRecordField.SingleLineText; | |||
| 対象車室一覧_車室番号: KintoneRecordField.SingleLineText; | |||
| 対象車室一覧_車室レコード番号: KintoneRecordField.Number; | |||
| }>; | |||
| 選考結果一覧: KintoneRecordField.Subtable<{ | |||
| 選考結果一覧_氏名: KintoneRecordField.SingleLineText; | |||
| 選考結果一覧_申込レコード番号: KintoneRecordField.Number; | |||
| 選考結果一覧_申込番号: KintoneRecordField.SingleLineText; | |||
| 選考結果一覧_車室レコード番号: KintoneRecordField.Number; | |||
| 選考結果一覧_プラン: KintoneRecordField.SingleLineText; | |||
| 選考結果一覧_車室タイプ: KintoneRecordField.SingleLineText; | |||
| 選考結果一覧_状態: KintoneRecordField.SingleLineText; | |||
| 選考結果一覧_車室番号: KintoneRecordField.SingleLineText; | |||
| }>; | |||
| 契約希望者一覧: KintoneRecordField.Subtable<{ | |||
| 契約希望者一覧_利用開始希望日: KintoneRecordField.Date; | |||
| 契約希望者一覧_申込番号: KintoneRecordField.SingleLineText; | |||
| 契約希望者一覧_申込レコード番号: KintoneRecordField.Number; | |||
| 契約希望者一覧_台数: KintoneRecordField.SingleLineText; | |||
| 契約希望者一覧_プラン: KintoneRecordField.SingleLineText; | |||
| 契約希望者一覧_状態: KintoneRecordField.SingleLineText; | |||
| 契約希望者一覧_氏名: KintoneRecordField.SingleLineText; | |||
| 契約希望者一覧_受付日時: KintoneRecordField.DateTime; | |||
| }>; | |||
| }; | |||
| @@ -0,0 +1,59 @@ | |||
| import { KintoneRecordField } from "@kintone/rest-api-client"; | |||
| import { AppRecord } from "."; | |||
| const F = { | |||
| 状態: "status", | |||
| } as const; | |||
| export const 状態Dropdown = { | |||
| 新規: "新規", | |||
| 承認_自動承認: "承認(自動承認)", | |||
| 承認_手動: "承認(手動)", | |||
| 契約中: "契約中", | |||
| 予約: "予約", | |||
| 選考当選: "選考当選", | |||
| 入金待ち: "入金待ち", | |||
| 空き待ち: "空き待ち", | |||
| キャンセル: "キャンセル", | |||
| } as const; | |||
| export type 状態Dropdown = (typeof 状態Dropdown)[keyof typeof 状態Dropdown]; | |||
| export const 定期申込予約フィールド名 = F; | |||
| export type 定期申込予約 = AppRecord & { | |||
| 備考: KintoneRecordField.MultiLineText; | |||
| 受付順: KintoneRecordField.Number; | |||
| 申込番号: KintoneRecordField.SingleLineText; | |||
| 利用方法: KintoneRecordField.MultiLineText; | |||
| 定期駐車場プラン: KintoneRecordField.SingleLineText; | |||
| 台数: KintoneRecordField.SingleLineText; | |||
| 防犯登録番号: KintoneRecordField.SingleLineText; | |||
| 請求対象分_月: KintoneRecordField.Number; | |||
| 初回振り込み合計額: KintoneRecordField.Number; | |||
| メールアドレス: KintoneRecordField.SingleLineText; | |||
| 受付日時: KintoneRecordField.DateTime; | |||
| 学生手帳: KintoneRecordField.SingleLineText; | |||
| 氏名: KintoneRecordField.SingleLineText; | |||
| 定期駐車料金: KintoneRecordField.Number; | |||
| 学校名: KintoneRecordField.SingleLineText; | |||
| 住所: KintoneRecordField.SingleLineText; | |||
| 電話番号: KintoneRecordField.SingleLineText; | |||
| 受付日: KintoneRecordField.Date; | |||
| ParkingNavi駐車場コード: KintoneRecordField.SingleLineText; | |||
| フリガナ: KintoneRecordField.SingleLineText; | |||
| 利用開始希望日: KintoneRecordField.Date; | |||
| メモ: KintoneRecordField.MultiLineText; | |||
| 車室番号: KintoneRecordField.SingleLineText; | |||
| ParkingNavi駐車場: KintoneRecordField.SingleLineText; | |||
| 駐車場: KintoneRecordField.SingleLineText; | |||
| 振込期日: KintoneRecordField.Date; | |||
| 日割り分_金額: KintoneRecordField.Number; | |||
| 自動承認メモ: KintoneRecordField.MultiLineText; | |||
| 車両番号: KintoneRecordField.SingleLineText; | |||
| 請求対象分_金額: KintoneRecordField.Number; | |||
| ParkingNaviプラン: KintoneRecordField.SingleLineText; | |||
| 日割り分_月: KintoneRecordField.Number; | |||
| 支払方法: KintoneRecordField.Dropdown; | |||
| auto_confirm_status: KintoneRecordField.SingleLineText; | |||
| [F.状態]: KintoneRecordField.Dropdown; | |||
| }; | |||
| @@ -0,0 +1,29 @@ | |||
| { | |||
| "compilerOptions": { | |||
| "sourceMap": true, | |||
| // TSはECMAScript 5に変換 | |||
| "target": "ES2015", | |||
| // TSのモジュールはES Modulesとして出力 | |||
| "module": "ES2015", | |||
| // 厳密モードとして設定 | |||
| "strict": true, | |||
| "allowJs": true, | |||
| "moduleResolution": "node", | |||
| "baseUrl": ".", | |||
| "paths": { | |||
| "@/*": [ | |||
| "src/*" | |||
| ] | |||
| }, | |||
| }, | |||
| "files": [ | |||
| "./node_modules/@kintone/dts-gen/kintone.d.ts", | |||
| ], | |||
| "include": [ | |||
| "src/**/*" | |||
| ], | |||
| "exclude": [ | |||
| "dist", | |||
| "node_modules" | |||
| ] | |||
| } | |||
| @@ -0,0 +1,19 @@ | |||
| const { execSync } = require('child_process'); | |||
| const glob = require('glob'); | |||
| // const command = `npx kintone-customize-uploader --base-url ${process.env.KINTONE_BASE_URL} --username ${process.env.KINTONE_USER} --password ${process.env.KINTONE_PASSWORD} `; | |||
| // npx kintone-customize-uploader --base-url https://5c8zkdujl0pn.cybozu.co --username sosuke.iwabuchi@satellite-tech.co.jp --password .1Satellite | |||
| // KINTONE_BASE_URL=https://5c8zkdujl0pn.cybozu.com | |||
| // KINTONE_USERNAME=sosuke.iwabuchi@satellite-tech.co.jp | |||
| // KINTONE_PASSWORD=.1Satellite | |||
| const command = `npx kintone-customize-uploader `; | |||
| const entries = | |||
| process.argv.slice(2).length > 0 | |||
| ? process.argv.slice(2) | |||
| : glob.sync('src/apps/**/customize-manifest.json'); | |||
| entries.forEach(file => { | |||
| console.log('\nuploading... ', file); | |||
| const result = execSync(command + file); | |||
| console.log('\n' + result); | |||
| }); | |||
| @@ -0,0 +1,108 @@ | |||
| const webpack = require('webpack'); | |||
| const path = require('path'); | |||
| const glob = require('glob'); | |||
| const { exec } = require('child_process'); | |||
| const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); | |||
| const TsconfigPathsPlugin = require('tsconfig-paths-webpack-plugin'); | |||
| const Dotenv = require('dotenv-webpack'); | |||
| const basePath = path.resolve('src', 'apps'); | |||
| // basePath配下の各ディレクトリを複数のentryとする | |||
| const entries = glob.sync('**/index.+(js|ts|tsx)', { cwd: basePath }).reduce( | |||
| (prev, file) => ({ | |||
| ...prev, | |||
| [path.dirname(file)]: path.resolve(basePath, file), | |||
| }), | |||
| {} | |||
| ); | |||
| module.exports = { | |||
| entry: entries, | |||
| cache: false, | |||
| resolve: { | |||
| // 拡張子を配列で指定 | |||
| extensions: [ | |||
| '.ts', '.js', | |||
| ], | |||
| plugins: [ | |||
| new TsconfigPathsPlugin({ | |||
| configFile: './tsconfig.json', | |||
| }), | |||
| ] | |||
| }, | |||
| module: { | |||
| rules: [ | |||
| { | |||
| test: /\.m?js$/, | |||
| exclude: /node_modules/, | |||
| use: { | |||
| loader: 'babel-loader', | |||
| options: { | |||
| presets: [ | |||
| [ | |||
| '@babel/preset-env', | |||
| { | |||
| useBuiltIns: 'usage', | |||
| corejs: 3, | |||
| }, | |||
| ], | |||
| ], | |||
| }, | |||
| }, | |||
| }, | |||
| { | |||
| test: /\.tsx?$/, | |||
| use: 'ts-loader', | |||
| } | |||
| ], | |||
| }, | |||
| output: { | |||
| path: path.resolve(__dirname, 'dist'), | |||
| filename: '[name].js', | |||
| }, | |||
| plugins: [ | |||
| // new Dotenv({ systemvars: true }), | |||
| new ForkTsCheckerWebpackPlugin(), | |||
| { | |||
| // watchモードのとき再ビルドされたものをアップロードする | |||
| apply: (compiler) => { | |||
| compiler.hooks.afterEmit.tapPromise( | |||
| 'upload javascript files', | |||
| (compilation) => { | |||
| if (!compiler.options.watch) return Promise.resolve(); | |||
| const emittedFiles = Object.keys(compilation.assets) | |||
| .filter((file) => { | |||
| const source = compilation.assets[file]; | |||
| return source.emitted && source.existsAt; | |||
| }) | |||
| .map((file) => file.replace('.js', '')); | |||
| const processes = glob | |||
| .sync(`@(${emittedFiles.join('|')})/customize-manifest.json`, { | |||
| cwd: basePath, | |||
| }) | |||
| .map((file) => { | |||
| console.log('\nuploading... ', file); | |||
| return exec( | |||
| `yarn upload ${path.resolve(basePath, file)}`, | |||
| (err, stdout, stderr) => { | |||
| if (stdout) process.stdout.write(stdout); | |||
| if (stderr) process.stderr.write(stderr); | |||
| } | |||
| ); | |||
| }); | |||
| return Promise.all(processes); | |||
| } | |||
| ); | |||
| }, | |||
| }, | |||
| // new webpack.DefinePlugin({ | |||
| // 'process.env': { | |||
| // "MYPAGE_BASE_URL": JSON.stringify(process.env.MYPAGE_BASE_URL), | |||
| // "MYPAGE_TOKEN": JSON.stringify(process.env.MYPAGE_TOKEN), | |||
| // } | |||
| // }), | |||
| ], | |||
| }; | |||