miaoda-game-devkit 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/bin/miaoda-game-lint.js +3 -0
- package/biome-config.json +24 -0
- package/dist/cli/lint.js +93 -0
- package/dist/index.d.mts +111 -0
- package/dist/index.d.ts +111 -0
- package/dist/index.js +827 -0
- package/dist/index.mjs +785 -0
- package/dist/lint/setup.mjs +125 -0
- package/dist/rules/check-image-import-plugin.js +92 -0
- package/dist/rules/check-style-import-plugin.js +92 -0
- package/dist/vitest-config.d.mts +23 -0
- package/dist/vitest-config.d.ts +23 -0
- package/dist/vitest-config.js +83 -0
- package/dist/vitest-config.mjs +60 -0
- package/oxlint-config.json +14 -0
- package/package.json +89 -0
- package/tsconfig-base.json +22 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// src/lint/setup.ts
|
|
2
|
+
import { afterEach } from "vitest";
|
|
3
|
+
var contexts = /* @__PURE__ */ new WeakMap();
|
|
4
|
+
function createCanvasContext(canvas) {
|
|
5
|
+
const imageData = (width = 1, height = 1) => ({
|
|
6
|
+
data: new Uint8ClampedArray(width * height * 4),
|
|
7
|
+
width,
|
|
8
|
+
height,
|
|
9
|
+
colorSpace: "srgb"
|
|
10
|
+
});
|
|
11
|
+
const state = {
|
|
12
|
+
canvas,
|
|
13
|
+
font: "16px sans-serif"
|
|
14
|
+
};
|
|
15
|
+
const context = new Proxy(
|
|
16
|
+
state,
|
|
17
|
+
{
|
|
18
|
+
get(target, property) {
|
|
19
|
+
if (property in target) {
|
|
20
|
+
return target[property];
|
|
21
|
+
}
|
|
22
|
+
if (property === "createImageData" || property === "getImageData") {
|
|
23
|
+
return (...args) => imageData(args.at(-2), args.at(-1));
|
|
24
|
+
}
|
|
25
|
+
if (property === "measureText") {
|
|
26
|
+
return (value) => {
|
|
27
|
+
const font = String(target.font ?? "16px sans-serif");
|
|
28
|
+
const size = Number.parseFloat(font.match(/([\d.]+)px/)?.[1] ?? "16");
|
|
29
|
+
const width = [...String(value)].reduce((total, character) => {
|
|
30
|
+
if (/\s/.test(character)) return total + size * 0.33;
|
|
31
|
+
if (/[^\u0000-\u00ff]/.test(character)) return total + size;
|
|
32
|
+
if (/[MW@#%&]/.test(character)) return total + size * 0.9;
|
|
33
|
+
if (/[ilI1.,'`]/.test(character)) return total + size * 0.32;
|
|
34
|
+
if (/[A-Z0-9]/.test(character)) return total + size * 0.64;
|
|
35
|
+
return total + size * 0.56;
|
|
36
|
+
}, 0);
|
|
37
|
+
return {
|
|
38
|
+
width,
|
|
39
|
+
actualBoundingBoxAscent: size * 0.8,
|
|
40
|
+
actualBoundingBoxDescent: size * 0.2
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
if (property === "createLinearGradient" || property === "createRadialGradient") {
|
|
45
|
+
return () => ({ addColorStop() {
|
|
46
|
+
} });
|
|
47
|
+
}
|
|
48
|
+
return () => void 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
return context;
|
|
53
|
+
}
|
|
54
|
+
HTMLCanvasElement.prototype.getContext = function getContext(contextId) {
|
|
55
|
+
if (contextId !== "2d") {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
58
|
+
const existing = contexts.get(this);
|
|
59
|
+
if (existing) {
|
|
60
|
+
return existing;
|
|
61
|
+
}
|
|
62
|
+
const context = createCanvasContext(this);
|
|
63
|
+
contexts.set(this, context);
|
|
64
|
+
return context;
|
|
65
|
+
};
|
|
66
|
+
var HeadlessImage = class {
|
|
67
|
+
onerror = null;
|
|
68
|
+
onload = null;
|
|
69
|
+
width = 2;
|
|
70
|
+
height = 2;
|
|
71
|
+
naturalWidth = 2;
|
|
72
|
+
naturalHeight = 2;
|
|
73
|
+
complete = false;
|
|
74
|
+
source = "";
|
|
75
|
+
get src() {
|
|
76
|
+
return this.source;
|
|
77
|
+
}
|
|
78
|
+
set src(value) {
|
|
79
|
+
this.source = value;
|
|
80
|
+
this.complete = true;
|
|
81
|
+
queueMicrotask(() => {
|
|
82
|
+
if (value.startsWith("data:image/")) {
|
|
83
|
+
this.onload?.(new Event("load"));
|
|
84
|
+
} else {
|
|
85
|
+
this.onerror?.(
|
|
86
|
+
new Event("error"),
|
|
87
|
+
"Image loading is unavailable in JSDOM",
|
|
88
|
+
0,
|
|
89
|
+
0,
|
|
90
|
+
void 0
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
globalThis.Image = HeadlessImage;
|
|
97
|
+
var storageValues = /* @__PURE__ */ new Map();
|
|
98
|
+
var headlessStorage = {
|
|
99
|
+
get length() {
|
|
100
|
+
return storageValues.size;
|
|
101
|
+
},
|
|
102
|
+
clear() {
|
|
103
|
+
storageValues.clear();
|
|
104
|
+
},
|
|
105
|
+
getItem(key) {
|
|
106
|
+
return storageValues.get(key) ?? null;
|
|
107
|
+
},
|
|
108
|
+
key(index) {
|
|
109
|
+
return [...storageValues.keys()][index] ?? null;
|
|
110
|
+
},
|
|
111
|
+
removeItem(key) {
|
|
112
|
+
storageValues.delete(key);
|
|
113
|
+
},
|
|
114
|
+
setItem(key, value) {
|
|
115
|
+
storageValues.set(key, value);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
Object.defineProperty(globalThis, "localStorage", {
|
|
119
|
+
configurable: true,
|
|
120
|
+
value: headlessStorage
|
|
121
|
+
});
|
|
122
|
+
afterEach(() => {
|
|
123
|
+
document.body.replaceChildren();
|
|
124
|
+
headlessStorage.clear();
|
|
125
|
+
});
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/rules/check-image-import-plugin.ts
|
|
31
|
+
var check_image_import_plugin_exports = {};
|
|
32
|
+
__export(check_image_import_plugin_exports, {
|
|
33
|
+
default: () => check_image_import_plugin_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(check_image_import_plugin_exports);
|
|
36
|
+
var import_node_fs = __toESM(require("fs"));
|
|
37
|
+
var import_node_path = __toESM(require("path"));
|
|
38
|
+
var import_oxc_resolver = require("oxc-resolver");
|
|
39
|
+
var IMAGE_EXTENSIONS = /\.(png|jpe?g|gif|webp|svg|bmp|ico)$/i;
|
|
40
|
+
var resolverCache = /* @__PURE__ */ new Map();
|
|
41
|
+
function getResolver(projectRoot) {
|
|
42
|
+
if (!resolverCache.has(projectRoot)) {
|
|
43
|
+
resolverCache.set(projectRoot, new import_oxc_resolver.ResolverFactory({
|
|
44
|
+
extensions: [".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico"],
|
|
45
|
+
conditionNames: ["import", "require", "node", "default"],
|
|
46
|
+
tsconfig: {
|
|
47
|
+
configFile: import_node_path.default.join(projectRoot, "tsconfig.json"),
|
|
48
|
+
references: "auto"
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return resolverCache.get(projectRoot);
|
|
53
|
+
}
|
|
54
|
+
var checkImageImportRule = {
|
|
55
|
+
meta: {
|
|
56
|
+
type: "problem",
|
|
57
|
+
docs: { description: "Check if imported image files exist" },
|
|
58
|
+
messages: {
|
|
59
|
+
imageNotFound: "Image file '{{source}}' does not exist or cannot be resolved"
|
|
60
|
+
},
|
|
61
|
+
schema: []
|
|
62
|
+
},
|
|
63
|
+
create(context) {
|
|
64
|
+
let projectRoot = import_node_path.default.dirname(context.filename);
|
|
65
|
+
while (projectRoot !== import_node_path.default.dirname(projectRoot)) {
|
|
66
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(projectRoot, "package.json"))) break;
|
|
67
|
+
projectRoot = import_node_path.default.dirname(projectRoot);
|
|
68
|
+
}
|
|
69
|
+
const resolver = getResolver(projectRoot);
|
|
70
|
+
const currentDir = import_node_path.default.dirname(context.filename);
|
|
71
|
+
return {
|
|
72
|
+
ImportDeclaration(node) {
|
|
73
|
+
const source = node.source.value;
|
|
74
|
+
if (typeof source !== "string" || !IMAGE_EXTENSIONS.test(source)) return;
|
|
75
|
+
try {
|
|
76
|
+
const resolved = resolver.sync(currentDir, source);
|
|
77
|
+
if (!resolved.path || !import_node_fs.default.existsSync(resolved.path)) {
|
|
78
|
+
context.report({ node: node.source, messageId: "imageNotFound", data: { source } });
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
context.report({ node: node.source, messageId: "imageNotFound", data: { source } });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var plugin = {
|
|
88
|
+
meta: { name: "check-image-exists" },
|
|
89
|
+
rules: { "no-missing-image": checkImageImportRule }
|
|
90
|
+
};
|
|
91
|
+
var check_image_import_plugin_default = plugin;
|
|
92
|
+
module.exports = module.exports.default;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/rules/check-style-import-plugin.ts
|
|
31
|
+
var check_style_import_plugin_exports = {};
|
|
32
|
+
__export(check_style_import_plugin_exports, {
|
|
33
|
+
default: () => check_style_import_plugin_default
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(check_style_import_plugin_exports);
|
|
36
|
+
var import_node_fs = __toESM(require("fs"));
|
|
37
|
+
var import_node_path = __toESM(require("path"));
|
|
38
|
+
var import_oxc_resolver = require("oxc-resolver");
|
|
39
|
+
var STYLE_EXTENSIONS = /\.(css|scss|sass|less|styl)$/i;
|
|
40
|
+
var resolverCache = /* @__PURE__ */ new Map();
|
|
41
|
+
function getResolver(projectRoot) {
|
|
42
|
+
if (!resolverCache.has(projectRoot)) {
|
|
43
|
+
resolverCache.set(projectRoot, new import_oxc_resolver.ResolverFactory({
|
|
44
|
+
extensions: [".css", ".scss", ".sass", ".less", ".styl"],
|
|
45
|
+
conditionNames: ["import", "require", "node", "default"],
|
|
46
|
+
tsconfig: {
|
|
47
|
+
configFile: import_node_path.default.join(projectRoot, "tsconfig.json"),
|
|
48
|
+
references: "auto"
|
|
49
|
+
}
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
return resolverCache.get(projectRoot);
|
|
53
|
+
}
|
|
54
|
+
var checkStyleImportRule = {
|
|
55
|
+
meta: {
|
|
56
|
+
type: "problem",
|
|
57
|
+
docs: { description: "Check if imported style files exist" },
|
|
58
|
+
messages: {
|
|
59
|
+
styleNotFound: "Style file '{{source}}' does not exist or cannot be resolved"
|
|
60
|
+
},
|
|
61
|
+
schema: []
|
|
62
|
+
},
|
|
63
|
+
create(context) {
|
|
64
|
+
let projectRoot = import_node_path.default.dirname(context.filename);
|
|
65
|
+
while (projectRoot !== import_node_path.default.dirname(projectRoot)) {
|
|
66
|
+
if (import_node_fs.default.existsSync(import_node_path.default.join(projectRoot, "package.json"))) break;
|
|
67
|
+
projectRoot = import_node_path.default.dirname(projectRoot);
|
|
68
|
+
}
|
|
69
|
+
const resolver = getResolver(projectRoot);
|
|
70
|
+
const currentDir = import_node_path.default.dirname(context.filename);
|
|
71
|
+
return {
|
|
72
|
+
ImportDeclaration(node) {
|
|
73
|
+
const source = node.source.value;
|
|
74
|
+
if (typeof source !== "string" || !STYLE_EXTENSIONS.test(source)) return;
|
|
75
|
+
try {
|
|
76
|
+
const resolved = resolver.sync(currentDir, source);
|
|
77
|
+
if (!resolved.path || !import_node_fs.default.existsSync(resolved.path)) {
|
|
78
|
+
context.report({ node: node.source, messageId: "styleNotFound", data: { source } });
|
|
79
|
+
}
|
|
80
|
+
} catch {
|
|
81
|
+
context.report({ node: node.source, messageId: "styleNotFound", data: { source } });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var plugin = {
|
|
88
|
+
meta: { name: "check-style-exists" },
|
|
89
|
+
rules: { "no-missing-style": checkStyleImportRule }
|
|
90
|
+
};
|
|
91
|
+
var check_style_import_plugin_default = plugin;
|
|
92
|
+
module.exports = module.exports.default;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
interface GameVitestConfigOptions {
|
|
4
|
+
/** 模板项目的绝对根目录,通常传入 `import.meta.dirname`。 */
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
/** 项目需要的额外路径别名;`@` 和 `phaser` 由 devkit 统一维护。 */
|
|
7
|
+
aliases?: Record<string, string>;
|
|
8
|
+
/** 在 devkit 基础 setup 之后执行的项目级 setup 文件。 */
|
|
9
|
+
additionalSetupFiles?: string[];
|
|
10
|
+
/** 单个游戏运行时测试的超时时间。 */
|
|
11
|
+
testTimeout?: number;
|
|
12
|
+
/** 单个游戏测试钩子的超时时间。 */
|
|
13
|
+
hookTimeout?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 创建妙搭 Phaser 游戏的 Vitest 配置。
|
|
17
|
+
*
|
|
18
|
+
* HEADLESS 环境、游戏测试范围、Scene coverage 和 mock 隔离均由 devkit
|
|
19
|
+
* 固定;模板只传入项目根目录及少量安全扩展。
|
|
20
|
+
*/
|
|
21
|
+
declare function defineGameVitestConfig(options: GameVitestConfigOptions): ViteUserConfig;
|
|
22
|
+
|
|
23
|
+
export { type GameVitestConfigOptions, defineGameVitestConfig };
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { ViteUserConfig } from 'vitest/config';
|
|
2
|
+
|
|
3
|
+
interface GameVitestConfigOptions {
|
|
4
|
+
/** 模板项目的绝对根目录,通常传入 `import.meta.dirname`。 */
|
|
5
|
+
projectRoot: string;
|
|
6
|
+
/** 项目需要的额外路径别名;`@` 和 `phaser` 由 devkit 统一维护。 */
|
|
7
|
+
aliases?: Record<string, string>;
|
|
8
|
+
/** 在 devkit 基础 setup 之后执行的项目级 setup 文件。 */
|
|
9
|
+
additionalSetupFiles?: string[];
|
|
10
|
+
/** 单个游戏运行时测试的超时时间。 */
|
|
11
|
+
testTimeout?: number;
|
|
12
|
+
/** 单个游戏测试钩子的超时时间。 */
|
|
13
|
+
hookTimeout?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* 创建妙搭 Phaser 游戏的 Vitest 配置。
|
|
17
|
+
*
|
|
18
|
+
* HEADLESS 环境、游戏测试范围、Scene coverage 和 mock 隔离均由 devkit
|
|
19
|
+
* 固定;模板只传入项目根目录及少量安全扩展。
|
|
20
|
+
*/
|
|
21
|
+
declare function defineGameVitestConfig(options: GameVitestConfigOptions): ViteUserConfig;
|
|
22
|
+
|
|
23
|
+
export { type GameVitestConfigOptions, defineGameVitestConfig };
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/vitest-config.ts
|
|
21
|
+
var vitest_config_exports = {};
|
|
22
|
+
__export(vitest_config_exports, {
|
|
23
|
+
defineGameVitestConfig: () => defineGameVitestConfig
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(vitest_config_exports);
|
|
26
|
+
var import_node_module = require("module");
|
|
27
|
+
var import_node_path = require("path");
|
|
28
|
+
var import_config = require("vitest/config");
|
|
29
|
+
function defineGameVitestConfig(options) {
|
|
30
|
+
const projectRequire = (0, import_node_module.createRequire)((0, import_node_path.resolve)(options.projectRoot, "package.json"));
|
|
31
|
+
const phaserPackageRoot = (0, import_node_path.dirname)(projectRequire.resolve("phaser/package.json"));
|
|
32
|
+
const phaserBrowserBuild = (0, import_node_path.resolve)(phaserPackageRoot, "dist/phaser.js");
|
|
33
|
+
return (0, import_config.defineConfig)({
|
|
34
|
+
resolve: {
|
|
35
|
+
alias: {
|
|
36
|
+
...options.aliases,
|
|
37
|
+
"@": (0, import_node_path.resolve)(options.projectRoot, "src"),
|
|
38
|
+
phaser: phaserBrowserBuild
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
test: {
|
|
42
|
+
include: ["tests/**/*.test.ts"],
|
|
43
|
+
testTimeout: options.testTimeout,
|
|
44
|
+
hookTimeout: options.hookTimeout,
|
|
45
|
+
server: {
|
|
46
|
+
deps: {
|
|
47
|
+
inline: [/miaoda-game-/]
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
environment: "jsdom",
|
|
51
|
+
environmentOptions: {
|
|
52
|
+
jsdom: {
|
|
53
|
+
url: "http://localhost/"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
setupFiles: [
|
|
57
|
+
"miaoda-game-devkit/vitest-setup",
|
|
58
|
+
...options.additionalSetupFiles ?? []
|
|
59
|
+
],
|
|
60
|
+
restoreMocks: true,
|
|
61
|
+
clearMocks: true,
|
|
62
|
+
coverage: {
|
|
63
|
+
provider: "v8",
|
|
64
|
+
include: ["src/scenes/**/*.{ts,tsx}"],
|
|
65
|
+
exclude: [
|
|
66
|
+
"src/scenes/**/*.d.ts",
|
|
67
|
+
"src/scenes/**/*.{spec,test}.{ts,tsx}"
|
|
68
|
+
],
|
|
69
|
+
reporter: ["text"],
|
|
70
|
+
thresholds: {
|
|
71
|
+
perFile: true,
|
|
72
|
+
lines: 1,
|
|
73
|
+
functions: 1,
|
|
74
|
+
statements: 1
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
81
|
+
0 && (module.exports = {
|
|
82
|
+
defineGameVitestConfig
|
|
83
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// src/vitest-config.ts
|
|
2
|
+
import { createRequire } from "module";
|
|
3
|
+
import { dirname, resolve } from "path";
|
|
4
|
+
import {
|
|
5
|
+
defineConfig
|
|
6
|
+
} from "vitest/config";
|
|
7
|
+
function defineGameVitestConfig(options) {
|
|
8
|
+
const projectRequire = createRequire(resolve(options.projectRoot, "package.json"));
|
|
9
|
+
const phaserPackageRoot = dirname(projectRequire.resolve("phaser/package.json"));
|
|
10
|
+
const phaserBrowserBuild = resolve(phaserPackageRoot, "dist/phaser.js");
|
|
11
|
+
return defineConfig({
|
|
12
|
+
resolve: {
|
|
13
|
+
alias: {
|
|
14
|
+
...options.aliases,
|
|
15
|
+
"@": resolve(options.projectRoot, "src"),
|
|
16
|
+
phaser: phaserBrowserBuild
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
test: {
|
|
20
|
+
include: ["tests/**/*.test.ts"],
|
|
21
|
+
testTimeout: options.testTimeout,
|
|
22
|
+
hookTimeout: options.hookTimeout,
|
|
23
|
+
server: {
|
|
24
|
+
deps: {
|
|
25
|
+
inline: [/miaoda-game-/]
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
environment: "jsdom",
|
|
29
|
+
environmentOptions: {
|
|
30
|
+
jsdom: {
|
|
31
|
+
url: "http://localhost/"
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
setupFiles: [
|
|
35
|
+
"miaoda-game-devkit/vitest-setup",
|
|
36
|
+
...options.additionalSetupFiles ?? []
|
|
37
|
+
],
|
|
38
|
+
restoreMocks: true,
|
|
39
|
+
clearMocks: true,
|
|
40
|
+
coverage: {
|
|
41
|
+
provider: "v8",
|
|
42
|
+
include: ["src/scenes/**/*.{ts,tsx}"],
|
|
43
|
+
exclude: [
|
|
44
|
+
"src/scenes/**/*.d.ts",
|
|
45
|
+
"src/scenes/**/*.{spec,test}.{ts,tsx}"
|
|
46
|
+
],
|
|
47
|
+
reporter: ["text"],
|
|
48
|
+
thresholds: {
|
|
49
|
+
perFile: true,
|
|
50
|
+
lines: 1,
|
|
51
|
+
functions: 1,
|
|
52
|
+
statements: 1
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
export {
|
|
59
|
+
defineGameVitestConfig
|
|
60
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"plugins": [],
|
|
3
|
+
"categories": {
|
|
4
|
+
"correctness": "off"
|
|
5
|
+
},
|
|
6
|
+
"jsPlugins": [
|
|
7
|
+
"./dist/rules/check-image-import-plugin.js",
|
|
8
|
+
"./dist/rules/check-style-import-plugin.js"
|
|
9
|
+
],
|
|
10
|
+
"rules": {
|
|
11
|
+
"check-image-exists/no-missing-image": "error",
|
|
12
|
+
"check-style-exists/no-missing-style": "error"
|
|
13
|
+
}
|
|
14
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "miaoda-game-devkit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared lint and deterministic Phaser HEADLESS testing tools for Miaoda games",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"module": "./dist/index.mjs",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"bin": {
|
|
10
|
+
"miaoda-game-lint": "bin/miaoda-game-lint.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": {
|
|
15
|
+
"types": "./dist/index.d.mts",
|
|
16
|
+
"default": "./dist/index.mjs"
|
|
17
|
+
},
|
|
18
|
+
"require": {
|
|
19
|
+
"types": "./dist/index.d.ts",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"./vitest-config": {
|
|
24
|
+
"import": {
|
|
25
|
+
"types": "./dist/vitest-config.d.mts",
|
|
26
|
+
"default": "./dist/vitest-config.mjs"
|
|
27
|
+
},
|
|
28
|
+
"require": {
|
|
29
|
+
"types": "./dist/vitest-config.d.ts",
|
|
30
|
+
"default": "./dist/vitest-config.js"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"./vitest-setup": "./dist/lint/setup.mjs",
|
|
34
|
+
"./biome": "./biome-config.json",
|
|
35
|
+
"./tsconfig-base": "./tsconfig-base.json"
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"dist",
|
|
39
|
+
"bin",
|
|
40
|
+
"biome-config.json",
|
|
41
|
+
"oxlint-config.json",
|
|
42
|
+
"tsconfig-base.json",
|
|
43
|
+
"!dist/**/*.map",
|
|
44
|
+
"!dist/lint/**/*.test.*"
|
|
45
|
+
],
|
|
46
|
+
"nx": {
|
|
47
|
+
"tags": [
|
|
48
|
+
"type:package",
|
|
49
|
+
"scope:devkit"
|
|
50
|
+
],
|
|
51
|
+
"targets": {
|
|
52
|
+
"build": {
|
|
53
|
+
"inputs": [
|
|
54
|
+
"default",
|
|
55
|
+
"^production",
|
|
56
|
+
{
|
|
57
|
+
"env": "NODE_ENV"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
},
|
|
61
|
+
"test": {
|
|
62
|
+
"executor": "nx:run-commands",
|
|
63
|
+
"dependsOn": [
|
|
64
|
+
"build"
|
|
65
|
+
],
|
|
66
|
+
"options": {
|
|
67
|
+
"command": "./bin/miaoda-game-lint.js --contracts-only",
|
|
68
|
+
"cwd": "packages/game-devkit"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"@biomejs/biome": "2.5.6",
|
|
75
|
+
"@typescript/native-preview": "7.0.0-dev.20260707.2",
|
|
76
|
+
"jsdom": "29.1.1",
|
|
77
|
+
"oxc-resolver": "11.24.2",
|
|
78
|
+
"oxlint": "1.76.0",
|
|
79
|
+
"tailwindcss": "3.4.19",
|
|
80
|
+
"vitest": "^4.1.10"
|
|
81
|
+
},
|
|
82
|
+
"peerDependencies": {
|
|
83
|
+
"phaser": ">=3.90.0 <5"
|
|
84
|
+
},
|
|
85
|
+
"publishConfig": {
|
|
86
|
+
"registry": "https://registry.npmjs.org/",
|
|
87
|
+
"access": "public"
|
|
88
|
+
}
|
|
89
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"target": "ES2020",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"moduleResolution": "bundler",
|
|
9
|
+
"allowImportingTsExtensions": true,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"moduleDetection": "force",
|
|
12
|
+
"resolveJsonModule": true,
|
|
13
|
+
"esModuleInterop": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"strict": true,
|
|
16
|
+
"noUnusedLocals": true,
|
|
17
|
+
"noUnusedParameters": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"noUncheckedSideEffectImports": true,
|
|
20
|
+
"skipLibCheck": true
|
|
21
|
+
}
|
|
22
|
+
}
|