Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion designer-demo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"build:alpha": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite build --mode alpha",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite build",
"test": "vitest run",
"test:watch": "vitest"
"test:watch": "vitest",
"uploadMaterials": "node ./scripts/uploadMaterials.mjs"
},
"dependencies": {
"@opentiny/tiny-engine": "workspace:^",
Expand All @@ -28,6 +29,9 @@
"@opentiny/tiny-engine-vite-config": "workspace:^",
"@vitejs/plugin-vue": "^5.1.2",
"cross-env": "^7.0.3",
"dotenv": "^16.6.1",
"fs-extra": "^11.3.2",
"picocolors": "^1.1.1",
"vite": "^5.4.2",
"vitest": "3.0.9"
}
Expand Down
88 changes: 88 additions & 0 deletions designer-demo/scripts/logger.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const log = (() => {
return (...args) => {
process.stdout.write(args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
).join(' ') + '\n');
};
})();

const warn = (() => {
return (...args) => {
process.stderr.write(args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg)
).join(' ') + '\n');
};
})();

class Logger {
constructor(command = 'default') {
this.command = command;
this.colors = null;
this.hasColors = false;
this.initColors();
}

async initColors() {
try {
const colorsModule = await import('colors');
this.colors = colorsModule.default || colorsModule;
this.hasColors = true;
} catch (err) {
warn('colors package not found, using basic logging');
this.hasColors = false;
}
}

output(type, ...args) {
const time = new Date().toLocaleTimeString();
const prefix = `[${this.command}] [${time}]`;
const message = args.map((arg) => {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2);
}
return String(arg);
}).join(' ');

const outputFn = type === 'error' || type === 'warn' ? warn : log;

if (this.hasColors && this.colors) {
const colorMap = {
info: this.colors.cyan,
warn: this.colors.yellow,
error: this.colors.red,
success: this.colors.green,
};
const coloredType = colorMap[type]
? colorMap[type](type.toUpperCase())
: type.toUpperCase();

outputFn(`${prefix} ${coloredType} ${message}`);
} else {
const emojiMap = {
info: 'ℹ️',
warn: '⚠️',
error: '❌',
success: '✅',
};
outputFn(`${prefix} ${emojiMap[type] || ''} ${message}`);
}
}

success(...args) {
this.output('success', ...args);
}

info(...args) {
this.output('info', ...args);
}

warn(...args) {
this.output('warn', ...args);
}

error(...args) {
this.output('error', ...args);
}
}

export default Logger;
72 changes: 72 additions & 0 deletions designer-demo/scripts/uploadMaterials.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import dotenv from 'dotenv'
import fs from 'fs-extra'
import { Buffer } from 'node:buffer'
import path from 'node:path'
import Logger from './logger.mjs'


/**
* 同步物料资产包到后端数据库
* 1. 读取 env/.env.local 文件,获取后端地址。需要设置地址如:backend_url=http://localhost:9090
* 2. 读取 public/mock/bundle.json 文件,获取物料资产包数据
* 3. 将物料资产包数据通过 POST 请求上传到后端接口 /material-center/api/component/bundle/create
* 4. 检查数据库t_component表中数据是否更新成功
*
* 使用场景:
* 1. 本地已经将 bundle.json 文件进行修改,但是数据需要同步到后端数据库中。
* 2. 本地已经将 bundle.json 文件进行修改,但是出码仍然不正确。
* @returns
*/
async function main() {
const logger = new Logger('uploadMaterials')

// 先构造出.env*文件的绝对路径
const appDirectory = fs.realpathSync(process.cwd())
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath)
const pathsDotenv = resolveApp('env')
logger.info(`Start to load .env.local file from ${pathsDotenv}/.env.local`)
dotenv.config({ path: `${pathsDotenv}/.env.local` })
const { backend_url } = process.env

if (!backend_url) {
logger.error('backend_url is not set in .env.local file')
process.exit(1)
}

const bundlePath = path.join(process.cwd(), './public/mock/bundle.json')
logger.info(`Start to read bundle.json file from ${bundlePath}`)
const bundle = fs.readJSONSync(bundlePath)
const jsonBuffer = Buffer.from(JSON.stringify(bundle))

const requestUrl = (backend_url.endsWith('/') ? backend_url.slice(0, -1) : backend_url) + '/material-center/api/component/bundle/create'
logger.info(`Start to upload bundle.json file to ${requestUrl}`)
try {
const formData = new FormData()
formData.append('file', new Blob([jsonBuffer], { type: 'application/json'}), 'bundle.json')
const response = await fetch(requestUrl, {
method: 'POST',
body: formData
})

if (!response.ok) {
const errorText = await response.text()
throw new Error(`Upload failed with status ${response.status}: ${errorText}`)
}
const data = await response.json()
if (data && data.success) {
logger.success(`File uploaded successfully:${JSON.stringify(data)}`)
} else {
logger.warn(`Upload completed but success flag is false: ${JSON.stringify(data)}`)
logger.warn(`Upload completed with warnings: ${JSON.stringify(data.message)}`)
}
} catch (error) {
logger.error('Error uploading file:', error instanceof Error ? error.message : String(error))
}
}

