build: establish strict TypeScript workspace

This commit is contained in:
chick
2026-07-16 09:34:50 +08:00
parent 43a69d8617
commit aa3c3f9803
22 changed files with 5509 additions and 6 deletions
+5
View File
@@ -0,0 +1,5 @@
node_modules
coverage
pnpm-lock.yaml
packages/**/test/fixtures
packages/test-fixtures/src/**/*.json
+5
View File
@@ -0,0 +1,5 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@multi-simadmin/api",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": "./src/index.ts",
"scripts": {
"typecheck": "tsc -p tsconfig.json"
}
}
+1
View File
@@ -0,0 +1 @@
export const apiWorkspaceReady = true;
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@multi-simadmin/web",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": "./src/index.ts",
"scripts": {
"typecheck": "tsc -p tsconfig.json"
}
}
+1
View File
@@ -0,0 +1 @@
export const webWorkspaceReady = true;
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}
+35
View File
@@ -0,0 +1,35 @@
import eslint from '@eslint/js';
import { defineConfig } from 'eslint/config';
import tseslint from 'typescript-eslint';
export default defineConfig(
{
ignores: ['node_modules/**', 'coverage/**', 'packages/**/test/fixtures/**'],
},
{
files: ['**/*.js'],
...eslint.configs.recommended,
languageOptions: {
...eslint.configs.recommended.languageOptions,
globals: {
console: 'readonly',
process: 'readonly',
URL: 'readonly',
},
},
},
{
files: ['**/*.ts'],
extends: [...tseslint.configs.recommended],
languageOptions: {
parser: tseslint.parser,
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
},
rules: {
'no-undef': 'off',
},
},
);
+2948 -3
View File
File diff suppressed because it is too large Load Diff
+19 -3
View File
@@ -2,14 +2,30 @@
"name": "multi-simadmin",
"version": "0.1.0",
"description": "Config-driven dashboard to manage multiple SimAdmin instances from one page",
"private": true,
"type": "module",
"packageManager": "pnpm@11.13.0",
"scripts": {
"start": "node server/index.js",
"dev": "node --watch server/index.js",
"test": "node --test"
"test": "node --test test/*.test.js packages/operation-registry/test/*.test.ts packages/test-fixtures/test/*.test.ts",
"test:legacy": "node --test test/*.test.js",
"test:unit": "vitest run",
"test:contract": "node --test packages/operation-registry/test/*.test.ts",
"lint": "eslint apps packages/contracts test/phase-one-workspace.test.js eslint.config.js vitest.config.ts",
"format:check": "prettier --check apps packages/contracts test/phase-one-workspace.test.js eslint.config.js vitest.config.ts tsconfig.base.json pnpm-workspace.yaml package.json",
"typecheck": "corepack pnpm --recursive --if-present run typecheck"
},
"dependencies": {
"@fastify/static": "^9.1.3",
"fastify": "^5.6.2"
"@fastify/static": "9.1.3",
"fastify": "5.10.0"
},
"devDependencies": {
"@eslint/js": "9.39.2",
"eslint": "9.39.2",
"prettier": "3.8.1",
"typescript": "5.9.3",
"typescript-eslint": "8.53.0",
"vitest": "4.0.18"
}
}
+10
View File
@@ -0,0 +1,10 @@
{
"name": "@multi-simadmin/contracts",
"version": "0.0.0",
"private": true,
"type": "module",
"exports": "./src/index.ts",
"scripts": {
"typecheck": "tsc -p tsconfig.json"
}
}
+8
View File
@@ -0,0 +1,8 @@
import { describe, expect, it } from 'vitest';
import { contractsWorkspaceReady } from './index.js';
describe('contracts workspace baseline', () => {
it('exposes a compilable package entry', () => {
expect(contractsWorkspaceReady).toBe(true);
});
});
+1
View File
@@ -0,0 +1 @@
export const contractsWorkspaceReady = true;
+7
View File
@@ -0,0 +1,7 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src"
},
"include": ["src/**/*.ts"]
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "@multi-simadmin/operation-registry",
"version": "0.0.0",
"private": true,
"type": "module"
}
+6
View File
@@ -0,0 +1,6 @@
{
"name": "@multi-simadmin/test-fixtures",
"version": "0.0.0",
"private": true,
"type": "module"
}
+2298
View File
File diff suppressed because it is too large Load Diff
+8
View File
@@ -0,0 +1,8 @@
packages:
- 'apps/*'
- 'packages/*'
# Vitest uses esbuild for TypeScript transformation. pnpm 11 blocks every
# dependency build by default, so keep the allow-list explicit and minimal.
allowBuilds:
esbuild: true
+93
View File
@@ -0,0 +1,93 @@
import assert from 'node:assert/strict';
import { readFile } from 'node:fs/promises';
import test from 'node:test';
const root = new URL('../', import.meta.url);
const readJson = async (path) => JSON.parse(await readFile(new URL(path, root), 'utf8'));
const workspacePackages = ['apps/api', 'apps/web', 'packages/contracts'];
test('Phase 1 workspace pins pnpm and preserves the legacy service gates', async () => {
const packageJson = await readJson('package.json');
assert.equal(packageJson.packageManager, 'pnpm@11.13.0');
for (const script of ['lint', 'format:check', 'typecheck', 'test:unit', 'test:contract']) {
assert.equal(typeof packageJson.scripts[script], 'string', `missing ${script} script`);
}
assert.equal(packageJson.scripts.start, 'node server/index.js');
assert.equal(packageJson.scripts.dev, 'node --watch server/index.js');
assert.match(
packageJson.scripts.test,
/node --test/,
'root test must retain the legacy node:test gate',
);
assert.match(packageJson.scripts.test, /test\/\*\.test\.js/);
assert.match(packageJson.scripts.test, /packages\/operation-registry\/test\/\*\.test\.ts/);
assert.match(packageJson.scripts.test, /packages\/test-fixtures\/test\/\*\.test\.ts/);
assert.doesNotMatch(
packageJson.scripts.test,
/^node --test$/,
'root test must not auto-discover Vitest-only files',
);
assert.match(packageJson.scripts.lint, /apps/);
assert.match(packageJson.scripts.lint, /packages\/contracts/);
assert.match(packageJson.scripts.lint, /eslint\.config\.js/);
assert.doesNotMatch(
packageJson.scripts.lint,
/eslint\s+\./,
'Phase 1 lint must not claim unmigrated legacy code',
);
assert.doesNotMatch(packageJson.scripts.lint, /echo|true/, 'lint must execute a real checker');
assert.match(packageJson.scripts['format:check'], /prettier --check/);
assert.match(
packageJson.scripts.typecheck,
/^corepack pnpm /,
'typecheck must work when pnpm is provided only by Corepack',
);
});
test('Phase 1 workspace and strict shared TypeScript policy are explicit', async () => {
const workspace = await readFile(new URL('pnpm-workspace.yaml', root), 'utf8');
assert.match(workspace, /^packages:\s*$/m);
assert.match(workspace, /- ['"]apps\/\*['"]/);
assert.match(workspace, /- ['"]packages\/\*['"]/);
const tsconfig = await readJson('tsconfig.base.json');
const required = {
strict: true,
noUncheckedIndexedAccess: true,
exactOptionalPropertyTypes: true,
useUnknownInCatchVariables: true,
noImplicitOverride: true,
};
for (const [option, value] of Object.entries(required)) {
assert.equal(tsconfig.compilerOptions[option], value, `${option} must be enabled`);
}
});
test('initial application and contract packages compile from isolated source entries', async () => {
for (const directory of workspacePackages) {
const manifest = await readJson(`${directory}/package.json`);
const tsconfig = await readJson(`${directory}/tsconfig.json`);
await readFile(new URL(`${directory}/src/index.ts`, root), 'utf8');
const serialized = JSON.stringify({ manifest, tsconfig });
assert.doesNotMatch(
serialized,
/(?:^|[/.])server(?:[/.]|$)/,
`${directory} must not depend on legacy server`,
);
assert.doesNotMatch(
serialized,
/(?:^|[/.])public(?:[/.]|$)/,
`${directory} must not depend on legacy public assets`,
);
assert.equal(tsconfig.extends, '../../tsconfig.base.json');
}
});
test('runtime dependencies stay pinned to the versions already present before workspace migration', async () => {
const packageJson = await readJson('package.json');
assert.equal(packageJson.dependencies.fastify, '5.10.0');
assert.equal(packageJson.dependencies['@fastify/static'], '9.1.3');
});
+17
View File
@@ -0,0 +1,17 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"useUnknownInCatchVariables": true,
"noImplicitOverride": true,
"noEmit": true,
"verbatimModuleSyntax": true,
"isolatedModules": true,
"skipLibCheck": true
}
}
+7
View File
@@ -0,0 +1,7 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
include: ['apps/**/*.test.ts', 'packages/contracts/**/*.test.ts'],
},
});