@starpak/config 1.0.5 → 1.0.6
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/lib/errors.d.ts +13 -0
- package/lib/errors.d.ts.map +1 -0
- package/lib/errors.js +27 -0
- package/lib/errors.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +37 -9
- package/lib/index.js.map +1 -1
- package/package.json +3 -2
- package/src/errors.ts +30 -0
- package/src/index.ts +33 -10
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { SpakError, type FormatKind } from '@starpak/error';
|
|
2
|
+
export interface ConfigErrorOptions {
|
|
3
|
+
cause?: unknown;
|
|
4
|
+
where?: string;
|
|
5
|
+
context?: Record<string, unknown>;
|
|
6
|
+
}
|
|
7
|
+
/** 构造一个带错误码的配置错误,message 保留本次发生的具体细节。 */
|
|
8
|
+
export declare function spakError(code: string, message: string, options?: ConfigErrorOptions): SpakError;
|
|
9
|
+
/** 任意异常归一成 `SpakError`,保留原始 cause。 */
|
|
10
|
+
export declare function normalizeError(error: unknown, code?: string, message?: string): SpakError;
|
|
11
|
+
/** 渲染成文本 / JSON / HTML / Markdown;调用方决定往哪儿写。 */
|
|
12
|
+
export declare function renderError(error: unknown, kind?: FormatKind): string;
|
|
13
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAMA,OAAO,EAAS,SAAS,EAAuB,KAAK,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAEvF,MAAM,WAAW,kBAAkB;IACjC,KAAK,CAAC,EAAE,OAAO,CAAA;IACf,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAClC;AAED,yCAAyC;AACzC,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,kBAAuB,GAAG,SAAS,CAEpG;AAED,sCAAsC;AACtC,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,MAA6B,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAI/G;AAED,gDAAgD;AAChD,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,GAAE,UAAmB,GAAG,MAAM,CAE7E"}
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ===== config — @starpak/error bridge =====
|
|
3
|
+
//
|
|
4
|
+
// 配置文件读写失败一律带稳定错误码(SPAK-xxxx)。抛出用 `spakError()`,
|
|
5
|
+
// 输出用 `renderError()`(内部只走 `format()`),这样 CLI / 日志 / SpakOS
|
|
6
|
+
// 面板拿到的是同一份 `ErrorInfo`。
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.spakError = spakError;
|
|
9
|
+
exports.normalizeError = normalizeError;
|
|
10
|
+
exports.renderError = renderError;
|
|
11
|
+
const error_1 = require("@starpak/error");
|
|
12
|
+
/** 构造一个带错误码的配置错误,message 保留本次发生的具体细节。 */
|
|
13
|
+
function spakError(code, message, options = {}) {
|
|
14
|
+
return new error_1.SpakError(code, { message, ...options });
|
|
15
|
+
}
|
|
16
|
+
/** 任意异常归一成 `SpakError`,保留原始 cause。 */
|
|
17
|
+
function normalizeError(error, code = error_1.CODES.CONFIG_INVALID, message) {
|
|
18
|
+
if ((0, error_1.isSpakError)(error))
|
|
19
|
+
return error;
|
|
20
|
+
const cause = error instanceof Error ? error : new Error(String(error));
|
|
21
|
+
return new error_1.SpakError(code, { message: message ?? cause.message, cause });
|
|
22
|
+
}
|
|
23
|
+
/** 渲染成文本 / JSON / HTML / Markdown;调用方决定往哪儿写。 */
|
|
24
|
+
function renderError(error, kind = 'text') {
|
|
25
|
+
return (0, error_1.format)(normalizeError(error).toInfo(), kind);
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":";AAAA,6CAA6C;AAC7C,EAAE;AACF,iDAAiD;AACjD,4DAA4D;AAC5D,yBAAyB;;AAWzB,8BAEC;AAGD,wCAIC;AAGD,kCAEC;AAvBD,0CAAuF;AAQvF,yCAAyC;AACzC,SAAgB,SAAS,CAAC,IAAY,EAAE,OAAe,EAAE,UAA8B,EAAE;IACvF,OAAO,IAAI,iBAAS,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,GAAG,OAAO,EAAE,CAAC,CAAA;AACrD,CAAC;AAED,sCAAsC;AACtC,SAAgB,cAAc,CAAC,KAAc,EAAE,OAAe,aAAK,CAAC,cAAc,EAAE,OAAgB;IAClG,IAAI,IAAA,mBAAW,EAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAA;IACpC,MAAM,KAAK,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAA;IACvE,OAAO,IAAI,iBAAS,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;AAC1E,CAAC;AAED,gDAAgD;AAChD,SAAgB,WAAW,CAAC,KAAc,EAAE,OAAmB,MAAM;IACnE,OAAO,IAAA,cAAM,EAAC,cAAc,CAAC,KAAK,CAAC,CAAC,MAAM,EAAE,EAAE,IAAI,CAAC,CAAA;AACrD,CAAC"}
|
package/lib/index.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare function loadConfig(): SpakConfig;
|
|
|
19
19
|
export declare function saveConfig(config: SpakConfig): void;
|
|
20
20
|
export declare function getConfig(key: string): any;
|
|
21
21
|
export declare function setConfig(key: string, value: any): SpakConfig;
|
|
22
|
+
export { renderError, spakError } from './errors';
|
|
22
23
|
declare const _default: {
|
|
23
24
|
loadConfig: typeof loadConfig;
|
|
24
25
|
saveConfig: typeof saveConfig;
|
package/lib/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAUA,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAA;IAC5B,GAAG,CAAC,EAAE;QACJ,OAAO,CAAC,EAAE,OAAO,CAAA;QACjB,OAAO,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;QAC/B,gBAAgB,CAAC,EAAE;YAAE,OAAO,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;QACxC,MAAM,CAAC,EAAE;YACP,OAAO,CAAC,EAAE,OAAO,CAAA;YACjB,qGAAqG;YACrG,aAAa,CAAC,EAAE,MAAM,CAAA;SACvB,CAAA;KACF,CAAA;CACF;AAwBD,wBAAgB,UAAU,IAAI,UAAU,CAgBvC;AAED,wBAAgB,UAAU,CAAC,MAAM,EAAE,UAAU,GAAG,IAAI,CAwBnD;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAS1C;AAED,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,GAAG,GAAG,UAAU,CAW7D;AAED,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,UAAU,CAAA;;;;;;;AACjD,wBAA+D"}
|
package/lib/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spakError = exports.renderError = void 0;
|
|
3
4
|
exports.loadConfig = loadConfig;
|
|
4
5
|
exports.saveConfig = saveConfig;
|
|
5
6
|
exports.getConfig = getConfig;
|
|
@@ -7,16 +8,32 @@ exports.setConfig = setConfig;
|
|
|
7
8
|
const fs_1 = require("fs");
|
|
8
9
|
const path_1 = require("path");
|
|
9
10
|
const util_1 = require("@starpak/util");
|
|
11
|
+
const error_1 = require("@starpak/error");
|
|
12
|
+
const errors_1 = require("./errors");
|
|
10
13
|
// 数据目录 = 项目源码根下的 data/(尊重 SPAK_DATA_DIR 覆盖,不写 ~/.spak)
|
|
11
14
|
const CONFIG_DIR = (0, util_1.projectDataDir)();
|
|
12
15
|
const CONFIG_FILE = (0, path_1.resolve)(CONFIG_DIR, 'config.json');
|
|
13
16
|
const defaultConfig = {
|
|
14
17
|
plugins: {},
|
|
15
18
|
};
|
|
19
|
+
/** 调试时把配置错误按统一格式打到 stderr(只走 format,不裸写 message)。 */
|
|
20
|
+
function diagnose(error) {
|
|
21
|
+
if (!process.env.SPAK_DEBUG)
|
|
22
|
+
return;
|
|
23
|
+
process.stderr.write((0, errors_1.renderError)(error) + '\n');
|
|
24
|
+
}
|
|
16
25
|
function ensureConfigDir() {
|
|
17
|
-
if (
|
|
26
|
+
if ((0, fs_1.existsSync)(CONFIG_DIR))
|
|
27
|
+
return;
|
|
28
|
+
try {
|
|
18
29
|
(0, fs_1.mkdirSync)(CONFIG_DIR, { recursive: true });
|
|
19
30
|
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
throw (0, errors_1.spakError)(error_1.CODES.OUTPUT_NOT_WRITABLE, `无法创建配置目录:${CONFIG_DIR}`, {
|
|
33
|
+
cause: error,
|
|
34
|
+
where: CONFIG_DIR,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
20
37
|
}
|
|
21
38
|
function loadConfig() {
|
|
22
39
|
ensureConfigDir();
|
|
@@ -28,7 +45,11 @@ function loadConfig() {
|
|
|
28
45
|
const data = (0, fs_1.readFileSync)(CONFIG_FILE, 'utf-8');
|
|
29
46
|
return { ...defaultConfig, ...JSON.parse(data) };
|
|
30
47
|
}
|
|
31
|
-
catch {
|
|
48
|
+
catch (error) {
|
|
49
|
+
diagnose((0, errors_1.spakError)(error_1.CODES.CONFIG_INVALID, `配置读取失败,已回退默认值:${CONFIG_FILE}`, {
|
|
50
|
+
cause: error,
|
|
51
|
+
where: CONFIG_FILE,
|
|
52
|
+
}));
|
|
32
53
|
return { ...defaultConfig };
|
|
33
54
|
}
|
|
34
55
|
}
|
|
@@ -43,19 +64,23 @@ function saveConfig(config) {
|
|
|
43
64
|
// rename() is atomic on POSIX filesystems; on Windows we fallback to
|
|
44
65
|
// replace via copy+unlink if rename fails.
|
|
45
66
|
try {
|
|
46
|
-
|
|
67
|
+
(0, fs_1.renameSync)(tmpFile, CONFIG_FILE);
|
|
47
68
|
}
|
|
48
|
-
catch {
|
|
49
|
-
|
|
50
|
-
|
|
69
|
+
catch (error) {
|
|
70
|
+
diagnose((0, errors_1.spakError)(error_1.CODES.FS_RENAME_UNSUPPORTED, `rename 失败,改用 copy+unlink:${tmpFile}`, { cause: error }));
|
|
71
|
+
(0, fs_1.copyFileSync)(tmpFile, CONFIG_FILE);
|
|
72
|
+
(0, fs_1.unlinkSync)(tmpFile);
|
|
51
73
|
}
|
|
52
74
|
}
|
|
53
|
-
catch (
|
|
75
|
+
catch (error) {
|
|
54
76
|
try {
|
|
55
|
-
|
|
77
|
+
(0, fs_1.unlinkSync)(tmpFile);
|
|
56
78
|
}
|
|
57
79
|
catch { /* ignore */ }
|
|
58
|
-
throw
|
|
80
|
+
throw (0, errors_1.spakError)(error_1.CODES.OUTPUT_NOT_WRITABLE, `配置写入失败:${CONFIG_FILE}`, {
|
|
81
|
+
cause: error,
|
|
82
|
+
where: CONFIG_FILE,
|
|
83
|
+
});
|
|
59
84
|
}
|
|
60
85
|
}
|
|
61
86
|
function getConfig(key) {
|
|
@@ -82,5 +107,8 @@ function setConfig(key, value) {
|
|
|
82
107
|
saveConfig(config);
|
|
83
108
|
return config;
|
|
84
109
|
}
|
|
110
|
+
var errors_2 = require("./errors");
|
|
111
|
+
Object.defineProperty(exports, "renderError", { enumerable: true, get: function () { return errors_2.renderError; } });
|
|
112
|
+
Object.defineProperty(exports, "spakError", { enumerable: true, get: function () { return errors_2.spakError; } });
|
|
85
113
|
exports.default = { loadConfig, saveConfig, getConfig, setConfig };
|
|
86
114
|
//# sourceMappingURL=index.js.map
|
package/lib/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@starpak/config",
|
|
3
3
|
"description": "Spak Config - Central configuration manager",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.6",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"exports": {
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"author": "Spak Team",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@starpak/util": "^1.0.5"
|
|
23
|
+
"@starpak/util": "^1.0.5",
|
|
24
|
+
"@starpak/error": "^0.1.4"
|
|
24
25
|
}
|
|
25
26
|
}
|
package/src/errors.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// ===== config — @starpak/error bridge =====
|
|
2
|
+
//
|
|
3
|
+
// 配置文件读写失败一律带稳定错误码(SPAK-xxxx)。抛出用 `spakError()`,
|
|
4
|
+
// 输出用 `renderError()`(内部只走 `format()`),这样 CLI / 日志 / SpakOS
|
|
5
|
+
// 面板拿到的是同一份 `ErrorInfo`。
|
|
6
|
+
|
|
7
|
+
import { CODES, SpakError, format, isSpakError, type FormatKind } from '@starpak/error'
|
|
8
|
+
|
|
9
|
+
export interface ConfigErrorOptions {
|
|
10
|
+
cause?: unknown
|
|
11
|
+
where?: string
|
|
12
|
+
context?: Record<string, unknown>
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 构造一个带错误码的配置错误,message 保留本次发生的具体细节。 */
|
|
16
|
+
export function spakError(code: string, message: string, options: ConfigErrorOptions = {}): SpakError {
|
|
17
|
+
return new SpakError(code, { message, ...options })
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 任意异常归一成 `SpakError`,保留原始 cause。 */
|
|
21
|
+
export function normalizeError(error: unknown, code: string = CODES.CONFIG_INVALID, message?: string): SpakError {
|
|
22
|
+
if (isSpakError(error)) return error
|
|
23
|
+
const cause = error instanceof Error ? error : new Error(String(error))
|
|
24
|
+
return new SpakError(code, { message: message ?? cause.message, cause })
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 渲染成文本 / JSON / HTML / Markdown;调用方决定往哪儿写。 */
|
|
28
|
+
export function renderError(error: unknown, kind: FormatKind = 'text'): string {
|
|
29
|
+
return format(normalizeError(error).toInfo(), kind)
|
|
30
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'
|
|
1
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync, renameSync, copyFileSync, unlinkSync } from 'fs'
|
|
2
2
|
import { resolve } from 'path'
|
|
3
3
|
import { projectDataDir } from '@starpak/util'
|
|
4
|
+
import { CODES } from '@starpak/error'
|
|
5
|
+
import { renderError, spakError } from './errors'
|
|
4
6
|
|
|
5
7
|
// 数据目录 = 项目源码根下的 data/(尊重 SPAK_DATA_DIR 覆盖,不写 ~/.spak)
|
|
6
8
|
const CONFIG_DIR = projectDataDir()
|
|
@@ -24,9 +26,21 @@ const defaultConfig: SpakConfig = {
|
|
|
24
26
|
plugins: {},
|
|
25
27
|
}
|
|
26
28
|
|
|
29
|
+
/** 调试时把配置错误按统一格式打到 stderr(只走 format,不裸写 message)。 */
|
|
30
|
+
function diagnose(error: unknown) {
|
|
31
|
+
if (!process.env.SPAK_DEBUG) return
|
|
32
|
+
process.stderr.write(renderError(error) + '\n')
|
|
33
|
+
}
|
|
34
|
+
|
|
27
35
|
function ensureConfigDir() {
|
|
28
|
-
if (
|
|
36
|
+
if (existsSync(CONFIG_DIR)) return
|
|
37
|
+
try {
|
|
29
38
|
mkdirSync(CONFIG_DIR, { recursive: true })
|
|
39
|
+
} catch (error) {
|
|
40
|
+
throw spakError(CODES.OUTPUT_NOT_WRITABLE, `无法创建配置目录:${CONFIG_DIR}`, {
|
|
41
|
+
cause: error,
|
|
42
|
+
where: CONFIG_DIR,
|
|
43
|
+
})
|
|
30
44
|
}
|
|
31
45
|
}
|
|
32
46
|
|
|
@@ -39,7 +53,11 @@ export function loadConfig(): SpakConfig {
|
|
|
39
53
|
try {
|
|
40
54
|
const data = readFileSync(CONFIG_FILE, 'utf-8')
|
|
41
55
|
return { ...defaultConfig, ...JSON.parse(data) }
|
|
42
|
-
} catch {
|
|
56
|
+
} catch (error) {
|
|
57
|
+
diagnose(spakError(CODES.CONFIG_INVALID, `配置读取失败,已回退默认值:${CONFIG_FILE}`, {
|
|
58
|
+
cause: error,
|
|
59
|
+
where: CONFIG_FILE,
|
|
60
|
+
}))
|
|
43
61
|
return { ...defaultConfig }
|
|
44
62
|
}
|
|
45
63
|
}
|
|
@@ -55,14 +73,18 @@ export function saveConfig(config: SpakConfig): void {
|
|
|
55
73
|
// rename() is atomic on POSIX filesystems; on Windows we fallback to
|
|
56
74
|
// replace via copy+unlink if rename fails.
|
|
57
75
|
try {
|
|
58
|
-
|
|
59
|
-
} catch {
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
renameSync(tmpFile, CONFIG_FILE)
|
|
77
|
+
} catch (error) {
|
|
78
|
+
diagnose(spakError(CODES.FS_RENAME_UNSUPPORTED, `rename 失败,改用 copy+unlink:${tmpFile}`, { cause: error }))
|
|
79
|
+
copyFileSync(tmpFile, CONFIG_FILE)
|
|
80
|
+
unlinkSync(tmpFile)
|
|
62
81
|
}
|
|
63
|
-
} catch (
|
|
64
|
-
try {
|
|
65
|
-
throw
|
|
82
|
+
} catch (error) {
|
|
83
|
+
try { unlinkSync(tmpFile) } catch { /* ignore */ }
|
|
84
|
+
throw spakError(CODES.OUTPUT_NOT_WRITABLE, `配置写入失败:${CONFIG_FILE}`, {
|
|
85
|
+
cause: error,
|
|
86
|
+
where: CONFIG_FILE,
|
|
87
|
+
})
|
|
66
88
|
}
|
|
67
89
|
}
|
|
68
90
|
|
|
@@ -90,4 +112,5 @@ export function setConfig(key: string, value: any): SpakConfig {
|
|
|
90
112
|
return config
|
|
91
113
|
}
|
|
92
114
|
|
|
115
|
+
export { renderError, spakError } from './errors'
|
|
93
116
|
export default { loadConfig, saveConfig, getConfig, setConfig }
|