first commit
This commit is contained in:
@@ -0,0 +1 @@
|
||||
config.toml
|
||||
@@ -0,0 +1,49 @@
|
||||
# ABC User Feedback CLI
|
||||
|
||||
The ABC User Feedback CLI helps you easily run web frontends and servers.
|
||||
|
||||
[ABC User Feedback](https://github.com/line/abc-user-feedback) is a standalone web application that manages Voice of Customer (VoC) data, allowing you to gather and organize feedback from your customers.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [Node.js v22 or above](https://nodejs.org/en/download/)
|
||||
- [Docker](https://docs.docker.com/desktop/)
|
||||
|
||||
## Running without dependency
|
||||
|
||||
You can run this cli with [npx](https://docs.npmjs.com/cli/v8/commands/npx), so there's no need to install additional dependencies or clone the repository. Regardless of your operating system or environment, you can run servers with just the prerequisites mentioned above.
|
||||
|
||||
There are `npx` commands for setting up the infrastructure, starting servers, and stopping servers.
|
||||
|
||||
## Initialization
|
||||
|
||||
The following command sets up the infrastructure(MySQL, SMTP, OpenSearch) based on your architecture(ARM/AMD).
|
||||
It also creates a `config.toml` file where you can configure environment variables to start the servers.
|
||||
|
||||
```sh
|
||||
npx auf-cli init
|
||||
```
|
||||
|
||||
## Start Servers
|
||||
|
||||
Based on `config.toml` file created during the initialization phase, this command generates a Docker Compose file. Using this Docker Compose file, the following command starts the API and web servers.
|
||||
|
||||
```sh
|
||||
npx auf-cli start
|
||||
```
|
||||
|
||||
## Stop Servers
|
||||
|
||||
The following command stops the API and web servers.
|
||||
|
||||
```sh
|
||||
npx auf-cli stop
|
||||
```
|
||||
|
||||
## Clean Mounted Volumes
|
||||
|
||||
The following command clean the mounted docker volumes created during initialization.
|
||||
|
||||
```sh
|
||||
npx auf-cli clean
|
||||
```
|
||||
@@ -0,0 +1,204 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import YAML from 'yaml';
|
||||
|
||||
import type { AppConfig } from './config';
|
||||
|
||||
export function generateComposeContent(cfg: AppConfig) {
|
||||
const doc = {
|
||||
name: 'abc-user-feedback',
|
||||
services: {
|
||||
web: {
|
||||
image: 'line/abc-user-feedback-web:latest',
|
||||
ports: [`${cfg.web.port}:3000`],
|
||||
depends_on: { api: { condition: 'service_healthy' } },
|
||||
restart: 'unless-stopped',
|
||||
environment: [
|
||||
`NEXT_PUBLIC_API_BASE_URL=http://localhost:${cfg.api.port}`,
|
||||
],
|
||||
},
|
||||
api: {
|
||||
image: 'line/abc-user-feedback-api:latest',
|
||||
environment: [
|
||||
`JWT_SECRET=${cfg.api.jwt_secret}`,
|
||||
'MYSQL_PRIMARY_URL=mysql://userfeedback:userfeedback@mysql:3306/userfeedback',
|
||||
`SMTP_HOST=${cfg.api.smtp.host}`,
|
||||
`SMTP_PORT=${cfg.api.smtp.port}`,
|
||||
`SMTP_SENDER=${cfg.api.smtp.sender}`,
|
||||
],
|
||||
ports: [`${cfg.api.port}:4000`],
|
||||
depends_on: { mysql: { condition: 'service_healthy' } },
|
||||
restart: 'unless-stopped',
|
||||
healthcheck: {
|
||||
test: [
|
||||
'CMD-SHELL',
|
||||
"node -e \"require('http').get('http://localhost:4000/api/health', res => process.exit(res.statusCode === 200 ? 0 : 1))\"",
|
||||
],
|
||||
interval: '10s',
|
||||
timeout: '5s',
|
||||
retries: '5',
|
||||
},
|
||||
},
|
||||
smtp4dev: {
|
||||
image: 'rnwood/smtp4dev:v3',
|
||||
ports: ['5080:80', '25:25', '143:143'],
|
||||
volumes: ['smtp4dev:/smtp4dev'],
|
||||
restart: 'unless-stopped',
|
||||
},
|
||||
mysql: {
|
||||
image: 'mysql:8.0',
|
||||
command: [
|
||||
'--default-authentication-plugin=mysql_native_password',
|
||||
'--collation-server=utf8mb4_bin',
|
||||
],
|
||||
environment: {
|
||||
MYSQL_ROOT_PASSWORD: 'userfeedback',
|
||||
MYSQL_DATABASE: 'userfeedback',
|
||||
MYSQL_USER: 'userfeedback',
|
||||
MYSQL_PASSWORD: 'userfeedback',
|
||||
TZ: 'UTC',
|
||||
},
|
||||
ports: [`${cfg.mysql?.port}:3306`],
|
||||
volumes: ['mysql:/var/lib/mysql'],
|
||||
restart: 'unless-stopped',
|
||||
healthcheck: {
|
||||
test: [
|
||||
'CMD',
|
||||
'mysqladmin',
|
||||
'ping',
|
||||
'-h',
|
||||
'localhost',
|
||||
'-uuserfeedback',
|
||||
'-puserfeedback',
|
||||
],
|
||||
interval: '10s',
|
||||
timeout: '5s',
|
||||
retries: '5',
|
||||
},
|
||||
},
|
||||
},
|
||||
volumes: { mysql: {}, smtp4dev: {} } as Record<string, object>,
|
||||
};
|
||||
|
||||
const apiEnvVariables = {
|
||||
MASTER_API_KEY: cfg.api.master_api_key,
|
||||
ACCESS_TOKEN_EXPIRED_TIME: cfg.api.access_token_expired_time,
|
||||
REFRESH_TOKEN_EXPIRED_TIME: cfg.api.refresh_token_expired_time,
|
||||
SMTP_USERNAME: cfg.api.smtp.username,
|
||||
SMTP_PASSWORD: cfg.api.smtp.password,
|
||||
SMTP_TLS: cfg.api.smtp.tls,
|
||||
SMTP_CIPHER_SPEC: cfg.api.smtp.cipher_spec,
|
||||
SMTP_OPPORTUNISTIC_TLS: cfg.api.smtp.opportunistic_tls,
|
||||
AUTO_FEEDBACK_DELETION_ENABLED: cfg.api.auto_feedback_deletion?.enabled,
|
||||
AUTO_FEEDBACK_DELETION_PERIOD_DAYS:
|
||||
cfg.api.auto_feedback_deletion?.period_days,
|
||||
OPENSEARCH_USE: cfg.api.opensearch?.enabled,
|
||||
};
|
||||
|
||||
for (const [key, value] of Object.entries(apiEnvVariables)) {
|
||||
if (value !== undefined) {
|
||||
doc.services.api.environment.push(`${key}=${value}`);
|
||||
}
|
||||
if (key === 'OPENSEARCH_USE' && value === true) {
|
||||
doc.services.api.environment.push(
|
||||
`OPENSEARCH_NODE=http://opensearch-node:9200`,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (cfg.mysql) {
|
||||
doc.services.mysql = {
|
||||
image: 'mysql:8.0',
|
||||
command: [
|
||||
'--default-authentication-plugin=mysql_native_password',
|
||||
'--collation-server=utf8mb4_bin',
|
||||
],
|
||||
environment: {
|
||||
MYSQL_ROOT_PASSWORD: 'userfeedback',
|
||||
MYSQL_DATABASE: 'userfeedback',
|
||||
MYSQL_USER: 'userfeedback',
|
||||
MYSQL_PASSWORD: 'userfeedback',
|
||||
TZ: 'UTC',
|
||||
},
|
||||
ports: [`${cfg.mysql.port}:3306`],
|
||||
volumes: ['mysql:/var/lib/mysql'],
|
||||
restart: 'unless-stopped',
|
||||
healthcheck: {
|
||||
test: [
|
||||
'CMD',
|
||||
'mysqladmin',
|
||||
'ping',
|
||||
'-h',
|
||||
'localhost',
|
||||
'-uuserfeedback',
|
||||
'-puserfeedback',
|
||||
],
|
||||
interval: '10s',
|
||||
timeout: '5s',
|
||||
retries: '5',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (cfg.api.opensearch) {
|
||||
doc.services['opensearch-node'] = {
|
||||
image: 'opensearchproject/opensearch:2.16.0',
|
||||
restart: 'unless-stopped',
|
||||
environment: [
|
||||
'cluster.name=opensearch-cluster',
|
||||
'node.name=opensearch-node',
|
||||
'discovery.type=single-node',
|
||||
'bootstrap.memory_lock=true',
|
||||
'OPENSEARCH_JAVA_OPTS=-Xms512m -Xmx512m',
|
||||
'plugins.security.disabled=true',
|
||||
'OPENSEARCH_INITIAL_ADMIN_PASSWORD=UserFeedback123!@#',
|
||||
],
|
||||
ulimits: {
|
||||
memlock: { soft: -1, hard: -1 },
|
||||
nofile: { soft: 65536, hard: 65536 },
|
||||
},
|
||||
volumes: ['opensearch:/usr/share/opensearch/data'],
|
||||
ports: ['9200:9200', '9600:9600'],
|
||||
healthcheck: {
|
||||
test: ['CMD', 'curl', '-f', 'http://localhost:9200/_cluster/health'],
|
||||
interval: '10s',
|
||||
timeout: '5s',
|
||||
retries: '5',
|
||||
},
|
||||
};
|
||||
doc.services.api.depends_on['opensearch-node'] = {
|
||||
condition: 'service_healthy',
|
||||
};
|
||||
doc.volumes.opensearch = {};
|
||||
|
||||
doc.services['opensearch-dashboards'] = {
|
||||
image: 'opensearchproject/opensearch-dashboards:2.16.0',
|
||||
restart: 'unless-stopped',
|
||||
ports: ['5601:5601'],
|
||||
environment: [
|
||||
'OPENSEARCH_HOSTS=["http://opensearch-node:9200"]',
|
||||
'DISABLE_SECURITY_DASHBOARDS_PLUGIN=true',
|
||||
],
|
||||
depends_on: {
|
||||
'opensearch-node': {
|
||||
condition: 'service_healthy',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const yml = YAML.stringify(doc);
|
||||
return yml;
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import path from 'path';
|
||||
import * as TOML from 'toml';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { exists, readFile } from './fsutil';
|
||||
|
||||
const ConfigSchema = z.object({
|
||||
web: z.object({
|
||||
port: z.number().default(3000),
|
||||
api_base_url: z.string().default('http://localhost:4000'),
|
||||
}),
|
||||
api: z.object({
|
||||
port: z.number().default(4000),
|
||||
jwt_secret: z.string().min(32).default('jwtsecretjwtsecretjwtsecret'),
|
||||
master_api_key: z.string().optional(),
|
||||
access_token_expired_time: z.string().optional(),
|
||||
refresh_token_expired_time: z.string().optional(),
|
||||
auto_feedback_deletion: z
|
||||
.object({
|
||||
enabled: z.boolean().default(false),
|
||||
period_days: z.number().optional(),
|
||||
})
|
||||
.optional(),
|
||||
smtp: z
|
||||
.object({
|
||||
host: z.string(),
|
||||
port: z.number(),
|
||||
sender: z.string(),
|
||||
username: z.string().optional(),
|
||||
password: z.string().optional(),
|
||||
tls: z.string().optional(),
|
||||
cipher_spec: z.string().optional(),
|
||||
opportunistic_tls: z.string().optional(),
|
||||
})
|
||||
.optional()
|
||||
.default({ host: 'smtp4dev', port: 25, sender: 'user@feedback.com' }),
|
||||
opensearch: z.object({ enabled: z.boolean().default(false) }).optional(),
|
||||
}),
|
||||
mysql: z.object({ port: z.number().default(13306) }).optional(),
|
||||
});
|
||||
|
||||
export type AppConfig = z.infer<typeof ConfigSchema>;
|
||||
|
||||
export function loadConfig(cwd = process.cwd()): AppConfig {
|
||||
const mainPath = path.join(cwd, 'config.toml');
|
||||
if (!exists(mainPath))
|
||||
throw new Error(
|
||||
"config.toml 이 없습니다. 먼저 'mystack init'을 실행하세요.",
|
||||
);
|
||||
const main = TOML.parse(readFile(mainPath)) as unknown;
|
||||
const parsed = ConfigSchema.safeParse(main);
|
||||
if (!parsed.success)
|
||||
throw new Error(
|
||||
'config.toml 검증 실패:\n' +
|
||||
JSON.stringify(parsed.error.format(), null, 2),
|
||||
);
|
||||
return parsed.data;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
|
||||
export function ensureDir(dir: string) {
|
||||
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
||||
}
|
||||
export function writeFile(p: string, content: string) {
|
||||
ensureDir(path.dirname(p));
|
||||
fs.writeFileSync(p, content, 'utf8');
|
||||
}
|
||||
export function exists(p: string) {
|
||||
return fs.existsSync(p);
|
||||
}
|
||||
export function readFile(p: string) {
|
||||
return fs.readFileSync(p, 'utf8');
|
||||
}
|
||||
Executable
+145
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { Command } from 'commander';
|
||||
|
||||
import packageJson from '../package.json';
|
||||
import { generateComposeContent } from './compose';
|
||||
import { loadConfig } from './config';
|
||||
import { exists, writeFile } from './fsutil';
|
||||
import { run, runWithStdin } from './shell';
|
||||
|
||||
const program = new Command();
|
||||
program
|
||||
.name('auf-cli')
|
||||
.description('Tiny stack CLI (config.toml only)')
|
||||
.version(packageJson.version);
|
||||
|
||||
program
|
||||
.command('init')
|
||||
.description('Generate config.toml template — use without .env')
|
||||
.option('--force', 'Overwrite existing file')
|
||||
.action((opts: { force?: boolean }) => {
|
||||
if (exists('config.toml') && !opts.force)
|
||||
throw new Error('config.toml already exists. Use --force to overwrite.');
|
||||
writeFile('config.toml', defaultConfigToml());
|
||||
console.log('✅ Created: config.toml');
|
||||
});
|
||||
|
||||
program
|
||||
.command('start')
|
||||
.description('Start services with docker compose up -d based on config.toml')
|
||||
.action(async () => {
|
||||
const cfg = loadConfig();
|
||||
|
||||
const composeContent = generateComposeContent(cfg);
|
||||
await runWithStdin(
|
||||
'docker',
|
||||
['compose', '-f', '-', 'up', '-d', '--remove-orphans'],
|
||||
composeContent,
|
||||
);
|
||||
|
||||
console.log('🚀 Services started successfully!');
|
||||
console.log('🔗 Available URLs:');
|
||||
console.log(` 📱 Web: http://localhost:${cfg.web.port}`);
|
||||
console.log(` 🔧 API: http://localhost:${cfg.api.port}`);
|
||||
if (cfg.mysql) {
|
||||
console.log(
|
||||
` 🗄️ MySQL: mysql://userfeedback:userfeedback@localhost:${cfg.mysql.port}`,
|
||||
);
|
||||
}
|
||||
if (cfg.api.opensearch?.enabled) {
|
||||
console.log(` 🔍 OpenSearch: http://localhost:9200`);
|
||||
}
|
||||
if (cfg.api.smtp.host === 'smtp4dev') {
|
||||
console.log(` 📧 SMTP Mail Web: http://localhost:5080`);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('stop')
|
||||
.description('Stop services with docker compose down')
|
||||
.action(async () => {
|
||||
const cfg = loadConfig();
|
||||
|
||||
const composeContent = generateComposeContent(cfg);
|
||||
await runWithStdin(
|
||||
'docker',
|
||||
['compose', '-f', '-', 'down'],
|
||||
composeContent,
|
||||
);
|
||||
console.log('🛑 Services stopped successfully');
|
||||
});
|
||||
|
||||
program
|
||||
.command('clean')
|
||||
.description('Clean up containers/networks/volumes')
|
||||
.option('--images', 'Also prune images')
|
||||
.action(async (opts: { images?: boolean }) => {
|
||||
const cfg = loadConfig();
|
||||
|
||||
const composeContent = generateComposeContent(cfg);
|
||||
await runWithStdin(
|
||||
'docker',
|
||||
['compose', '-f', '-', 'down', '--volumes', '--remove-orphans'],
|
||||
composeContent,
|
||||
);
|
||||
|
||||
if (opts.images) await run('docker', ['image', 'prune', '-f']);
|
||||
console.log('🧹 Cleanup completed successfully');
|
||||
});
|
||||
|
||||
program.parseAsync().catch((e: Error) => {
|
||||
console.error('❌ Error:', e.message);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
function defaultConfigToml() {
|
||||
return `
|
||||
[web]
|
||||
port = 3000
|
||||
# api_base_url = "http://localhost:4000"
|
||||
|
||||
[api]
|
||||
port = 4000
|
||||
jwt_secret = "jwtsecretjwtsecretjwtsecretjwtsecretjwtsecretjwtsecret"
|
||||
|
||||
# master_api_key = "MASTER_KEY"
|
||||
# access_token_expired_time = "10m"
|
||||
# refresh_token_expired_time = "1h"
|
||||
|
||||
# [api.auto_feedback_deletion]
|
||||
# enabled = true
|
||||
# period_days = 365
|
||||
|
||||
# [api.smtp]
|
||||
# host = "smtp4dev" # SMTP_HOST
|
||||
# port = 25 # SMTP_PORT
|
||||
# sender = "user@feedback.com"
|
||||
# username=
|
||||
# password=
|
||||
# tls=
|
||||
# ciper_spec=
|
||||
# opportunitic_tls=
|
||||
|
||||
|
||||
# [api.opensearch]
|
||||
# enabled = true
|
||||
|
||||
[mysql]
|
||||
port = 13306
|
||||
`;
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright 2025 LY Corporation
|
||||
*
|
||||
* LY Corporation licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
import { spawn } from 'child_process';
|
||||
|
||||
export function run(cmd: string, args: string[] = [], cwd?: string) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const p = spawn(cmd, args, {
|
||||
stdio: 'inherit',
|
||||
cwd,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
p.on('close', (code) =>
|
||||
code === 0 ? resolve() : (
|
||||
reject(new Error(`${cmd} ${args.join(' ')} exited with ${code}`))
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
export function runWithStdin(
|
||||
cmd: string,
|
||||
args: string[] = [],
|
||||
input: string,
|
||||
cwd?: string,
|
||||
) {
|
||||
return new Promise<void>((resolve, reject) => {
|
||||
const p = spawn(cmd, args, {
|
||||
stdio: ['pipe', 'inherit', 'inherit'],
|
||||
cwd,
|
||||
shell: process.platform === 'win32',
|
||||
});
|
||||
|
||||
p.stdin.write(input);
|
||||
p.stdin.end();
|
||||
|
||||
p.on('close', (code) =>
|
||||
code === 0 ? resolve() : (
|
||||
reject(new Error(`${cmd} ${args.join(' ')} exited with ${code}`))
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
import baseConfig from '@ufb/eslint-config/base';
|
||||
|
||||
export default [
|
||||
{
|
||||
ignores: ['dist/**', '**/*.js'],
|
||||
},
|
||||
...baseConfig,
|
||||
];
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"name": "auf-cli",
|
||||
"version": "1.0.11",
|
||||
"description": "Command line interface for ABC User Feedback",
|
||||
"repository": "https://github.com/line/abc-user-feedback/tree/main/apps/cli",
|
||||
"bin": {
|
||||
"auf-cli": "./dist/index.js"
|
||||
},
|
||||
"author": "ABC User Feedback",
|
||||
"keywords": [
|
||||
"auf",
|
||||
"cli",
|
||||
"command-line",
|
||||
"tool",
|
||||
"abc-user-feedback",
|
||||
"VOC"
|
||||
],
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"clean": "git clean -xdf dist .turbo node_modules .cache",
|
||||
"format": "prettier --check . --ignore-path ../../.gitignore",
|
||||
"format:fix": "prettier --write --list-different .",
|
||||
"lint": "eslint",
|
||||
"start": "node dist/index.js",
|
||||
"start:dev": "ts-node bin/index.ts",
|
||||
"dev": "ts-node bin/index.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/prompts": "^2.4.9",
|
||||
"child_process": "^1.0.2",
|
||||
"commander": "^14.0.3",
|
||||
"js-toml": "^1.0.3",
|
||||
"toml": "^3.0.0",
|
||||
"yaml": "^2.8.3",
|
||||
"zod": "^4.3.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "24.12.0",
|
||||
"@ufb/eslint-config": "workspace:*",
|
||||
"@ufb/prettier-config": "workspace:*",
|
||||
"@ufb/tsconfig": "workspace:*",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "catalog:"
|
||||
},
|
||||
"prettier": "@ufb/prettier-config"
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"extends": "@ufb/tsconfig/nestjs.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["."],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Reference in New Issue
Block a user