main()
.catch((e) => {
const logger = new Logger('uploadMaterials')
logger.error('Error uploading file:', e instanceof Error ? e.message : String(e));
process.exit(1);
})
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
"splitMaterials": "node ./scripts/splitMaterials.mjs",
"buildMaterials": "node ./scripts/buildMaterials.mjs",
"updateTemplate": "node ./scripts/updateTemplate.mjs",
"uploadMaterials": "node scripts/uploadMaterials.mjs"
"uploadMaterials": "pnpm --filter designer-demo uploadMaterials"
},
"devDependencies": {
"@eslint/js": "^8.57.1",
Expand Down
4 changes: 4 additions & 0 deletions packages/engine-cli/template/designer/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "concurrently 'pnpm:serve:mock' 'pnpm:serve:frontend'",
"build:alpha": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite build --mode alpha",
"build": "cross-env NODE_OPTIONS=--max-old-space-size=10240 vite build",
"uploadMaterials": "node ./scripts/uploadMaterials.mjs",
"serve:frontend": "cross-env vite",
"serve:mock": "node node_modules/@opentiny/tiny-engine-mock/dist/app.js"
},
Expand All @@ -28,6 +29,9 @@
"@opentiny/tiny-engine-vite-config": "^2.8.0",
"@vitejs/plugin-vue": "^5.1.2",
"cross-env": "^7.0.3",
"dotenv": "^16.6.1",
"fs-extra": "^11.3.2",
"picocolors": "^1.1.1",
"vite": "^5.4.2",
"concurrently": "^8.2.0"
}
Expand Down
88 changes: 88 additions & 0 deletions packages/engine-cli/template/designer/scripts/logger.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const log = (() => {
return (...args) => {
process.stdout.write(
args.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg))).join(' ') + '\n'
)
}
})()

const warn = (() => {
return (...args) => {
process.stderr.write(
args.map((arg) => (typeof arg === 'object' ? JSON.stringify(arg, null, 2) : String(arg))).join(' ') + '\n'
)
}
})()

