Merge branch 'phase/57-client-typecheck'
# Conflicts: # docs/phases/10-craft/README.md
This commit is contained in:
Generated
-7
@@ -7,9 +7,6 @@
|
||||
"": {
|
||||
"name": "hschool-client",
|
||||
"version": "0.1.0",
|
||||
"dependencies": {
|
||||
"hschool-client": "file:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^24.10.1",
|
||||
"happy-dom": "^20.11.2",
|
||||
@@ -597,10 +594,6 @@
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/hschool-client": {
|
||||
"resolved": "",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/lightningcss": {
|
||||
"version": "1.33.0",
|
||||
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
"build": "tsc -b && vite build",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "tsc -b",
|
||||
"test": "vitest run",
|
||||
"test": "node ./scripts/run-test.mjs",
|
||||
"test:watch": "vitest"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -20,8 +20,5 @@
|
||||
"typescript": "~5.9.3",
|
||||
"vite": "^8.2.1",
|
||||
"vitest": "^4.1.10"
|
||||
},
|
||||
"dependencies": {
|
||||
"hschool-client": "file:"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { createRequire } from 'node:module';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const clientRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const require = createRequire(import.meta.url);
|
||||
const tscBin = require.resolve('typescript/bin/tsc');
|
||||
|
||||
const pkg = JSON.parse(readFileSync(path.join(clientRoot, 'package.json'), 'utf8')) as {
|
||||
scripts: Record<string, string>;
|
||||
dependencies?: Record<string, string>;
|
||||
};
|
||||
|
||||
describe('npm test script', () => {
|
||||
it('runs typecheck then vitest, without swallowing tsc', () => {
|
||||
expect(pkg.scripts.test).toBe('node ./scripts/run-test.mjs');
|
||||
const runner = readFileSync(path.join(clientRoot, 'scripts', 'run-test.mjs'), 'utf8');
|
||||
expect(runner).toContain("'-b'");
|
||||
expect(runner).toContain("'run'");
|
||||
expect(runner).toMatch(/if \(typecheck !== 0\)/);
|
||||
expect(pkg.scripts.typecheck).toBe('tsc -b');
|
||||
});
|
||||
|
||||
it('keeps watch as vitest only', () => {
|
||||
expect(pkg.scripts['test:watch']).toBe('vitest');
|
||||
expect(pkg.scripts['test:watch']).not.toMatch(/typecheck|tsc/);
|
||||
});
|
||||
|
||||
it('does not depend on the hschool-client file: loop', () => {
|
||||
expect(pkg.dependencies?.['hschool-client']).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('duplicate dictionary keys', () => {
|
||||
it('makes tsc fail on a strings.ts ru object without vite build', () => {
|
||||
const dir = mkdtempSync(path.join(tmpdir(), 'hschool-dup-'));
|
||||
try {
|
||||
const original = readFileSync(path.join(clientRoot, 'src', 'i18n', 'strings.ts'), 'utf8');
|
||||
const needle = " createSchool: 'Создать школу',";
|
||||
expect(original).toContain(needle);
|
||||
const duplicate = original.replace(
|
||||
needle,
|
||||
`${needle}\n createSchool: 'дубликат',`,
|
||||
);
|
||||
writeFileSync(path.join(dir, 'strings.ts'), duplicate);
|
||||
writeFileSync(
|
||||
path.join(dir, 'locale.ts'),
|
||||
readFileSync(path.join(clientRoot, 'src', 'i18n', 'locale.ts'), 'utf8'),
|
||||
);
|
||||
writeFileSync(
|
||||
path.join(dir, 'tsconfig.json'),
|
||||
JSON.stringify({
|
||||
compilerOptions: {
|
||||
strict: true,
|
||||
noEmit: true,
|
||||
target: 'ES2022',
|
||||
module: 'ESNext',
|
||||
moduleResolution: 'bundler',
|
||||
allowImportingTsExtensions: true,
|
||||
verbatimModuleSyntax: true,
|
||||
lib: ['ES2022', 'DOM'],
|
||||
},
|
||||
include: ['strings.ts', 'locale.ts'],
|
||||
}),
|
||||
);
|
||||
|
||||
const result = spawnSync(process.execPath, [tscBin, '-p', dir], {
|
||||
encoding: 'utf8',
|
||||
});
|
||||
const output = `${result.stdout ?? ''}${result.stderr ?? ''}`;
|
||||
expect(result.status).not.toBe(0);
|
||||
expect(output).toMatch(/TS1117|TS2300|TS1118/);
|
||||
} finally {
|
||||
rmSync(dir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,38 @@
|
||||
// `tsc -b && vitest run` on Windows npm (tsc.cmd) can drop the typecheck exit code.
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { createRequire } from 'node:module';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const clientRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
||||
const require = createRequire(import.meta.url);
|
||||
const win = process.platform === 'win32';
|
||||
|
||||
function statusOf(result) {
|
||||
if (result.error) {
|
||||
console.error(result.error);
|
||||
return 1;
|
||||
}
|
||||
return result.status ?? 1;
|
||||
}
|
||||
|
||||
const typecheck = statusOf(
|
||||
spawnSync(process.execPath, [require.resolve('typescript/bin/tsc'), '-b'], {
|
||||
cwd: clientRoot,
|
||||
stdio: 'inherit',
|
||||
}),
|
||||
);
|
||||
if (typecheck !== 0) {
|
||||
process.exit(typecheck);
|
||||
}
|
||||
|
||||
const vitest = path.join(clientRoot, 'node_modules', '.bin', win ? 'vitest.cmd' : 'vitest');
|
||||
process.exit(
|
||||
statusOf(
|
||||
spawnSync(vitest, ['run', ...process.argv.slice(2)], {
|
||||
cwd: clientRoot,
|
||||
stdio: 'inherit',
|
||||
shell: win,
|
||||
}),
|
||||
),
|
||||
);
|
||||
@@ -20,5 +20,5 @@
|
||||
"noUncheckedSideEffectImports": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
"include": ["vite.config.ts", "scripts/**/*.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user