@konomi-app/k2 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -1
- package/dist/commands/build-esbuild.js +1 -1
- package/dist/commands/plugin-dev/upload.js +3 -6
- package/dist/commands/plugin-esbuild.js +9 -5
- package/dist/index.js +1 -1
- package/dist/lib/kintone-api-client.js +94 -0
- package/dist/lib/utils.js +32 -2
- package/dist/lib/zip.js +23 -4
- package/dist/plugin.js +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,3 +1,67 @@
|
|
|
1
1
|
# k2 - 🍳 kintone kitchen 🍳
|
|
2
2
|
|
|
3
|
-
kintone
|
|
3
|
+
kintone SDK for Node.js
|
|
4
|
+
|
|
5
|
+
## 🥦 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @konomi-app/k2
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 🥚 Requirements
|
|
12
|
+
|
|
13
|
+
### mkcert
|
|
14
|
+
|
|
15
|
+
K2 uses `mkcert` to generate a self-signed certificate for local development. You can install it by following the instructions [here](https://github.com/FiloSottile/mkcert).
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Windows
|
|
19
|
+
choco install mkcert
|
|
20
|
+
|
|
21
|
+
# macOS
|
|
22
|
+
brew install mkcert
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
If you are using Windows, you may need to install `Chocolatey` first. You can find the instructions [here](https://chocolatey.org/install).
|
|
26
|
+
|
|
27
|
+
## 🥕 Usage (kintone Customization)
|
|
28
|
+
|
|
29
|
+
### 1. Initialize
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx k2 init
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
This command will create a `.k2` directory in your project root. This directory contains the configuration files for K2.
|
|
36
|
+
|
|
37
|
+
### 2. Local Development
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npx k2 dev
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
By executing the above command, a local server will be launched on the port number specified in the configuration file. By setting the URL of the local server to the app implementing kintone customization, you can develop in a local environment.
|
|
44
|
+
|
|
45
|
+
### 3. Build
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npx k2 build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## 🥬 Usage (kintone Plugin)
|
|
52
|
+
|
|
53
|
+
### 1. Initialize
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx plugin init
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This command will create a `.plugin` directory in your project root. This directory contains the configuration files for the plugin.
|
|
60
|
+
|
|
61
|
+
### 2. Local Development
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npx plugin dev
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
By executing the above command, a local server will be launched on the port number specified in the configuration file. By setting the URL of the local server to the app implementing kintone plugin, you can develop in a local environment.
|
|
@@ -4,7 +4,7 @@ import { getContentsZipBuffer, getZipFileNameSuffix, outputContentsZip } from '.
|
|
|
4
4
|
import fs from 'fs-extra';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import { PLUGIN_WORKSPACE_DIRECTORY } from '../../lib/constants.js';
|
|
7
|
-
import {
|
|
7
|
+
import { apiUploadZip } from '../../lib/utils.js';
|
|
8
8
|
import chokider from 'chokidar';
|
|
9
9
|
import chalk from 'chalk';
|
|
10
10
|
export const watchContentsAndUploadZip = async (params) => {
|
|
@@ -31,13 +31,10 @@ export const watchContentsAndUploadZip = async (params) => {
|
|
|
31
31
|
const output = await packer(buffer, pluginPrivateKey);
|
|
32
32
|
const zipFileName = `plugin${getZipFileNameSuffix('dev')}.zip`;
|
|
33
33
|
await fs.writeFile(path.join(PLUGIN_WORKSPACE_DIRECTORY, zipFileName), output.plugin);
|
|
34
|
+
const { method } = await apiUploadZip({ env: 'dev', pluginId: output.id });
|
|
34
35
|
console.log(chalk.hex('#e5e7eb')(`${new Date().toLocaleTimeString()} `) +
|
|
35
36
|
chalk.cyan(`[upload] `) +
|
|
36
|
-
`
|
|
37
|
-
await uploadZip('dev');
|
|
38
|
-
console.log(chalk.hex('#e5e7eb')(`${new Date().toLocaleTimeString()} `) +
|
|
39
|
-
chalk.cyan(`[upload] `) +
|
|
40
|
-
`done`);
|
|
37
|
+
`uploaded ${method === 'POST' ? '(new)' : '(update)'}`);
|
|
41
38
|
}
|
|
42
39
|
catch (error) {
|
|
43
40
|
console.log(chalk.hex('#e5e7eb')(`${new Date().toLocaleTimeString()} `) +
|
|
@@ -5,6 +5,7 @@ import { PLUGIN_CONTENTS_DIRECTORY } from '../lib/constants.js';
|
|
|
5
5
|
import { importPluginConfig } from '../lib/import.js';
|
|
6
6
|
import { getTailwindConfig, outputCss } from '../lib/tailwind.js';
|
|
7
7
|
import { buildWithEsbuild } from '../lib/esbuild.js';
|
|
8
|
+
import { lint } from '../lib/lint.js';
|
|
8
9
|
export default function command() {
|
|
9
10
|
program
|
|
10
11
|
.command('esbuild')
|
|
@@ -15,13 +16,13 @@ export async function action() {
|
|
|
15
16
|
console.group('🍳 Build the project for production');
|
|
16
17
|
try {
|
|
17
18
|
const config = await importPluginConfig();
|
|
19
|
+
if (config?.lint?.build) {
|
|
20
|
+
await lint();
|
|
21
|
+
console.log('✨ Lint success.');
|
|
22
|
+
}
|
|
18
23
|
if (!fs.existsSync(PLUGIN_CONTENTS_DIRECTORY)) {
|
|
19
24
|
await fs.mkdir(PLUGIN_CONTENTS_DIRECTORY, { recursive: true });
|
|
20
25
|
}
|
|
21
|
-
const entries = {
|
|
22
|
-
desktop: path.join('src', 'desktop', 'index.ts'),
|
|
23
|
-
config: path.join('src', 'config', 'index.ts'),
|
|
24
|
-
};
|
|
25
26
|
if (config.tailwind?.css && config.tailwind?.config) {
|
|
26
27
|
const tailwindConfig = await getTailwindConfig(config.tailwind);
|
|
27
28
|
const inputFile = path.resolve(config.tailwind.css);
|
|
@@ -30,12 +31,14 @@ export async function action() {
|
|
|
30
31
|
inputPath,
|
|
31
32
|
outputPath: path.join(PLUGIN_CONTENTS_DIRECTORY, 'config.css'),
|
|
32
33
|
config: tailwindConfig.config,
|
|
34
|
+
minify: true,
|
|
33
35
|
});
|
|
34
36
|
console.log('✨ Built config.css');
|
|
35
37
|
await outputCss({
|
|
36
38
|
inputPath,
|
|
37
39
|
outputPath: path.join(PLUGIN_CONTENTS_DIRECTORY, 'desktop.css'),
|
|
38
|
-
config: tailwindConfig.
|
|
40
|
+
config: tailwindConfig.desktop,
|
|
41
|
+
minify: true,
|
|
39
42
|
});
|
|
40
43
|
console.log('✨ Built desktop.css');
|
|
41
44
|
}
|
|
@@ -48,6 +51,7 @@ export async function action() {
|
|
|
48
51
|
outdir: PLUGIN_CONTENTS_DIRECTORY,
|
|
49
52
|
minify: true,
|
|
50
53
|
sourcemap: false,
|
|
54
|
+
legalComments: 'none',
|
|
51
55
|
});
|
|
52
56
|
console.log('✨ Built desktop.js and config.js');
|
|
53
57
|
console.log('✨ Build success.');
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,7 @@ import viteDev from './commands/dev-vite.js';
|
|
|
7
7
|
import genkey from './commands/genkey.js';
|
|
8
8
|
import esbuildBuild from './commands/build-esbuild.js';
|
|
9
9
|
import lint from './commands/lint.js';
|
|
10
|
-
program.name('k2').version('
|
|
10
|
+
program.name('k2').version('1.4.0').description('k2 - 🍳 kintone kitchen 🍳');
|
|
11
11
|
build();
|
|
12
12
|
viteBuild();
|
|
13
13
|
esbuildBuild();
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { config } from 'dotenv';
|
|
2
|
+
export class KintoneApiClient {
|
|
3
|
+
#baseUrl;
|
|
4
|
+
#authHeader;
|
|
5
|
+
constructor() {
|
|
6
|
+
config();
|
|
7
|
+
const { KINTONE_BASE_URL, KINTONE_USERNAME, KINTONE_PASSWORD, KINTONE_BASIC_AUTH_USERNAME = '', KINTONE_BASIC_AUTH_PASSWORD = '', } = process.env;
|
|
8
|
+
if (!KINTONE_BASE_URL || !KINTONE_USERNAME || !KINTONE_PASSWORD) {
|
|
9
|
+
throw new Error(`.envの設定が不十分です。以下のパラメータは必須です
|
|
10
|
+
KINTONE_BASE_URL
|
|
11
|
+
KINTONE_USERNAME
|
|
12
|
+
KINTONE_PASSWORD`);
|
|
13
|
+
}
|
|
14
|
+
const authHeader = {
|
|
15
|
+
'X-Cybozu-Authorization': Buffer.from(`${KINTONE_USERNAME}:${KINTONE_PASSWORD}`).toString('base64'),
|
|
16
|
+
...(KINTONE_BASIC_AUTH_USERNAME &&
|
|
17
|
+
KINTONE_BASIC_AUTH_PASSWORD && {
|
|
18
|
+
Authorization: `Basic ${Buffer.from(`${KINTONE_BASIC_AUTH_USERNAME}:${KINTONE_BASIC_AUTH_PASSWORD}`).toString('base64')}`,
|
|
19
|
+
}),
|
|
20
|
+
};
|
|
21
|
+
this.#baseUrl = KINTONE_BASE_URL;
|
|
22
|
+
this.#authHeader = authHeader;
|
|
23
|
+
}
|
|
24
|
+
getEndpointUrl(path) {
|
|
25
|
+
return `${this.#baseUrl}${path}`;
|
|
26
|
+
}
|
|
27
|
+
async upload(params) {
|
|
28
|
+
const { blob, fileName } = params;
|
|
29
|
+
const form = new FormData();
|
|
30
|
+
form.append('file', blob, fileName);
|
|
31
|
+
const uploadResult = await fetch(this.getEndpointUrl('/k/v1/file.json'), {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: this.#authHeader,
|
|
34
|
+
body: form,
|
|
35
|
+
});
|
|
36
|
+
const { fileKey } = await uploadResult.json();
|
|
37
|
+
return fileKey;
|
|
38
|
+
}
|
|
39
|
+
async getPlugins(params = {}) {
|
|
40
|
+
const url = new URL(this.getEndpointUrl('/k/v1/plugins.json'));
|
|
41
|
+
if (params.offset) {
|
|
42
|
+
url.searchParams.set('offset', String(params.offset));
|
|
43
|
+
}
|
|
44
|
+
if (params.limit) {
|
|
45
|
+
url.searchParams.set('limit', String(params.limit));
|
|
46
|
+
}
|
|
47
|
+
const pluginResponse = await fetch(url.toString(), {
|
|
48
|
+
headers: this.#authHeader,
|
|
49
|
+
});
|
|
50
|
+
return pluginResponse.json();
|
|
51
|
+
}
|
|
52
|
+
async getAllPlugins() {
|
|
53
|
+
const plugins = [];
|
|
54
|
+
let offset = 0;
|
|
55
|
+
let limit = 100;
|
|
56
|
+
let hasMore = true;
|
|
57
|
+
while (hasMore) {
|
|
58
|
+
const { plugins: currentPlugins } = await this.getPlugins({ offset, limit });
|
|
59
|
+
plugins.push(...currentPlugins);
|
|
60
|
+
if (currentPlugins.length < limit) {
|
|
61
|
+
hasMore = false;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
offset += limit;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return plugins;
|
|
68
|
+
}
|
|
69
|
+
async addPlugin(params) {
|
|
70
|
+
const { fileKey } = params;
|
|
71
|
+
const pluginResponse = await fetch(this.getEndpointUrl('/k/v1/plugin.json'), {
|
|
72
|
+
method: 'POST',
|
|
73
|
+
headers: {
|
|
74
|
+
'Content-Type': 'application/json',
|
|
75
|
+
...this.#authHeader,
|
|
76
|
+
},
|
|
77
|
+
body: JSON.stringify({
|
|
78
|
+
fileKey,
|
|
79
|
+
}),
|
|
80
|
+
});
|
|
81
|
+
return pluginResponse.json();
|
|
82
|
+
}
|
|
83
|
+
async updatePlugin(params) {
|
|
84
|
+
const pluginResponse = await fetch(this.getEndpointUrl('/k/v1/plugin.json'), {
|
|
85
|
+
method: 'PUT',
|
|
86
|
+
headers: {
|
|
87
|
+
'Content-Type': 'application/json',
|
|
88
|
+
...this.#authHeader,
|
|
89
|
+
},
|
|
90
|
+
body: JSON.stringify(params),
|
|
91
|
+
});
|
|
92
|
+
return pluginResponse.json();
|
|
93
|
+
}
|
|
94
|
+
}
|
package/dist/lib/utils.js
CHANGED
|
@@ -1,10 +1,41 @@
|
|
|
1
1
|
import { PLUGIN_WORKSPACE_DIRECTORY } from './constants.js';
|
|
2
2
|
import { exec } from './exec.js';
|
|
3
|
+
import { KintoneApiClient } from './kintone-api-client.js';
|
|
3
4
|
import { getZipFileNameSuffix } from './zip.js';
|
|
4
5
|
import { config } from 'dotenv';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import path from 'path';
|
|
5
8
|
export const isEnv = (env) => {
|
|
6
9
|
return ['prod', 'dev', 'standalone'].includes(env);
|
|
7
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* プラグインを追加・更新するAPIを利用してプラグインをアップロードします
|
|
13
|
+
*/
|
|
14
|
+
export const apiUploadZip = async (params) => {
|
|
15
|
+
const { env, pluginId } = params;
|
|
16
|
+
const kc = new KintoneApiClient();
|
|
17
|
+
const zipFileName = `plugin${getZipFileNameSuffix(env)}.zip`;
|
|
18
|
+
const zipFile = new Blob([await fs.readFile(path.join(PLUGIN_WORKSPACE_DIRECTORY, zipFileName))]);
|
|
19
|
+
const fileKey = await kc.upload({ blob: zipFile, fileName: zipFileName });
|
|
20
|
+
const plugins = await kc.getAllPlugins();
|
|
21
|
+
const plugin = plugins.find((p) => p.id === pluginId);
|
|
22
|
+
if (plugin) {
|
|
23
|
+
const json = await kc.updatePlugin({ id: pluginId, fileKey });
|
|
24
|
+
if ('errors' in json && json.errors) {
|
|
25
|
+
console.error((json.errors.id?.messages ?? []).map((m) => `Error: ${m}`).join('\n'));
|
|
26
|
+
}
|
|
27
|
+
return { method: 'PUT' };
|
|
28
|
+
}
|
|
29
|
+
const result = await kc.addPlugin({ fileKey });
|
|
30
|
+
if ('code' in result) {
|
|
31
|
+
console.error(`Error: ${result.message}`);
|
|
32
|
+
}
|
|
33
|
+
return { method: 'POST' };
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* @kintone/plugin-uploaderを利用してプラグインをアップロードします
|
|
37
|
+
* APIを使用した方が高速ですが、2024年11月までは設定により無効になる可能性があるため、暫定的に残しています
|
|
38
|
+
*/
|
|
8
39
|
export const uploadZip = async (env) => {
|
|
9
40
|
config();
|
|
10
41
|
const { KINTONE_BASE_URL, KINTONE_USERNAME, KINTONE_PASSWORD, KINTONE_BASIC_AUTH_USERNAME = '', KINTONE_BASIC_AUTH_PASSWORD = '', } = process.env;
|
|
@@ -12,8 +43,7 @@ export const uploadZip = async (env) => {
|
|
|
12
43
|
throw new Error(`.envの設定が不十分です。以下のパラメータは必須です
|
|
13
44
|
KINTONE_BASE_URL
|
|
14
45
|
KINTONE_USERNAME
|
|
15
|
-
KINTONE_PASSWORD
|
|
16
|
-
`);
|
|
46
|
+
KINTONE_PASSWORD`);
|
|
17
47
|
}
|
|
18
48
|
const zipFileName = `plugin${getZipFileNameSuffix(env)}.zip`;
|
|
19
49
|
let command = `kintone-plugin-uploader ${PLUGIN_WORKSPACE_DIRECTORY}/${zipFileName} --base-url ${KINTONE_BASE_URL} --username ${KINTONE_USERNAME} --password ${KINTONE_PASSWORD}`;
|
package/dist/lib/zip.js
CHANGED
|
@@ -5,8 +5,22 @@ import invariant from 'tiny-invariant';
|
|
|
5
5
|
import { PLUGIN_CONTENTS_DIRECTORY, PLUGIN_WORKSPACE_DIRECTORY } from './constants.js';
|
|
6
6
|
export const outputContentsZip = async (manifest) => {
|
|
7
7
|
const archive = archiver('zip', { zlib: { level: 9 } });
|
|
8
|
+
archive.on('warning', (error) => {
|
|
9
|
+
if (error.code === 'ENOENT') {
|
|
10
|
+
console.warn(error);
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
throw error;
|
|
14
|
+
}
|
|
15
|
+
});
|
|
8
16
|
const outputZipPath = path.join(PLUGIN_WORKSPACE_DIRECTORY, 'contents.zip');
|
|
9
17
|
const outputZipStream = fs.createWriteStream(outputZipPath);
|
|
18
|
+
outputZipStream.on('close', () => {
|
|
19
|
+
console.log(`📦 ${archive.pointer()} total bytes`);
|
|
20
|
+
});
|
|
21
|
+
outputZipStream.on('end', function () {
|
|
22
|
+
console.log('📦 Data has been drained');
|
|
23
|
+
});
|
|
10
24
|
const filterLocalContent = (file) => {
|
|
11
25
|
return !/^https?:\/\//.test(file);
|
|
12
26
|
};
|
|
@@ -24,17 +38,22 @@ export const outputContentsZip = async (manifest) => {
|
|
|
24
38
|
...(manifest.config.css || []).filter(filterLocalContent),
|
|
25
39
|
]),
|
|
26
40
|
];
|
|
27
|
-
console.
|
|
28
|
-
|
|
29
|
-
|
|
41
|
+
console.group('📁 Target files');
|
|
42
|
+
targetFiles.forEach((file, i) => {
|
|
43
|
+
const prefix = i === targetFiles.length - 1 ? '└─' : '├─';
|
|
44
|
+
console.log(`${prefix} 📄 ${file}`);
|
|
45
|
+
});
|
|
46
|
+
console.groupEnd();
|
|
47
|
+
for (const file of targetFiles) {
|
|
30
48
|
const filePath = path.join(PLUGIN_CONTENTS_DIRECTORY, file);
|
|
31
49
|
if (!fs.existsSync(filePath)) {
|
|
32
50
|
throw new Error(`${filePath} does not exist`);
|
|
33
51
|
}
|
|
34
52
|
archive.file(filePath, { name: file });
|
|
35
|
-
}
|
|
53
|
+
}
|
|
36
54
|
archive.pipe(outputZipStream);
|
|
37
55
|
await archive.finalize();
|
|
56
|
+
await new Promise((resolve) => outputZipStream.on('close', resolve));
|
|
38
57
|
};
|
|
39
58
|
export const getContentsZipBuffer = async () => {
|
|
40
59
|
const outputZipPath = path.join(PLUGIN_WORKSPACE_DIRECTORY, 'contents.zip');
|
package/dist/plugin.js
CHANGED
|
@@ -10,7 +10,7 @@ import test from './commands/test/index.js';
|
|
|
10
10
|
import upload from './commands/upload/index.js';
|
|
11
11
|
import zip from './commands/plugin-zip.js';
|
|
12
12
|
import lint from './commands/lint.js';
|
|
13
|
-
program.name('plugin').version('
|
|
13
|
+
program.name('plugin').version('1.4.0').description('🍳 kintone kitchen 🍳 for kintone plugin');
|
|
14
14
|
build();
|
|
15
15
|
esbuild();
|
|
16
16
|
dev();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@konomi-app/k2",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "kintone sdk",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"mini-css-extract-plugin": "^2",
|
|
49
49
|
"postcss": "^8",
|
|
50
50
|
"sass": "^1",
|
|
51
|
-
"sass-loader": "^
|
|
51
|
+
"sass-loader": "^16",
|
|
52
52
|
"style-loader": "^4",
|
|
53
53
|
"tailwindcss": "^3",
|
|
54
54
|
"terser-webpack-plugin": "^5",
|
|
@@ -67,4 +67,4 @@
|
|
|
67
67
|
"@types/html-minifier": "^4",
|
|
68
68
|
"typescript": "*"
|
|
69
69
|
}
|
|
70
|
-
}
|
|
70
|
+
}
|