fix: 修复n8n项目多个模块错误和配置问题
- 添加WooCommerce节点描述文件 - 实现错误处理基础类结构 - 修复data-store仓库TypeScript类型问题 - 更新Claude本地配置 - 修复start.bat脚本
This commit is contained in:
12
n8n-n8n-1.109.2/.claude/settings.local.json
Normal file
12
n8n-n8n-1.109.2/.claude/settings.local.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(pnpm run dev:*)",
|
||||||
|
"Bash(pnpm list:*)",
|
||||||
|
"Bash(pnpm build:*)",
|
||||||
|
"Read(//e/work/n8n_Demo/**)"
|
||||||
|
],
|
||||||
|
"deny": [],
|
||||||
|
"ask": []
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
"clean": "rimraf dist .turbo",
|
"clean": "rimraf dist .turbo",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"copy-templates": "node scripts/copy-templates.mjs",
|
"copy-templates": "node scripts/copy-templates.mjs",
|
||||||
"dev": "tsc -p tsconfig.build.json -w --onCompilationComplete \"pnpm copy-templates\"",
|
"dev": "tsc-watch -p tsconfig.build.json --onSuccess \"pnpm copy-templates\"",
|
||||||
"format": "biome format --write src",
|
"format": "biome format --write src",
|
||||||
"format:check": "biome ci src",
|
"format:check": "biome ci src",
|
||||||
"lint": "eslint src --quiet",
|
"lint": "eslint src --quiet",
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export class DataStoreColumnRepository extends Repository<DataStoreColumn> {
|
|||||||
dataStoreId,
|
dataStoreId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await em.insert(DataStoreColumn, column);
|
await em.insert(DataStoreColumn, { ...column } as any);
|
||||||
|
|
||||||
const queryRunner = em.queryRunner;
|
const queryRunner = em.queryRunner;
|
||||||
if (!queryRunner) {
|
if (!queryRunner) {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class DataStoreRepository extends Repository<DataStore> {
|
|||||||
let dataStoreId: string | undefined;
|
let dataStoreId: string | undefined;
|
||||||
await this.manager.transaction(async (em) => {
|
await this.manager.transaction(async (em) => {
|
||||||
const dataStore = em.create(DataStore, { name, columns, projectId });
|
const dataStore = em.create(DataStore, { name, columns, projectId });
|
||||||
await em.insert(DataStore, dataStore);
|
await em.insert(DataStore, { ...dataStore } as any);
|
||||||
dataStoreId = dataStore.id;
|
dataStoreId = dataStore.id;
|
||||||
|
|
||||||
const queryRunner = em.queryRunner;
|
const queryRunner = em.queryRunner;
|
||||||
@@ -47,7 +47,7 @@ export class DataStoreRepository extends Repository<DataStore> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (columnEntities.length > 0) {
|
if (columnEntities.length > 0) {
|
||||||
await em.insert(DataStoreColumn, columnEntities);
|
await em.insert(DataStoreColumn, columnEntities as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
// create user table (will create empty table with just id column if no columns)
|
// create user table (will create empty table with just id column if no columns)
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
import { customerCreateFields, customerUpdateFields } from './shared';
|
||||||
|
|
||||||
|
export const customerOperations: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Operation',
|
||||||
|
name: 'operation',
|
||||||
|
type: 'options',
|
||||||
|
noDataExpression: true,
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Create',
|
||||||
|
value: 'create',
|
||||||
|
description: 'Create a customer',
|
||||||
|
action: 'Create a customer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Delete',
|
||||||
|
value: 'delete',
|
||||||
|
description: 'Delete a customer',
|
||||||
|
action: 'Delete a customer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get',
|
||||||
|
value: 'get',
|
||||||
|
description: 'Retrieve a customer',
|
||||||
|
action: 'Get a customer',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Get Many',
|
||||||
|
value: 'getAll',
|
||||||
|
description: 'Retrieve many customers',
|
||||||
|
action: 'Get many customers',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Update',
|
||||||
|
value: 'update',
|
||||||
|
description: 'Update a customer',
|
||||||
|
action: 'Update a customer',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'create',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const customerFields: INodeProperties[] = [
|
||||||
|
// ----------------------------------------
|
||||||
|
// customer: create
|
||||||
|
// ----------------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'name@email.com',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customerCreateFields,
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// customer: delete
|
||||||
|
// ----------------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Customer ID',
|
||||||
|
name: 'customerId',
|
||||||
|
description: 'ID of the customer to delete',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['delete'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// customer: get
|
||||||
|
// ----------------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Customer ID',
|
||||||
|
name: 'customerId',
|
||||||
|
description: 'ID of the customer to retrieve',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['get'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// customer: getAll
|
||||||
|
// ----------------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Return All',
|
||||||
|
name: 'returnAll',
|
||||||
|
type: 'boolean',
|
||||||
|
default: false,
|
||||||
|
description: 'Whether to return all results or only up to a given limit',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Limit',
|
||||||
|
name: 'limit',
|
||||||
|
type: 'number',
|
||||||
|
default: 50,
|
||||||
|
description: 'Max number of results to return',
|
||||||
|
typeOptions: {
|
||||||
|
minValue: 1,
|
||||||
|
},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
returnAll: [false],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Filters',
|
||||||
|
name: 'filters',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Filter',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['getAll'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'name@email.com',
|
||||||
|
default: '',
|
||||||
|
description: 'Email address to filter customers by',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Sort Order',
|
||||||
|
name: 'order',
|
||||||
|
description: 'Order to sort customers in',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'Ascending',
|
||||||
|
value: 'asc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Descending',
|
||||||
|
value: 'desc',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'asc',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Order By',
|
||||||
|
name: 'orderby',
|
||||||
|
description: 'Field to sort customers by',
|
||||||
|
type: 'options',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
name: 'ID',
|
||||||
|
value: 'id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Include',
|
||||||
|
value: 'include',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Name',
|
||||||
|
value: 'name',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Registered Date',
|
||||||
|
value: 'registered_date',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
default: 'id',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// customer: update
|
||||||
|
// ----------------------------------------
|
||||||
|
{
|
||||||
|
displayName: 'Customer ID',
|
||||||
|
name: 'customerId',
|
||||||
|
description: 'ID of the customer to update',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
default: '',
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
customerUpdateFields,
|
||||||
|
];
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from './CustomerDescription';
|
||||||
@@ -0,0 +1,185 @@
|
|||||||
|
import type { INodeProperties } from 'n8n-workflow';
|
||||||
|
|
||||||
|
const customerAddressOptions: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'First Name',
|
||||||
|
name: 'first_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Last Name',
|
||||||
|
name: 'last_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Company',
|
||||||
|
name: 'company',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Address 1',
|
||||||
|
name: 'address_1',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Address 2',
|
||||||
|
name: 'address_2',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'City',
|
||||||
|
name: 'city',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'State',
|
||||||
|
name: 'state',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Postcode',
|
||||||
|
name: 'postcode',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Country',
|
||||||
|
name: 'country',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Email',
|
||||||
|
name: 'email',
|
||||||
|
type: 'string',
|
||||||
|
placeholder: 'name@email.com',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Phone',
|
||||||
|
name: 'phone',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const customerUpdateOptions: INodeProperties[] = [
|
||||||
|
{
|
||||||
|
displayName: 'Billing Address',
|
||||||
|
name: 'billing',
|
||||||
|
type: 'collection',
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
options: customerAddressOptions,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'First Name',
|
||||||
|
name: 'first_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Last Name',
|
||||||
|
name: 'last_name',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Metadata',
|
||||||
|
name: 'meta_data',
|
||||||
|
type: 'fixedCollection',
|
||||||
|
typeOptions: {
|
||||||
|
multipleValues: true,
|
||||||
|
},
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Metadata Field',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
displayName: 'Metadata Fields',
|
||||||
|
name: 'meta_data_fields',
|
||||||
|
values: [
|
||||||
|
{
|
||||||
|
displayName: 'Key',
|
||||||
|
name: 'key',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Value',
|
||||||
|
name: 'value',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Password',
|
||||||
|
name: 'password',
|
||||||
|
type: 'string',
|
||||||
|
typeOptions: { password: true },
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
'/resource': ['customer'],
|
||||||
|
'/operation': ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
displayName: 'Shipping Address',
|
||||||
|
name: 'shipping',
|
||||||
|
type: 'collection',
|
||||||
|
default: {},
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
options: customerAddressOptions,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const customerCreateOptions: INodeProperties[] = [
|
||||||
|
...customerUpdateOptions,
|
||||||
|
{
|
||||||
|
displayName: 'Username',
|
||||||
|
name: 'username',
|
||||||
|
type: 'string',
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const customerCreateFields: INodeProperties = {
|
||||||
|
displayName: 'Additional Fields',
|
||||||
|
name: 'additionalFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['create'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: customerCreateOptions,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const customerUpdateFields: INodeProperties = {
|
||||||
|
displayName: 'Update Fields',
|
||||||
|
name: 'updateFields',
|
||||||
|
type: 'collection',
|
||||||
|
placeholder: 'Add Field',
|
||||||
|
default: {},
|
||||||
|
displayOptions: {
|
||||||
|
show: {
|
||||||
|
resource: ['customer'],
|
||||||
|
operation: ['update'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
options: customerUpdateOptions,
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import { ApplicationError, type ReportingOptions } from '@n8n/errors';
|
||||||
|
|
||||||
|
import type { Functionality, IDataObject, JsonObject } from '../../interfaces';
|
||||||
|
|
||||||
|
interface ExecutionBaseErrorOptions extends ReportingOptions {
|
||||||
|
cause?: Error;
|
||||||
|
errorResponse?: JsonObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ExecutionBaseError extends ApplicationError {
|
||||||
|
description: string | null | undefined;
|
||||||
|
|
||||||
|
override cause?: Error;
|
||||||
|
|
||||||
|
errorResponse?: JsonObject;
|
||||||
|
|
||||||
|
timestamp: number;
|
||||||
|
|
||||||
|
context: IDataObject = {};
|
||||||
|
|
||||||
|
lineNumber: number | undefined;
|
||||||
|
|
||||||
|
functionality: Functionality = 'regular';
|
||||||
|
|
||||||
|
constructor(message: string, options: ExecutionBaseErrorOptions = {}) {
|
||||||
|
super(message, options);
|
||||||
|
|
||||||
|
this.name = this.constructor.name;
|
||||||
|
this.timestamp = Date.now();
|
||||||
|
|
||||||
|
const { cause, errorResponse } = options;
|
||||||
|
if (cause instanceof ExecutionBaseError) {
|
||||||
|
this.context = cause.context;
|
||||||
|
} else if (cause && !(cause instanceof Error)) {
|
||||||
|
this.cause = cause;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorResponse) this.errorResponse = errorResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
toJSON?() {
|
||||||
|
return {
|
||||||
|
message: this.message,
|
||||||
|
lineNumber: this.lineNumber,
|
||||||
|
timestamp: this.timestamp,
|
||||||
|
name: this.name,
|
||||||
|
description: this.description,
|
||||||
|
context: this.context,
|
||||||
|
cause: this.cause,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,187 @@
|
|||||||
|
import { ExecutionBaseError } from './execution-base.error';
|
||||||
|
import type { IDataObject, INode, JsonObject } from '../../interfaces';
|
||||||
|
import { isTraversableObject, jsonParse } from '../../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Descriptive messages for common errors.
|
||||||
|
*/
|
||||||
|
const COMMON_ERRORS: IDataObject = {
|
||||||
|
// nodeJS errors
|
||||||
|
ECONNREFUSED: 'The service refused the connection - perhaps it is offline',
|
||||||
|
ECONNRESET:
|
||||||
|
'The connection to the server was closed unexpectedly, perhaps it is offline. You can retry the request immediately or wait and retry later.',
|
||||||
|
ENOTFOUND:
|
||||||
|
'The connection cannot be established, this usually occurs due to an incorrect host (domain) value',
|
||||||
|
ETIMEDOUT:
|
||||||
|
"The connection timed out, consider setting the 'Retry on Fail' option in the node settings",
|
||||||
|
ERRADDRINUSE:
|
||||||
|
'The port is already occupied by some other application, if possible change the port or kill the application that is using it',
|
||||||
|
EADDRNOTAVAIL: 'The address is not available, ensure that you have the right IP address',
|
||||||
|
ECONNABORTED: 'The connection was aborted, perhaps the server is offline',
|
||||||
|
EHOSTUNREACH: 'The host is unreachable, perhaps the server is offline',
|
||||||
|
EAI_AGAIN: 'The DNS server returned an error, perhaps the server is offline',
|
||||||
|
ENOENT: 'The file or directory does not exist',
|
||||||
|
EISDIR: 'The file path was expected but the given path is a directory',
|
||||||
|
ENOTDIR: 'The directory path was expected but the given path is a file',
|
||||||
|
EACCES: 'Forbidden by access permissions, make sure you have the right permissions',
|
||||||
|
EEXIST: 'The file or directory already exists',
|
||||||
|
EPERM: 'Operation not permitted, make sure you have the right permissions',
|
||||||
|
// other errors
|
||||||
|
GETADDRINFO: 'The server closed the connection unexpectedly',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for specific NodeError-types, with functionality for finding
|
||||||
|
* a value recursively inside an error object.
|
||||||
|
*/
|
||||||
|
export abstract class NodeError extends ExecutionBaseError {
|
||||||
|
messages: string[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
readonly node: INode,
|
||||||
|
error: Error | JsonObject,
|
||||||
|
) {
|
||||||
|
const isError = error instanceof Error;
|
||||||
|
const message = isError ? error.message : '';
|
||||||
|
const options = isError ? { cause: error } : { errorResponse: error };
|
||||||
|
super(message, options);
|
||||||
|
|
||||||
|
if (error instanceof NodeError) {
|
||||||
|
this.tags.reWrapped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Finds property through exploration based on potential keys and traversal keys.
|
||||||
|
* Depth-first approach.
|
||||||
|
*
|
||||||
|
* This method iterates over `potentialKeys` and, if the value at the key is a
|
||||||
|
* truthy value, the type of the value is checked:
|
||||||
|
* (1) if a string or number, the value is returned as a string; or
|
||||||
|
* (2) if an array,
|
||||||
|
* its string or number elements are collected as a long string,
|
||||||
|
* its object elements are traversed recursively (restart this function
|
||||||
|
* with each object as a starting point), or
|
||||||
|
* (3) if it is an object, it traverses the object and nested ones recursively
|
||||||
|
* based on the `potentialKeys` and returns a string if found.
|
||||||
|
*
|
||||||
|
* If nothing found via `potentialKeys` this method iterates over `traversalKeys` and
|
||||||
|
* if the value at the key is a traversable object, it restarts with the object as the
|
||||||
|
* new starting point (recursion).
|
||||||
|
* If nothing found for any of the `traversalKeys`, exploration continues with remaining
|
||||||
|
* `traversalKeys`.
|
||||||
|
*
|
||||||
|
* Otherwise, if all the paths have been exhausted and no value is eligible, `null` is
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
protected findProperty(
|
||||||
|
jsonError: JsonObject,
|
||||||
|
potentialKeys: string[],
|
||||||
|
traversalKeys: string[] = [],
|
||||||
|
): string | null {
|
||||||
|
for (const key of potentialKeys) {
|
||||||
|
let value = jsonError[key];
|
||||||
|
if (value) {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
try {
|
||||||
|
value = jsonParse(value);
|
||||||
|
} catch (error) {
|
||||||
|
return value as string;
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') return value;
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') return value.toString();
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
const resolvedErrors: string[] = value
|
||||||
|
|
||||||
|
.map((jsonError) => {
|
||||||
|
if (typeof jsonError === 'string') return jsonError;
|
||||||
|
if (typeof jsonError === 'number') return jsonError.toString();
|
||||||
|
if (isTraversableObject(jsonError)) {
|
||||||
|
return this.findProperty(jsonError, potentialKeys);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.filter((errorValue): errorValue is string => errorValue !== null);
|
||||||
|
|
||||||
|
if (resolvedErrors.length === 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return resolvedErrors.join(' | ');
|
||||||
|
}
|
||||||
|
if (isTraversableObject(value)) {
|
||||||
|
const property = this.findProperty(value, potentialKeys);
|
||||||
|
if (property) {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const key of traversalKeys) {
|
||||||
|
const value = jsonError[key];
|
||||||
|
if (isTraversableObject(value)) {
|
||||||
|
const property = this.findProperty(value, potentialKeys, traversalKeys);
|
||||||
|
if (property) {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Preserve the original error message before setting the new one
|
||||||
|
*/
|
||||||
|
protected addToMessages(message: string): void {
|
||||||
|
if (message && !this.messages.includes(message)) {
|
||||||
|
this.messages.push(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set descriptive error message if code is provided or if message contains any of the common errors,
|
||||||
|
* update description to include original message plus the description
|
||||||
|
*/
|
||||||
|
protected setDescriptiveErrorMessage(
|
||||||
|
message: string,
|
||||||
|
messages: string[],
|
||||||
|
code?: string | null,
|
||||||
|
messageMapping?: { [key: string]: string },
|
||||||
|
): [string, string[]] {
|
||||||
|
let newMessage = message;
|
||||||
|
|
||||||
|
if (messageMapping) {
|
||||||
|
for (const [mapKey, mapMessage] of Object.entries(messageMapping)) {
|
||||||
|
if ((message || '').toUpperCase().includes(mapKey.toUpperCase())) {
|
||||||
|
newMessage = mapMessage;
|
||||||
|
messages.push(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (newMessage !== message) {
|
||||||
|
return [newMessage, messages];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if code is provided and it is in the list of common errors set the message and return early
|
||||||
|
if (code && typeof code === 'string' && COMMON_ERRORS[code.toUpperCase()]) {
|
||||||
|
newMessage = COMMON_ERRORS[code] as string;
|
||||||
|
messages.push(message);
|
||||||
|
return [newMessage, messages];
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if message contains any of the common errors and set the message and description
|
||||||
|
for (const [errorCode, errorDescriptiveMessage] of Object.entries(COMMON_ERRORS)) {
|
||||||
|
if ((message || '').toUpperCase().includes(errorCode.toUpperCase())) {
|
||||||
|
newMessage = errorDescriptiveMessage as string;
|
||||||
|
messages.push(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [newMessage, messages];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import type { Event } from '@sentry/node';
|
||||||
|
import callsites from 'callsites';
|
||||||
|
|
||||||
|
import type { ErrorTags, ErrorLevel, ReportingOptions } from '@n8n/errors';
|
||||||
|
|
||||||
|
export type BaseErrorOptions = { description?: string | undefined | null } & ErrorOptions &
|
||||||
|
ReportingOptions;
|
||||||
|
/**
|
||||||
|
* Base class for all errors
|
||||||
|
*/
|
||||||
|
export abstract class BaseError extends Error {
|
||||||
|
/**
|
||||||
|
* Error level. Defines which level the error should be logged/reported
|
||||||
|
* @default 'error'
|
||||||
|
*/
|
||||||
|
level: ErrorLevel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the error should be reported to Sentry.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
readonly shouldReport: boolean;
|
||||||
|
|
||||||
|
readonly description: string | null | undefined;
|
||||||
|
|
||||||
|
readonly tags: ErrorTags;
|
||||||
|
|
||||||
|
readonly extra?: Event['extra'];
|
||||||
|
|
||||||
|
readonly packageName?: string;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
message: string,
|
||||||
|
{
|
||||||
|
level = 'error',
|
||||||
|
description,
|
||||||
|
shouldReport,
|
||||||
|
tags = {},
|
||||||
|
extra,
|
||||||
|
...rest
|
||||||
|
}: BaseErrorOptions = {},
|
||||||
|
) {
|
||||||
|
super(message, rest);
|
||||||
|
|
||||||
|
this.level = level;
|
||||||
|
this.shouldReport = shouldReport ?? (level === 'error' || level === 'fatal');
|
||||||
|
this.description = description;
|
||||||
|
this.tags = tags;
|
||||||
|
this.extra = extra;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const filePath = callsites()[2].getFileName() ?? '';
|
||||||
|
const match = /packages\/([^\/]+)\//.exec(filePath)?.[1];
|
||||||
|
|
||||||
|
if (match) this.tags.packageName = match;
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { BaseErrorOptions } from './base.error';
|
||||||
|
import { BaseError } from './base.error';
|
||||||
|
|
||||||
|
export type OperationalErrorOptions = Omit<BaseErrorOptions, 'level'> & {
|
||||||
|
level?: 'info' | 'warning' | 'error';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error that indicates a transient issue, like a network request failing,
|
||||||
|
* a database query timing out, etc. These are expected to happen, are
|
||||||
|
* transient by nature and should be handled gracefully.
|
||||||
|
*
|
||||||
|
* Default level: warning
|
||||||
|
*/
|
||||||
|
export class OperationalError extends BaseError {
|
||||||
|
constructor(message: string, opts: OperationalErrorOptions = {}) {
|
||||||
|
opts.level = opts.level ?? 'warning';
|
||||||
|
|
||||||
|
super(message, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
import type { BaseErrorOptions } from './base.error';
|
||||||
|
import { BaseError } from './base.error';
|
||||||
|
|
||||||
|
export type UnexpectedErrorOptions = Omit<BaseErrorOptions, 'level'> & {
|
||||||
|
level?: 'error' | 'fatal';
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error that indicates something is wrong in the code: logic mistakes,
|
||||||
|
* unhandled cases, assertions that fail. These are not recoverable and
|
||||||
|
* should be brought to developers' attention.
|
||||||
|
*
|
||||||
|
* Default level: error
|
||||||
|
*/
|
||||||
|
export class UnexpectedError extends BaseError {
|
||||||
|
constructor(message: string, opts: UnexpectedErrorOptions = {}) {
|
||||||
|
opts.level = opts.level ?? 'error';
|
||||||
|
|
||||||
|
super(message, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { BaseErrorOptions } from './base.error';
|
||||||
|
import { BaseError } from './base.error';
|
||||||
|
|
||||||
|
export type UserErrorOptions = Omit<BaseErrorOptions, 'level'> & {
|
||||||
|
level?: 'info' | 'warning';
|
||||||
|
description?: string | null | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error that indicates the user performed an action that caused an error.
|
||||||
|
* E.g. provided invalid input, tried to access a resource they’re not
|
||||||
|
* authorized to, or violates a business rule.
|
||||||
|
*
|
||||||
|
* Default level: info
|
||||||
|
*/
|
||||||
|
export class UserError extends BaseError {
|
||||||
|
declare readonly description: string | null | undefined;
|
||||||
|
|
||||||
|
constructor(message: string, opts: UserErrorOptions = {}) {
|
||||||
|
opts.level = opts.level ?? 'info';
|
||||||
|
|
||||||
|
super(message, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,475 +0,0 @@
|
|||||||
@echo off
|
|
||||||
chcp 65001 >nul 2>&1
|
|
||||||
REM n8n 中文版快速启动脚本 (Windows版 - 增强兼容性)
|
|
||||||
REM 作者: 小齐
|
|
||||||
REM 最后更新: 2025-09-08
|
|
||||||
REM 兼容性: Windows 7/8/10/11, PowerShell/CMD
|
|
||||||
|
|
||||||
setlocal enabledelayedexpansion
|
|
||||||
|
|
||||||
REM 显示启动横幅
|
|
||||||
:show_banner
|
|
||||||
echo ======================================
|
|
||||||
echo n8n 中文版快速启动脚本
|
|
||||||
echo 版本: n8n-1.109.2 with 中文翻译
|
|
||||||
echo 维护者: xiaoqi
|
|
||||||
echo ======================================
|
|
||||||
echo.
|
|
||||||
|
|
||||||
REM 解析命令行参数
|
|
||||||
set "DEV_MODE="
|
|
||||||
set "FORCE_BUILD="
|
|
||||||
set "CHECK_ONLY="
|
|
||||||
set "SKIP_DEPS="
|
|
||||||
set "USE_NPM="
|
|
||||||
|
|
||||||
:parse_args
|
|
||||||
if "%~1"=="" goto main_execution
|
|
||||||
if /I "%~1"=="-h" goto show_help
|
|
||||||
if /I "%~1"=="--help" goto show_help
|
|
||||||
if /I "%~1"=="-d" (
|
|
||||||
set "DEV_MODE=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
if /I "%~1"=="--dev" (
|
|
||||||
set "DEV_MODE=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
if /I "%~1"=="-b" (
|
|
||||||
set "FORCE_BUILD=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
if /I "%~1"=="--build" (
|
|
||||||
set "FORCE_BUILD=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
if /I "%~1"=="-c" (
|
|
||||||
set "CHECK_ONLY=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
if /I "%~1"=="--check" (
|
|
||||||
set "CHECK_ONLY=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
if /I "%~1"=="--skip-deps" (
|
|
||||||
set "SKIP_DEPS=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
if /I "%~1"=="--npm" (
|
|
||||||
set "USE_NPM=true"
|
|
||||||
shift
|
|
||||||
goto parse_args
|
|
||||||
)
|
|
||||||
echo [错误] 未知选项: %~1
|
|
||||||
goto show_help
|
|
||||||
|
|
||||||
:main_execution
|
|
||||||
REM 检查操作系统版本
|
|
||||||
call :check_os_version
|
|
||||||
|
|
||||||
REM 检查并切换到正确目录
|
|
||||||
call :check_and_change_directory
|
|
||||||
if errorlevel 1 goto error_exit
|
|
||||||
|
|
||||||
REM 检查依赖
|
|
||||||
if not defined SKIP_DEPS (
|
|
||||||
echo [信息] 正在检查系统依赖...
|
|
||||||
call :check_dependencies
|
|
||||||
if errorlevel 1 goto error_exit
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 检查端口占用
|
|
||||||
call :check_port
|
|
||||||
if errorlevel 1 goto error_exit
|
|
||||||
|
|
||||||
REM 停止现有进程
|
|
||||||
call :stop_existing
|
|
||||||
|
|
||||||
REM 设置环境变量
|
|
||||||
call :setup_environment
|
|
||||||
|
|
||||||
REM 仅检查模式
|
|
||||||
if defined CHECK_ONLY (
|
|
||||||
echo [成功] 系统状态检查完成,一切正常
|
|
||||||
goto end
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 安装依赖(如果需要)
|
|
||||||
if not exist "node_modules\" (
|
|
||||||
echo [信息] 检测到缺少依赖,正在安装...
|
|
||||||
if defined USE_NPM (
|
|
||||||
call :install_with_npm
|
|
||||||
) else (
|
|
||||||
call :install_with_pnpm
|
|
||||||
)
|
|
||||||
if errorlevel 1 goto error_exit
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 强制构建或检查构建状态
|
|
||||||
if defined FORCE_BUILD (
|
|
||||||
echo [信息] 强制重新构建项目...
|
|
||||||
call :build_project
|
|
||||||
if errorlevel 1 goto error_exit
|
|
||||||
) else (
|
|
||||||
call :check_build
|
|
||||||
if errorlevel 1 goto error_exit
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 开发模式或正常启动
|
|
||||||
if defined DEV_MODE (
|
|
||||||
echo [信息] 正在以开发模式启动 n8n...
|
|
||||||
echo [警告] 开发模式启动时间较长,请耐心等待...
|
|
||||||
if defined USE_NPM (
|
|
||||||
call npm run dev
|
|
||||||
) else (
|
|
||||||
call pnpm dev
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
call :start_n8n
|
|
||||||
)
|
|
||||||
goto end
|
|
||||||
|
|
||||||
REM ========== 函数定义 ==========
|
|
||||||
|
|
||||||
:check_os_version
|
|
||||||
REM 检查 Windows 版本兼容性
|
|
||||||
ver | findstr /i "5\.1\." >nul
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [警告] 检测到 Windows XP,可能存在兼容性问题
|
|
||||||
)
|
|
||||||
ver | findstr /i "6\.0\." >nul
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [信息] 检测到 Windows Vista
|
|
||||||
)
|
|
||||||
ver | findstr /i "6\.1\." >nul
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [信息] 检测到 Windows 7
|
|
||||||
)
|
|
||||||
ver | findstr /i "6\.2\." >nul
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [信息] 检测到 Windows 8
|
|
||||||
)
|
|
||||||
ver | findstr /i "6\.3\." >nul
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [信息] 检测到 Windows 8.1
|
|
||||||
)
|
|
||||||
ver | findstr /i "10\.0\." >nul
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [信息] 检测到 Windows 10/11
|
|
||||||
)
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:check_and_change_directory
|
|
||||||
REM 检查当前目录是否为 n8n 项目目录
|
|
||||||
if exist "package.json" if exist "packages" (
|
|
||||||
echo [信息] 检测到当前已在 n8n 项目目录
|
|
||||||
exit /b 0
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 获取脚本所在目录(兼容不同 Windows 版本)
|
|
||||||
set "SCRIPT_DIR=%~dp0"
|
|
||||||
if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
|
|
||||||
|
|
||||||
REM 如果脚本在 n8n-n8n-1.109.2 目录内
|
|
||||||
echo %SCRIPT_DIR% | findstr /I /C:"n8n-n8n-1.109.2" >nul
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [信息] 切换到脚本所在的 n8n 项目目录
|
|
||||||
cd /d "%SCRIPT_DIR%" 2>nul
|
|
||||||
if errorlevel 1 (
|
|
||||||
REM 兼容旧版 Windows
|
|
||||||
cd "%SCRIPT_DIR%" 2>nul
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo [错误] 无法切换到目录: %SCRIPT_DIR%
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
echo [成功] 已切换到正确的 n8n 项目目录
|
|
||||||
exit /b 0
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 尝试找到 n8n-n8n-1.109.2 目录
|
|
||||||
if exist "n8n-n8n-1.109.2\" (
|
|
||||||
cd n8n-n8n-1.109.2
|
|
||||||
) else if exist "..\n8n-n8n-1.109.2\" (
|
|
||||||
cd ..\n8n-n8n-1.109.2
|
|
||||||
) else if exist "..\..\n8n-n8n-1.109.2\" (
|
|
||||||
cd ..\..\n8n-n8n-1.109.2
|
|
||||||
) else (
|
|
||||||
echo [错误] 未找到 n8n-n8n-1.109.2 目录
|
|
||||||
echo [信息] 请确保在正确的项目目录下运行此脚本
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 验证目录结构
|
|
||||||
if not exist "package.json" (
|
|
||||||
echo [错误] 目录结构不正确,缺少 package.json
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
if not exist "packages" (
|
|
||||||
echo [错误] 目录结构不正确,缺少 packages 目录
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
|
|
||||||
echo [成功] 已切换到正确的 n8n 项目目录
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:check_dependencies
|
|
||||||
REM 检查 Node.js
|
|
||||||
where node >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
REM 尝试常见的 Node.js 安装路径
|
|
||||||
if exist "C:\Program Files\nodejs\node.exe" (
|
|
||||||
set "PATH=C:\Program Files\nodejs;%PATH%"
|
|
||||||
) else if exist "C:\Program Files (x86)\nodejs\node.exe" (
|
|
||||||
set "PATH=C:\Program Files (x86)\nodejs;%PATH%"
|
|
||||||
) else (
|
|
||||||
echo [错误] Node.js 未安装或未在 PATH 中
|
|
||||||
echo [信息] 请访问 https://nodejs.org 下载安装
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
echo [信息] Node.js 版本:
|
|
||||||
node --version
|
|
||||||
|
|
||||||
REM 检查包管理器
|
|
||||||
if not defined USE_NPM (
|
|
||||||
where pnpm >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo [警告] pnpm 未安装,尝试使用 npm
|
|
||||||
set "USE_NPM=true"
|
|
||||||
) else (
|
|
||||||
echo [信息] 使用 pnpm 作为包管理器
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo [信息] 使用 npm 作为包管理器
|
|
||||||
)
|
|
||||||
|
|
||||||
echo [成功] 依赖检查通过
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:install_with_pnpm
|
|
||||||
echo [信息] 使用 pnpm 安装依赖...
|
|
||||||
where pnpm >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo [信息] 正在安装 pnpm...
|
|
||||||
call npm install -g pnpm@8
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo [错误] pnpm 安装失败
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
call pnpm install --no-frozen-lockfile
|
|
||||||
exit /b %errorlevel%
|
|
||||||
|
|
||||||
:install_with_npm
|
|
||||||
echo [警告] 使用 npm 安装(可能不兼容 workspace)
|
|
||||||
echo [信息] 建议安装 pnpm: npm install -g pnpm
|
|
||||||
call npm install --legacy-peer-deps
|
|
||||||
exit /b %errorlevel%
|
|
||||||
|
|
||||||
:check_port
|
|
||||||
REM 检查端口 5678 是否被占用
|
|
||||||
netstat -ano 2>nul | findstr ":5678" >nul 2>&1
|
|
||||||
if not errorlevel 1 (
|
|
||||||
echo [警告] 端口 5678 已被占用
|
|
||||||
echo [信息] 正在查看占用进程...
|
|
||||||
netstat -ano | findstr ":5678"
|
|
||||||
echo.
|
|
||||||
|
|
||||||
set /p "kill_process=是否终止占用进程?(y/N): "
|
|
||||||
if /I "!kill_process!"=="y" (
|
|
||||||
echo [信息] 正在终止占用进程...
|
|
||||||
for /f "tokens=5" %%a in ('netstat -ano ^| findstr ":5678"') do (
|
|
||||||
taskkill /PID %%a /F >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
REM Windows 7 兼容性
|
|
||||||
tskill %%a >nul 2>&1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
echo [成功] 进程已终止
|
|
||||||
timeout /t 2 /nobreak >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
REM Windows 7 兼容性
|
|
||||||
ping -n 3 127.0.0.1 >nul
|
|
||||||
)
|
|
||||||
) else (
|
|
||||||
echo [错误] 无法启动 n8n,端口被占用
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:stop_existing
|
|
||||||
echo [信息] 正在停止现有的 n8n 进程...
|
|
||||||
|
|
||||||
REM 停止可能存在的 n8n 相关进程
|
|
||||||
taskkill /F /IM node.exe /FI "WINDOWTITLE eq *n8n*" >nul 2>&1
|
|
||||||
taskkill /F /IM node.exe /FI "WINDOWTITLE eq *pnpm*" >nul 2>&1
|
|
||||||
taskkill /F /IM node.exe /FI "WINDOWTITLE eq *npm*" >nul 2>&1
|
|
||||||
|
|
||||||
REM 等待
|
|
||||||
timeout /t 2 /nobreak >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
ping -n 3 127.0.0.1 >nul
|
|
||||||
)
|
|
||||||
|
|
||||||
echo [成功] 已停止现有进程
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:setup_environment
|
|
||||||
echo [信息] 正在设置环境变量...
|
|
||||||
|
|
||||||
set "N8N_DEFAULT_LOCALE=zh-CN"
|
|
||||||
set "N8N_SECURE_COOKIE=false"
|
|
||||||
set "DB_SQLITE_POOL_SIZE=5"
|
|
||||||
set "N8N_RUNNERS_ENABLED=true"
|
|
||||||
set "N8N_BLOCK_ENV_ACCESS_IN_NODE=false"
|
|
||||||
set "NODE_OPTIONS=--max-old-space-size=4096"
|
|
||||||
|
|
||||||
echo [成功] 环境变量已设置
|
|
||||||
echo - N8N_DEFAULT_LOCALE=zh-CN
|
|
||||||
echo - N8N_SECURE_COOKIE=false
|
|
||||||
echo - DB_SQLITE_POOL_SIZE=5
|
|
||||||
echo - NODE_OPTIONS=--max-old-space-size=4096
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:check_build
|
|
||||||
if not exist "packages\cli\dist\" goto need_build
|
|
||||||
if not exist "packages\core\dist\" goto need_build
|
|
||||||
if not exist "packages\workflow\dist\" goto need_build
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:need_build
|
|
||||||
echo [警告] 检测到项目需要构建
|
|
||||||
set /p "build_project=是否现在构建项目?这可能需要几分钟时间 (y/N): "
|
|
||||||
if /I "!build_project!"=="y" (
|
|
||||||
call :build_project
|
|
||||||
)
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:build_project
|
|
||||||
echo [信息] 正在构建项目...
|
|
||||||
if defined USE_NPM (
|
|
||||||
call npm run build > build.log 2>&1
|
|
||||||
) else (
|
|
||||||
call pnpm build > build.log 2>&1
|
|
||||||
)
|
|
||||||
if errorlevel 1 (
|
|
||||||
echo [错误] 构建失败,请查看 build.log
|
|
||||||
type build.log | more
|
|
||||||
exit /b 1
|
|
||||||
)
|
|
||||||
echo [成功] 项目构建完成
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:start_n8n
|
|
||||||
echo [信息] 正在启动 n8n 中文版...
|
|
||||||
|
|
||||||
REM 创建日志文件名(兼容不同日期格式)
|
|
||||||
set "LOG_FILE=n8n-%date:~0,4%%date:~5,2%%date:~8,2%-%time:~0,2%%time:~3,2%.log"
|
|
||||||
set "LOG_FILE=%LOG_FILE: =0%"
|
|
||||||
|
|
||||||
echo [信息] 正在启动 n8n 服务...
|
|
||||||
echo [信息] 日志文件: %LOG_FILE%
|
|
||||||
|
|
||||||
REM 启动 n8n
|
|
||||||
if defined USE_NPM (
|
|
||||||
start "n8n Server" /B cmd /c "npm start > %LOG_FILE% 2>&1"
|
|
||||||
) else (
|
|
||||||
start "n8n Server" /B cmd /c "pnpm start > %LOG_FILE% 2>&1"
|
|
||||||
)
|
|
||||||
|
|
||||||
REM 等待启动
|
|
||||||
echo 等待 n8n 启动
|
|
||||||
set "count=0"
|
|
||||||
:wait_loop
|
|
||||||
set /a count+=1
|
|
||||||
if %count% gtr 60 goto start_failed
|
|
||||||
|
|
||||||
REM 检查端口是否开启
|
|
||||||
netstat -an 2>nul | findstr ":5678" | findstr "LISTENING" >nul 2>&1
|
|
||||||
if not errorlevel 1 goto start_success
|
|
||||||
|
|
||||||
REM 显示进度
|
|
||||||
set /p "=." <nul
|
|
||||||
timeout /t 1 /nobreak >nul 2>&1
|
|
||||||
if errorlevel 1 (
|
|
||||||
ping -n 2 127.0.0.1 >nul
|
|
||||||
)
|
|
||||||
goto wait_loop
|
|
||||||
|
|
||||||
:start_success
|
|
||||||
echo.
|
|
||||||
echo [成功] n8n 启动成功!
|
|
||||||
echo.
|
|
||||||
echo ========== 启动信息 ==========
|
|
||||||
echo 访问地址: http://localhost:5678
|
|
||||||
echo 界面语言: 中文 (zh-CN)
|
|
||||||
echo 日志文件: %LOG_FILE%
|
|
||||||
echo =============================
|
|
||||||
echo.
|
|
||||||
echo [信息] 使用 Ctrl+C 停止服务
|
|
||||||
echo [提示] 首次访问需要设置账户
|
|
||||||
echo.
|
|
||||||
pause
|
|
||||||
exit /b 0
|
|
||||||
|
|
||||||
:start_failed
|
|
||||||
echo.
|
|
||||||
echo [错误] n8n 启动失败
|
|
||||||
echo [信息] 查看日志文件: %LOG_FILE%
|
|
||||||
if exist "%LOG_FILE%" (
|
|
||||||
echo.
|
|
||||||
echo ===== 错误日志 =====
|
|
||||||
type "%LOG_FILE%" | more
|
|
||||||
)
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:show_help
|
|
||||||
echo 用法: %~nx0 [选项]
|
|
||||||
echo.
|
|
||||||
echo 选项:
|
|
||||||
echo -h, --help 显示此帮助信息
|
|
||||||
echo -d, --dev 使用开发模式启动 (支持热重载)
|
|
||||||
echo -b, --build 强制重新构建项目
|
|
||||||
echo -c, --check 仅检查系统状态,不启动
|
|
||||||
echo --skip-deps 跳过依赖检查
|
|
||||||
echo --npm 使用 npm 替代 pnpm
|
|
||||||
echo.
|
|
||||||
echo 示例:
|
|
||||||
echo %~nx0 # 正常启动
|
|
||||||
echo %~nx0 -d # 开发模式启动
|
|
||||||
echo %~nx0 -b # 重新构建并启动
|
|
||||||
echo %~nx0 --npm # 使用 npm 启动
|
|
||||||
echo.
|
|
||||||
echo 故障排除:
|
|
||||||
echo 1. 如果 pnpm 安装失败,使用 --npm 选项
|
|
||||||
echo 2. 如果端口被占用,脚本会提示终止进程
|
|
||||||
echo 3. 构建失败时查看 build.log 文件
|
|
||||||
echo.
|
|
||||||
goto end
|
|
||||||
|
|
||||||
:error_exit
|
|
||||||
echo [错误] 脚本执行失败
|
|
||||||
echo.
|
|
||||||
echo 常见问题解决:
|
|
||||||
echo 1. 确保 Node.js 版本为 18.x 或 20.x
|
|
||||||
echo 2. 尝试使用管理员权限运行
|
|
||||||
echo 3. 使用 --npm 选项如果 pnpm 有问题
|
|
||||||
echo 4. 检查网络连接和代理设置
|
|
||||||
echo.
|
|
||||||
pause
|
|
||||||
exit /b 1
|
|
||||||
|
|
||||||
:end
|
|
||||||
endlocal
|
|
||||||
exit /b 0
|
|
||||||
Reference in New Issue
Block a user