class Logger {
constructor(command = 'default') {
this.command = command
this.colors = null
this.hasColors = false
this.initColors()
}
Comment on lines +17 to +23
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical race condition: async initialization in constructor.

initColors() is async but called without await from the constructor. This creates a race condition where output() calls made immediately after instantiation will always see hasColors = false and colors = null, causing colorization to be skipped even when the package is available.

Consider one of these approaches:

Option 1: Static factory method (recommended)

-  constructor(command = 'default') {
+  static async create(command = 'default') {
+    const logger = new Logger(command);
+    await logger.initColors();
+    return logger;
+  }
+
+  constructor(command = 'default') {
     this.command = command
     this.colors = null
     this.hasColors = false
-    this.initColors()
   }

Option 2: Lazy initialization on first use

   constructor(command = 'default') {
     this.command = command
     this.colors = null
     this.hasColors = false
-    this.initColors()
+    this.colorInitPromise = this.initColors()
   }

-  output(type, ...args) {
+  async output(type, ...args) {
+    await this.colorInitPromise
     const time = new Date().toLocaleTimeString()
     // ... rest of method
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
class Logger {
constructor(command = 'default') {
this.command = command
this.colors = null
this.hasColors = false
this.initColors()
}
class Logger {
static async create(command = 'default') {
const logger = new Logger(command);
await logger.initColors();
return logger;
}
constructor(command = 'default') {
this.command = command
this.colors = null
this.hasColors = false
}


async initColors() {
try {
const colorsModule = await import('colors')
this.colors = colorsModule.default || colorsModule
this.hasColors = true
} catch (err) {
warn('colors package not found, using basic logging')
this.hasColors = false
}
}
Comment on lines +25 to +34
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Verify correct color package is imported.

Based on past review comments, the PR adds picocolors to devDependencies, but this code imports colors. If the dependency mismatch is real, the import will always fail and colors will never be available.

Run this script to verify which color package is actually in the dependencies:


🏁 Script executed:

#!/bin/bash
# Check which color packages are in package.json files

echo "=== Template designer package.json ==="
fd -t f '^package.json$' packages/engine-cli/template/designer --exec jq -r 'select(.devDependencies) | .devDependencies | to_entries | .[] | select(.key | contains("color")) | "\(.key): \(.value)"' {}

echo -e "\n=== Designer-demo package.json ==="
fd -t f '^package.json$' designer-demo --exec jq -r 'select(.devDependencies) | .devDependencies | to_entries | .[] | select(.key | contains("color")) | "\(.key): \(.value)"' {}

echo -e "\n=== Root package.json ==="
jq -r 'select(.devDependencies) | .devDependencies | to_entries | .[] | select(.key | contains("color")) | "\(.key): \(.value)"' package.json

Length of output: 158


Fix incorrect package import in logger.mjs.

The code imports colors (line 27) but the available dependency is picocolors. All package.json files in the repository specify only picocolors as available. Change the import statement to use the correct package:

const colorsModule = await import('picocolors')
🤖 Prompt for AI Agents
In packages/engine-cli/template/designer/scripts/logger.mjs around lines 25 to
34, the code attempts to dynamically import the non-existent 'colors' package;
replace that import with the correct package name 'picocolors' so it reads an
import for 'picocolors' instead, then continue to assign this.colors =
colorsModule.default || colorsModule and leave the existing hasColors/try-catch
behavior unchanged.


output(type, ...args) {
const time = new Date().toLocaleTimeString()
const prefix = `[${this.command}] [${time}]`
const message = args
.map((arg) => {
if (typeof arg === 'object') {
return JSON.stringify(arg, null, 2)
}
return String(arg)
})
.join(' ')

const outputFn = type === 'error' || type === 'warn' ? warn : log

if (this.hasColors && this.colors) {
const colorMap = {
info: this.colors.cyan,
warn: this.colors.yellow,
error: this.colors.red,
success: this.colors.green
}
const coloredType = colorMap[type] ? colorMap[type](type.toUpperCase()) : type.toUpperCase()

outputFn(`${prefix} ${coloredType} ${message}`)
} else {
const emojiMap = {
info: 'ℹ️',
warn: '⚠️',
error: '❌',
success: '✅'
}
outputFn(`${prefix} ${emojiMap[type] || ''} ${message}`)
}
}

success(...args) {
this.output('success', ...args)
}

info(...args) {
this.output('info', ...args)
}

warn(...args) {
this.output('warn', ...args)
}

error(...args) {
this.output('error', ...args)
}
}

export default Logger
72 changes: 72 additions & 0 deletions packages/engine-cli/template/designer/scripts/uploadMaterials.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import dotenv from 'dotenv'
import fs from 'fs-extra'
import { Buffer } from 'node:buffer'
import path from 'node:path'
import Logger from './logger.mjs'

/**
* 同步物料资产包到后端数据库
* 1. 读取 env/.env.local 文件,获取后端地址。需要设置地址如:backend_url=http://localhost:9090
* 2. 读取 public/mock/bundle.json 文件,获取物料资产包数据
* 3. 将物料资产包数据通过 POST 请求上传到后端接口 /material-center/api/component/bundle/create
* 4. 检查数据库t_component表中数据是否更新成功
*
* 使用场景:
* 1. 本地已经将 bundle.json 文件进行修改,但是数据需要同步到后端数据库中。
* 2. 本地已经将 bundle.json 文件进行修改,但是出码仍然不正确。
* @returns
*/
async function main() {
const logger = new Logger('uploadMaterials')

// 先构造出.env*文件的绝对路径
const appDirectory = fs.realpathSync(process.cwd())
const resolveApp = (relativePath) => path.resolve(appDirectory, relativePath)
const pathsDotenv = resolveApp('env')
logger.info(`Start to load .env.local file from ${pathsDotenv}/.env.local`)
dotenv.config({ path: `${pathsDotenv}/.env.local` })
const { backend_url } = process.env

if (!backend_url) {
logger.error('backend_url is not set in .env.local file')
process.exit(1)
}

const bundlePath = path.join(process.cwd(), './public/mock/bundle.json')
logger.info(`Start to read bundle.json file from ${bundlePath}`)
const bundle = fs.readJSONSync(bundlePath)
const jsonBuffer = Buffer.from(JSON.stringify(bundle))

const requestUrl =
(backend_url.endsWith('/') ? backend_url.slice(0, -1) : backend_url) +
'/material-center/api/component/bundle/create'
logger.info(`Start to upload bundle.json file to ${requestUrl}`)
try {
const formData = new FormData()
formData.append('file', new Blob([jsonBuffer], { type: 'application/json' }), 'bundle.json')
const response = await fetch(requestUrl, {
method: 'POST',
body: formData
})

if (!response.ok) {
const errorText = await response.text()
throw new Error(`Upload failed with status ${response.status}: ${errorText}`)
}
const data = await response.json()
if (data && data.success) {
logger.success(`File uploaded successfully:${JSON.stringify(data)}`)
} else {
logger.warn(`Upload completed but success flag is false: ${JSON.stringify(data)}`)
logger.warn(`Upload completed with warnings: ${JSON.stringify(data.message)}`)
}
} catch (error) {
logger.error('Error uploading file:', error instanceof Error ? error.message : String(error))
}
}

main().catch((e) => {
const logger = new Logger('uploadMaterials')
logger.error('Error uploading file:', e instanceof Error ? e.message : String(e))
process.exit(1)
})
2 changes: 1 addition & 1 deletion scripts/updateTemplate.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fs from 'fs-extra'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import Logger from './logger.mjs'
import pkg from '../packages/design-core/package.json' assert { type: 'json' }
import pkg from '../packages/design-core/package.json' with { type: 'json' }

const logger = new Logger('updateTemplate')

Expand Down
Loading