@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 CHANGED
@@ -1,3 +1,67 @@
1
1
  # k2 - 🍳 kintone kitchen 🍳
2
2
 
3
- kintone sdk for node.js
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.
@@ -38,7 +38,7 @@ export async function action(options) {
38
38
  outdir,
39
39
  sourcemap: false,
40
40
  minify: true,
41
- target: 'es2020',
41
+ legalComments: 'none',
42
42
  }),
43
43
  buildTailwind(fullConfig),
44
44
  ]);
@@ -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 { uploadZip } from '../../lib/utils.js';
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
- `start uploading`);
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.config,
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('0.9.0').description('k2 - 🍳 kintone kitchen 🍳');
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.log('📁 Target files');
28
- console.dir(targetFiles);
29
- targetFiles.forEach((file) => {
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('0.9.0').description('🍳 kintone kitchen 🍳 for kintone plugin');
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.0",
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": "^14",
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
+ }