Merge origin/master and resolve self-serve relay sync conflicts
This commit is contained in:
@@ -0,0 +1,223 @@
|
||||
{
|
||||
"version": 1,
|
||||
"snapshot_of": "backend-php",
|
||||
"projects": {
|
||||
"backend-php": {
|
||||
"display_name": "Copenhagen Truck Wash API",
|
||||
"relative_root": ".",
|
||||
"commands": {
|
||||
"setup": {
|
||||
"default": "./scripts/setup.sh",
|
||||
"win32": "powershell -ExecutionPolicy Bypass -File .\\scripts\\setup.ps1"
|
||||
},
|
||||
"run": {
|
||||
"default": "docker compose up -d traefik redis mysql-debug php1 caddy"
|
||||
},
|
||||
"test_unit": {
|
||||
"default": "docker compose exec -T php1 sh -lc \"cd /var/www/html && composer test:unit\""
|
||||
},
|
||||
"test_e2e": {
|
||||
"default": ""
|
||||
},
|
||||
"test_full": {
|
||||
"default": ""
|
||||
},
|
||||
"debug": {
|
||||
"default": "docker compose logs -f --tail=200 php1"
|
||||
}
|
||||
},
|
||||
"codex_environment": {
|
||||
"name": "api",
|
||||
"actions": [
|
||||
{
|
||||
"name": "Start API",
|
||||
"icon": "run",
|
||||
"command_id": "run"
|
||||
},
|
||||
{
|
||||
"name": "Stop API",
|
||||
"icon": "run",
|
||||
"literal_command": {
|
||||
"default": "docker compose down"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "PHP logs",
|
||||
"icon": "debug",
|
||||
"command_id": "debug"
|
||||
},
|
||||
{
|
||||
"name": "PHP unit tests",
|
||||
"icon": "test",
|
||||
"command_id": "test_unit"
|
||||
},
|
||||
{
|
||||
"name": "AI workflow check",
|
||||
"icon": "debug",
|
||||
"literal_command": {
|
||||
"default": "node scripts/sync-ai-workflow.mjs --check"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"generated_content": {
|
||||
"aiassistant_tests_lines": [
|
||||
"# Backend PHP Testing Rules",
|
||||
"",
|
||||
"These rules apply to `services/nginx/app/tests` and any backend change that needs verification.",
|
||||
"",
|
||||
"1. Add or update tests for every new feature, bug fix, API contract change, or search or authentication workflow change.",
|
||||
"2. Run backend verification in the `php1` container. Do not use host-side PHP for the supported workflow.",
|
||||
"3. Use `docker compose exec -T php1 sh -lc \"cd /var/www/html && composer test:unit\"` for unit coverage.",
|
||||
"4. Use `docker compose exec -T php1 sh -lc \"cd /var/www/html && composer test:integration\"` when code depends on Redis, MySQL, or environment-backed configuration.",
|
||||
"5. Use `docker compose exec -T php1 sh -lc \"cd /var/www/html && composer test:api\"` when route behavior, envelopes, or request parsing changes.",
|
||||
"6. Keep tests deterministic: no live third-party calls, no shared Redis keys, no broad database cleanup, and no sleeps unless time behavior is the thing under test.",
|
||||
"7. Prefer narrow fixtures, explicit cleanup, and behavior-level assertions over implementation checks.",
|
||||
"8. When public routes, schemas, or permissions change, update `openapi.yaml` together with the tests.",
|
||||
"9. Run the narrowest relevant suite first, then the broader suite that matches the risk before you finish the task.",
|
||||
"",
|
||||
"Canonical workflow reference: `.ai-workflow/workflow.md`."
|
||||
],
|
||||
"aiassistant_routes_lines": [
|
||||
"# Backend Route Rules",
|
||||
"",
|
||||
"These rules apply to files under `services/nginx/app/routes` and the classes they call.",
|
||||
"",
|
||||
"1. Keep route handlers thin: validate input, enforce permissions, call domain code, and write the response.",
|
||||
"2. Default to protected endpoints. Use the existing authentication and permission helpers instead of ad hoc access checks.",
|
||||
"3. Add or update backend tests in the `php1` container whenever route behavior changes.",
|
||||
"4. Update `openapi.yaml` whenever paths, parameters, request bodies, response envelopes, or permissions change.",
|
||||
"5. Prefer deterministic route tests and avoid live external integrations in route coverage.",
|
||||
"6. Use concise API errors and keep sensitive implementation details out of the response body.",
|
||||
"7. When a change touches department, order, or subuser authorization, cover both the allow path and the deny path.",
|
||||
"",
|
||||
"Canonical workflow reference: `.ai-workflow/workflow.md`."
|
||||
],
|
||||
"junie_lines": [
|
||||
"# Copenhagen Truck Wash API Development Guidelines",
|
||||
"",
|
||||
"This file is generated from the canonical AI workflow and is the supported Junie-facing reference for the backend repository.",
|
||||
"",
|
||||
"## Build And Run",
|
||||
"",
|
||||
"- Project root: `services/nginx/app` is the effective PHP application root.",
|
||||
"- Setup: use `./scripts/setup.sh` on POSIX or `powershell -ExecutionPolicy Bypass -File .\\scripts\\setup.ps1` on Windows.",
|
||||
"- Start local API stack: `docker compose up -d traefik redis mysql-debug php1 caddy`.",
|
||||
"- Tail logs with `docker compose logs -f --tail=200 php1`.",
|
||||
"",
|
||||
"## Testing",
|
||||
"",
|
||||
"- Supported backend validation runs in `php1`.",
|
||||
"- Unit tests: `docker compose exec -T php1 sh -lc \"cd /var/www/html && composer test:unit\"`.",
|
||||
"- Integration tests: `docker compose exec -T php1 sh -lc \"cd /var/www/html && composer test:integration\"`.",
|
||||
"- API tests: `docker compose exec -T php1 sh -lc \"cd /var/www/html && composer test:api\"`.",
|
||||
"- Prefer the narrowest suite that proves the change, then run the broader suite that matches the risk.",
|
||||
"",
|
||||
"## Workflow Notes",
|
||||
"",
|
||||
"- Generated assistant metadata is checked with `node scripts/sync-ai-workflow.mjs --check`.",
|
||||
"- Route and schema changes require matching updates to `openapi.yaml`.",
|
||||
"- Runtime OpenAI product behavior is out of scope for this workflow bundle unless a task explicitly changes product code.",
|
||||
"",
|
||||
"Canonical workflow reference: `.ai-workflow/workflow.md`."
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"assistants": {
|
||||
"codex": {
|
||||
"description": "Codex environment files and actions."
|
||||
},
|
||||
"aiassistant": {
|
||||
"description": "Project-specific AI Assistant rules."
|
||||
},
|
||||
"junie": {
|
||||
"description": "Project-specific Junie guidance."
|
||||
},
|
||||
"copilot": {
|
||||
"description": "Standardized Copilot task dispatch workflow."
|
||||
}
|
||||
},
|
||||
"commands": {
|
||||
"setup": {
|
||||
"description": "Install dependencies and prepare the supported local environment."
|
||||
},
|
||||
"run": {
|
||||
"description": "Start the primary local development entrypoint for the project."
|
||||
},
|
||||
"test_unit": {
|
||||
"description": "Run the project's narrow unit-style verification command."
|
||||
},
|
||||
"test_e2e": {
|
||||
"description": "Run the project's targeted browser or end-to-end verification command."
|
||||
},
|
||||
"test_full": {
|
||||
"description": "Run the broader high-confidence verification command when the project defines one."
|
||||
},
|
||||
"debug": {
|
||||
"description": "Run the supported debug entrypoint."
|
||||
}
|
||||
},
|
||||
"generated_outputs": [
|
||||
{
|
||||
"id": "backend_codex_environment",
|
||||
"template": "codex_project_environment",
|
||||
"project": "backend-php",
|
||||
"path": ".codex/environments/environment.toml"
|
||||
},
|
||||
{
|
||||
"id": "backend_aiassistant_tests",
|
||||
"template": "aiassistant_backend_tests_rule",
|
||||
"project": "backend-php",
|
||||
"path": ".aiassistant/rules/Creating and maintaining tests.md"
|
||||
},
|
||||
{
|
||||
"id": "backend_aiassistant_routes",
|
||||
"template": "aiassistant_backend_routes_rule",
|
||||
"project": "backend-php",
|
||||
"path": ".aiassistant/rules/Creating and securing routes.md"
|
||||
},
|
||||
{
|
||||
"id": "backend_junie_guidelines",
|
||||
"template": "junie_backend_guidelines",
|
||||
"project": "backend-php",
|
||||
"path": ".junie/guidelines.md"
|
||||
},
|
||||
{
|
||||
"id": "backend_copilot_workflow",
|
||||
"template": "copilot_dispatcher_workflow",
|
||||
"project": "backend-php",
|
||||
"path": ".github/workflows/copilot.yml"
|
||||
}
|
||||
],
|
||||
"sync_targets": {
|
||||
"backend-php": {
|
||||
"source_root": ".",
|
||||
"destination": "C:\\Users\\2jepp\\PhpstormProjects\\api",
|
||||
"supported_metadata_dirs": [
|
||||
".codex",
|
||||
".aiassistant",
|
||||
".junie",
|
||||
".github",
|
||||
".ai-workflow",
|
||||
"scripts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"copilot": {
|
||||
"workflow_name": "Copilot Task Dispatcher",
|
||||
"input_description": "The task description for Copilot",
|
||||
"issue_label": "copilot-task",
|
||||
"title_prefix": "Copilot Task:",
|
||||
"body_lines": [
|
||||
"Assigned to Copilot by @{{ACTOR}}.",
|
||||
"",
|
||||
"Task",
|
||||
"{{TASK}}",
|
||||
"",
|
||||
"AI workflow notes",
|
||||
"- Generated assistant metadata is synchronized from the canonical AI workflow bundle.",
|
||||
"- Run `node scripts/sync-ai-workflow.mjs --check` if assistant metadata changed."
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
<!-- AUTOGENERATED SNAPSHOT for backend-php: refresh from the canonical workspace with `node scripts/sync-ai-workflow.mjs --write`. -->
|
||||
|
||||
# Developer AI Workflow
|
||||
|
||||
This directory is the canonical source of truth for the repository's developer-facing AI workflow.
|
||||
|
||||
## Goals
|
||||
|
||||
- Keep Codex, AI Assistant, Junie, and Copilot aligned from one maintained source.
|
||||
- Make assistant metadata deterministic so the generated files can be rewritten safely and checked in CI.
|
||||
- Preserve the current mirror-repo workflow for `backend-php` and `front-end-vue`.
|
||||
- Keep all changes in this workflow scoped to developer tooling and documentation. Runtime OpenAI features stay out of scope.
|
||||
|
||||
## Supported Assistants
|
||||
|
||||
- `Codex`: local environments and actions under `.codex/environments`.
|
||||
- `AI Assistant`: generated guidance under `.aiassistant/rules`.
|
||||
- `Junie`: generated project guidance under `.junie/guidelines.md`.
|
||||
- `Copilot`: standardized issue-dispatch workflow content under `.github/workflows/copilot.yml`.
|
||||
|
||||
## Global Rules
|
||||
|
||||
1. Change only `.ai-workflow/workflow.md` and `.ai-workflow/manifest.json` when updating the developer AI workflow.
|
||||
2. Regenerate all derived files with `node scripts/sync-ai-workflow.mjs --write`.
|
||||
3. Validate drift with `node scripts/sync-ai-workflow.mjs --check`.
|
||||
4. Generated assistant files are not hand-edited.
|
||||
5. Unsupported surfaces stay unsupported until they have a real owner and a real config.
|
||||
6. `front-end-vue/.ai/mcp/mcp.json` is intentionally unsupported and should not be recreated until there is an actual MCP integration to maintain.
|
||||
|
||||
## Command Matrix
|
||||
|
||||
### `backend-php`
|
||||
|
||||
- Setup: use the existing setup scripts in `backend-php/scripts`.
|
||||
- Run: start the API stack with `traefik`, `redis`, `mysql-debug`, `php1`, and `caddy`.
|
||||
- Debug: tail `php1` logs.
|
||||
- PHP verification: always run backend validation in the `php1` container.
|
||||
- Unit tests: `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:unit"`.
|
||||
- Integration tests: `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:integration"`.
|
||||
- API tests: `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:api"`.
|
||||
|
||||
### `front-end-vue`
|
||||
|
||||
- Setup: `npm ci` and `npx playwright install`.
|
||||
- Run: `npm run dev`.
|
||||
- Unit tests: `npm run test:unit` with Vitest.
|
||||
- Browser smoke: `npm run test:e2e:smoke`.
|
||||
- Browser full suite: `npm run test:e2e:ci`.
|
||||
- Debug: `npx playwright test --debug`.
|
||||
- Browser validation is Playwright-first. WebdriverIO and Appium are not part of the supported workflow.
|
||||
|
||||
### `automation`
|
||||
|
||||
- Setup: `npm ci` and `npx playwright install`.
|
||||
- Run: `npx playwright test --ui`.
|
||||
- Browser tests: `npx playwright test`.
|
||||
- Debug: `npx playwright test --debug`.
|
||||
|
||||
## Mirror Repos And Sync
|
||||
|
||||
- `backend-php` mirrors to `C:\Users\2jepp\PhpstormProjects\api`.
|
||||
- `front-end-vue` mirrors to `C:\Users\2jepp\WebstormProjects\pleno-vue`.
|
||||
- Supported metadata directories that must stay mirrored are `.codex`, `.aiassistant`, `.junie`, `.github`, generated `.ai-workflow`, and `scripts/sync-ai-workflow.mjs`.
|
||||
- Cache and build directories remain excluded from the watcher.
|
||||
- The mirror repos receive generated snapshots of `.ai-workflow` and `scripts/sync-ai-workflow.mjs` so their local CI can run `--check` without depending on the combined workspace root.
|
||||
|
||||
## Generated Outputs
|
||||
|
||||
- Root Codex environment for combined backend and frontend entrypoints.
|
||||
- Backend and frontend Codex environments.
|
||||
- Backend and frontend AI Assistant guidance.
|
||||
- Backend and frontend Junie guidance.
|
||||
- Backend Copilot dispatcher workflow.
|
||||
- Backend and frontend snapshot copies of `.ai-workflow` plus `scripts/sync-ai-workflow.mjs` for mirrored repositories.
|
||||
@@ -2,261 +2,20 @@
|
||||
apply: always
|
||||
---
|
||||
|
||||
### Creating and maintaining tests — Rules (Project‑specific)
|
||||
<!-- AUTOGENERATED: Run `node scripts/sync-ai-workflow.mjs --write`. -->
|
||||
|
||||
These rules apply to all tests under `services/nginx/app/tests` for the Copenhagen Truck Wash API. They codify how to create, run, and maintain the lightweight CLI PHP tests used in this repository.
|
||||
# Backend PHP Testing Rules
|
||||
|
||||
1. Scope and philosophy
|
||||
- Prefer fast, deterministic CLI scripts over framework‑based tests.
|
||||
- Keep unit‑style tests isolated from I/O (no DB/Redis/files/network). Use test doubles and explicit `require_once` for only the code exercised.
|
||||
- Use integration tests sparingly and only when configuration/globals are required. Run them in Docker with `USE_ENV=true`.
|
||||
These rules apply to `services/nginx/app/tests` and any backend change that needs verification.
|
||||
|
||||
2. Location and naming
|
||||
- Place tests in `services/nginx/app/tests/<domain>/YourTest.php` where `<domain>` reflects the feature area (e.g., `subusers`, `orders`, `redis`).
|
||||
- File names must end with `Test.php` (e.g., `SelfservePermissionInitTest.php`).
|
||||
- Keep one coherent scenario per file. If a file grows beyond ~150 lines or mixes unrelated scenarios, split it.
|
||||
|
||||
3. Execution modes
|
||||
- Unit‑style (preferred): run on host with PHP CLI.
|
||||
- Command: `php services/nginx/app/tests/<domain>/<Name>Test.php`
|
||||
- Integration‑style (needs env/globals): run inside Docker (php1) where `USE_ENV=true` and env vars are provided by `docker-compose.yml`.
|
||||
- Start minimal stack: `docker compose up -d traefik redis php1 caddy`
|
||||
- Command: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`
|
||||
|
||||
4. Bootstrapping and includes
|
||||
- At the top of every test, define `WD` if not already defined:
|
||||
```php
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
```
|
||||
- For unit‑style tests: include only the files you exercise via explicit `require_once WD . '/path/to/file.php';`.
|
||||
- Do NOT include `config.php` for unit‑style tests; it throws if `USE_ENV` is not set.
|
||||
- For integration‑style tests that need globals (`$CONFIG_DB`, `$REDIS_CONFIG`, etc.): `require_once WD . '/config.php';` and run the test inside `php1`.
|
||||
|
||||
5. Output and exit codes
|
||||
- Use simple helpers for human‑readable output:
|
||||
```php
|
||||
function ok($message){ echo "\n\033[32m✔ $message\033[0m\n"; }
|
||||
function fail($message){ echo "\n\033[31m✖ $message\033[0m\n"; }
|
||||
```
|
||||
- Prefer collecting failures and exiting non‑zero when any assertion fails:
|
||||
```php
|
||||
$failed = 0;
|
||||
// ... on failure set $failed = 1;
|
||||
exit($failed);
|
||||
```
|
||||
- End with a concise completion line, e.g., `echo "\n<Name>Test completed.\n";`.
|
||||
|
||||
6. Determinism, time, and randomness
|
||||
- Avoid `sleep()` and real time dependencies. If time is relevant, pass it as a parameter or stub the time source.
|
||||
- Do not use random values unless strictly necessary; if used, seed explicitly (`mt_srand(1234)`) and document it.
|
||||
|
||||
7. External I/O and side effects
|
||||
- Never call external services (Stripe, e‑conomic, MinIO, Slack, WordPress) from tests. Stub or override code paths as shown in `tests/subusers/SelfservePermissionInitTest.php`.
|
||||
- Do not modify files under the repository during tests. If temporary files are required, use `sys_get_temp_dir()` and delete them before exit.
|
||||
|
||||
8. Redis and database (integration‑only)
|
||||
- Run inside Docker (`php1`) so `config.php` can populate `$REDIS_CONFIG` and `$CONFIG_DB`.
|
||||
- Use unique, namespaced Redis keys for test data (e.g., `test:<feature>:<uuid>`). Clean them up at the end. Do NOT call `FLUSHALL`.
|
||||
- Prefer fakes/stubs over real DB writes. If DB writes are unavoidable, scope them to clearly identifiable rows and delete them before exit.
|
||||
|
||||
9. Performance budgets
|
||||
- Each unit‑style test script should complete in < 100 ms on a typical dev machine.
|
||||
- Each integration‑style test should complete in < 2 s and must avoid N+1 loops or heavy queries.
|
||||
|
||||
10. When to add or update tests
|
||||
- New route/feature: add at least one unit‑style test for core logic. If behavior depends on configuration/globals, add a minimal integration test.
|
||||
- Bug fix: first reproduce with a failing test; then fix and ensure the test passes.
|
||||
- Refactor: keep behavior‑preserving tests green. If public behavior changes intentionally, update both tests and `openapi.yaml` accordingly.
|
||||
|
||||
11. Maintenance expectations
|
||||
- Do not weaken or remove assertions to “make tests pass”. Investigate and fix root causes.
|
||||
- Keep tests small and readable. Extract tiny helpers within the test file rather than introducing new shared libraries.
|
||||
- Match the project code style and line endings (see `.editorconfig`: UTF‑8, CRLF, 4‑space indent, class brace `next_line`).
|
||||
|
||||
12. Templates
|
||||
- Unit‑style template:
|
||||
```php
|
||||
<?php
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
// require_once WD . '/modules/foo/classes/bar.php';
|
||||
|
||||
function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
|
||||
function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
|
||||
$failed = 0;
|
||||
|
||||
// Arrange / Act
|
||||
$sum = 2 + 2;
|
||||
|
||||
// Assert
|
||||
if ($sum === 4) { ok('Basic arithmetic works (2 + 2 = 4)'); } else { fail('Expected 4'); $failed = 1; }
|
||||
|
||||
echo "\nExampleUnitTest completed.\n";
|
||||
exit($failed);
|
||||
```
|
||||
|
||||
- Integration‑style template (Redis example):
|
||||
```php
|
||||
<?php
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
require_once WD . '/config.php'; // populates $REDIS_CONFIG when USE_ENV=true
|
||||
require_once WD . '/classes/redis.php';
|
||||
|
||||
function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
|
||||
function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
|
||||
$failed = 0;
|
||||
|
||||
// Use a namespaced test key
|
||||
$key = 'test:redis:example:' . uniqid('', true);
|
||||
|
||||
try {
|
||||
$r = new classes\redis();
|
||||
$r->set($key, 'pong');
|
||||
$val = $r->get($key);
|
||||
if ($val === 'pong') { ok('Redis set/get works for namespaced key'); } else { fail('Unexpected value from Redis'); $failed = 1; }
|
||||
} finally {
|
||||
// Best‑effort cleanup
|
||||
try { $r->del($key); } catch (\Throwable $e) { /* ignore */ }
|
||||
}
|
||||
|
||||
echo "\nExampleRedisIntegrationTest completed.\n";
|
||||
exit($failed);
|
||||
```
|
||||
|
||||
13. Quick reference
|
||||
- Run unit‑style test on host: `php services/nginx/app/tests/<domain>/<Name>Test.php`
|
||||
- Run integration‑style test in Docker: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`
|
||||
- Minimal stack for integration: `docker compose up -d traefik redis php1 caddy`
|
||||
|
||||
For broader build, configuration, and existing examples, see `.junie/guidelines.md` (Testing section).
|
||||
|
||||
---
|
||||
apply: always
|
||||
---
|
||||
|
||||
### Creating and maintaining tests — Rules (Project‑specific)
|
||||
|
||||
These rules apply to all tests under `services/nginx/app/tests` for the Copenhagen Truck Wash API. They codify how to create, run, and maintain the lightweight CLI PHP tests used in this repository.
|
||||
|
||||
1. Scope and philosophy
|
||||
- Prefer fast, deterministic CLI scripts over framework‑based tests.
|
||||
- Keep unit‑style tests isolated from I/O (no DB/Redis/files/network). Use test doubles and explicit `require_once` for only the code exercised.
|
||||
- Use integration tests sparingly and only when configuration/globals are required. Run them in Docker with `USE_ENV=true`.
|
||||
|
||||
2. Location and naming
|
||||
- Place tests in `services/nginx/app/tests/<domain>/YourTest.php` where `<domain>` reflects the feature area (e.g., `subusers`, `orders`, `redis`).
|
||||
- File names must end with `Test.php` (e.g., `SelfservePermissionInitTest.php`).
|
||||
- Keep one coherent scenario per file. If a file grows beyond ~150 lines or mixes unrelated scenarios, split it.
|
||||
|
||||
3. Execution modes
|
||||
- Unit‑style (preferred): run on host with PHP CLI.
|
||||
- Command: `php services/nginx/app/tests/<domain>/<Name>Test.php`
|
||||
- Integration‑style (needs env/globals): run inside Docker (php1) where `USE_ENV=true` and env vars are provided by `docker-compose.yml`.
|
||||
- Start minimal stack: `docker compose up -d traefik redis php1 caddy`
|
||||
- Command: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`
|
||||
|
||||
4. Bootstrapping and includes
|
||||
- At the top of every test, define `WD` if not already defined:
|
||||
```php
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
```
|
||||
- For unit‑style tests: include only the files you exercise via explicit `require_once WD . '/path/to/file.php';`.
|
||||
- Do NOT include `config.php` for unit‑style tests; it throws if `USE_ENV` is not set.
|
||||
- For integration‑style tests that need globals (`$CONFIG_DB`, `$REDIS_CONFIG`, etc.): `require_once WD . '/config.php';` and run the test inside `php1`.
|
||||
|
||||
5. Output and exit codes
|
||||
- Use simple helpers for human‑readable output:
|
||||
```php
|
||||
function ok($message){ echo "\n\033[32m✔ $message\033[0m\n"; }
|
||||
function fail($message){ echo "\n\033[31m✖ $message\033[0m\n"; }
|
||||
```
|
||||
- Prefer collecting failures and exiting non‑zero when any assertion fails:
|
||||
```php
|
||||
$failed = 0;
|
||||
// ... on failure set $failed = 1;
|
||||
exit($failed);
|
||||
```
|
||||
- End with a concise completion line, e.g., `echo "\n<Name>Test completed.\n";`.
|
||||
|
||||
6. Determinism, time, and randomness
|
||||
- Avoid `sleep()` and real time dependencies. If time is relevant, pass it as a parameter or stub the time source.
|
||||
- Do not use random values unless strictly necessary; if used, seed explicitly (`mt_srand(1234)`) and document it.
|
||||
|
||||
7. External I/O and side effects
|
||||
- Never call external services (Stripe, e‑conomic, MinIO, Slack, WordPress) from tests. Stub or override code paths as shown in `tests/subusers/SelfservePermissionInitTest.php`.
|
||||
- Do not modify files under the repository during tests. If temporary files are required, use `sys_get_temp_dir()` and delete them before exit.
|
||||
|
||||
8. Redis and database (integration‑only)
|
||||
- Run inside Docker (`php1`) so `config.php` can populate `$REDIS_CONFIG` and `$CONFIG_DB`.
|
||||
- Use unique, namespaced Redis keys for test data (e.g., `test:<feature>:<uuid>`). Clean them up at the end. Do NOT call `FLUSHALL`.
|
||||
- Prefer fakes/stubs over real DB writes. If DB writes are unavoidable, scope them to clearly identifiable rows and delete them before exit.
|
||||
|
||||
9. Performance budgets
|
||||
- Each unit‑style test script should complete in < 100 ms on a typical dev machine.
|
||||
- Each integration‑style test should complete in < 2 s and must avoid N+1 loops or heavy queries.
|
||||
|
||||
10. When to add or update tests
|
||||
- New route/feature: add at least one unit‑style test for core logic. If behavior depends on configuration/globals, add a minimal integration test.
|
||||
- Bug fix: first reproduce with a failing test; then fix and ensure the test passes.
|
||||
- Refactor: keep behavior‑preserving tests green. If public behavior changes intentionally, update both tests and `openapi.yaml` accordingly.
|
||||
|
||||
11. Maintenance expectations
|
||||
- Do not weaken or remove assertions to “make tests pass”. Investigate and fix root causes.
|
||||
- Keep tests small and readable. Extract tiny helpers within the test file rather than introducing new shared libraries.
|
||||
- Match the project code style and line endings (see `.editorconfig`: UTF‑8, CRLF, 4‑space indent, class brace `next_line`).
|
||||
|
||||
12. Templates
|
||||
- Unit‑style template:
|
||||
```php
|
||||
<?php
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
// require_once WD . '/modules/foo/classes/bar.php';
|
||||
|
||||
function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
|
||||
function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
|
||||
$failed = 0;
|
||||
|
||||
// Arrange / Act
|
||||
$sum = 2 + 2;
|
||||
|
||||
// Assert
|
||||
if ($sum === 4) { ok('Basic arithmetic works (2 + 2 = 4)'); } else { fail('Expected 4'); $failed = 1; }
|
||||
|
||||
echo "\nExampleUnitTest completed.\n";
|
||||
exit($failed);
|
||||
```
|
||||
|
||||
- Integration‑style template (Redis example):
|
||||
```php
|
||||
<?php
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
require_once WD . '/config.php'; // populates $REDIS_CONFIG when USE_ENV=true
|
||||
require_once WD . '/classes/redis.php';
|
||||
|
||||
function ok($m){ echo "\n\033[32m✔ $m\033[0m\n"; }
|
||||
function fail($m){ echo "\n\033[31m✖ $m\033[0m\n"; }
|
||||
$failed = 0;
|
||||
|
||||
// Use a namespaced test key
|
||||
$key = 'test:redis:example:' . uniqid('', true);
|
||||
|
||||
try {
|
||||
$r = new classes\redis();
|
||||
$r->set($key, 'pong');
|
||||
$val = $r->get($key);
|
||||
if ($val === 'pong') { ok('Redis set/get works for namespaced key'); } else { fail('Unexpected value from Redis'); $failed = 1; }
|
||||
} finally {
|
||||
// Best‑effort cleanup
|
||||
try { $r->del($key); } catch (\Throwable $e) { /* ignore */ }
|
||||
}
|
||||
|
||||
echo "\nExampleRedisIntegrationTest completed.\n";
|
||||
exit($failed);
|
||||
```
|
||||
|
||||
13. Quick reference
|
||||
- Run unit‑style test on host: `php services/nginx/app/tests/<domain>/<Name>Test.php`
|
||||
- Run integration‑style test in Docker: `docker compose exec -T php1 php /var/www/html/tests/<domain>/<Name>Test.php`
|
||||
- Minimal stack for integration: `docker compose up -d traefik redis php1 caddy`
|
||||
|
||||
For broader build, configuration, and existing examples, see `.junie/guidelines.md` (Testing section).
|
||||
1. Add or update tests for every new feature, bug fix, API contract change, or search or authentication workflow change.
|
||||
2. Run backend verification in the `php1` container. Do not use host-side PHP for the supported workflow.
|
||||
3. Use `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:unit"` for unit coverage.
|
||||
4. Use `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:integration"` when code depends on Redis, MySQL, or environment-backed configuration.
|
||||
5. Use `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:api"` when route behavior, envelopes, or request parsing changes.
|
||||
6. Keep tests deterministic: no live third-party calls, no shared Redis keys, no broad database cleanup, and no sleeps unless time behavior is the thing under test.
|
||||
7. Prefer narrow fixtures, explicit cleanup, and behavior-level assertions over implementation checks.
|
||||
8. When public routes, schemas, or permissions change, update `openapi.yaml` together with the tests.
|
||||
9. Run the narrowest relevant suite first, then the broader suite that matches the risk before you finish the task.
|
||||
|
||||
Canonical workflow reference: `.ai-workflow/workflow.md`.
|
||||
|
||||
@@ -2,172 +2,18 @@
|
||||
apply: always
|
||||
---
|
||||
|
||||
### Creating and securing routes — Rules (Project‑specific)
|
||||
<!-- AUTOGENERATED: Run `node scripts/sync-ai-workflow.mjs --write`. -->
|
||||
|
||||
These rules define how to add HTTP routes to the Copenhagen Truck Wash API and how to secure them consistently. They reflect current patterns in `services/nginx/app/routes` and the helper APIs in `traits\route_t`.
|
||||
# Backend Route Rules
|
||||
|
||||
1. Location, naming, and structure
|
||||
- Place route classes under `services/nginx/app/routes`.
|
||||
- File names should describe the domain and end with `Route.php`, e.g., `BookingsRoute.php`, `ModuleSelfServeRoute.php`.
|
||||
- Namespace must be `routes` and each file must define one class that uses `traits\route_t` and implements `run(): void`.
|
||||
```php
|
||||
<?php
|
||||
namespace routes;
|
||||
use traits\route_t;
|
||||
These rules apply to files under `services/nginx/app/routes` and the classes they call.
|
||||
|
||||
class ExampleRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
// endpoints go here
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Registering endpoints (HTTP verbs)
|
||||
- Define endpoints inside `run()` using the helpers provided by `route_t`:
|
||||
- `get($path, $handler, $permissions = [])`
|
||||
- `post($path, $handler, $permissions = [])`
|
||||
- `put($path, $handler, $permissions = [])`
|
||||
- `delete($path, $handler, $permissions = [])`
|
||||
- `patch($path, $handler, $permissions = [])`
|
||||
- `options($path, $handler, $permissions = [])` (useful for CORS preflight when needed)
|
||||
- Handlers are closures that perform validation, authorization, side effects, and write responses via the global `$response` (`classes\response`).
|
||||
- Example (read‑only endpoint):
|
||||
```php
|
||||
$this->get('/example', function () {
|
||||
global $response;
|
||||
$response->success(['message' => 'Hello World!']);
|
||||
});
|
||||
```
|
||||
|
||||
3. Route paths and local routing
|
||||
- Caddy serves the app directly; Traefik adds an external `/api` prefix for local access.
|
||||
- Direct path in the app: `/foo/bar`.
|
||||
- Local access paths (both work):
|
||||
- `http://localhost/foo/bar` (direct)
|
||||
- `http://localhost/api/foo/bar` (Traefik strip‑prefix `/api` → still routes to `/foo/bar` in the app)
|
||||
- Keep paths meaningful and resource‑oriented. Prefer plural nouns for collections and subpaths for actions when unavoidable (e.g., `/modules/self-serve/lane/status`).
|
||||
|
||||
4. Responses and status codes
|
||||
- Use `$response->success($payload, $status = 200)` for successful results and `$response->error($messageOrPayload, $status)` for failures.
|
||||
- Typical statuses:
|
||||
- 200 OK for successful reads/updates; 201 Created when new resources are created.
|
||||
- 400 Bad Request for validation errors.
|
||||
- 401 Unauthorized when authentication is missing/invalid.
|
||||
- 403 Forbidden when authenticated but lacking permissions.
|
||||
- Return concise, non‑sensitive error messages. Internal details go to logs (see `objects\logs_o`).
|
||||
|
||||
5. Input handling and validation (route_t helpers)
|
||||
- Read parameters via:
|
||||
- `getParameter($name)` or `getParametersAsArray()` (preferred; integrates with `classes\response` parsing)
|
||||
- `fromRequest($name)` (checks JSON body, then `$_POST`, then query)
|
||||
- `fromQuery($name)` and `fromRoute($segmentName)` when relevant
|
||||
- Validate early and fail fast using helpers:
|
||||
- Presence: `requireParameters(['a','b'])`, `isParametersSet(['a','b'])`
|
||||
- Types: `requireType($val, type_string()|type_int()|TYPE_BOOL()|TYPE_ARRAY())`, `requireTypeIn($val, [...])`
|
||||
- Ranges/lengths: `requireMinValue($n, $min)`, `requireMaxValue($n, $max)`, `requireMaxLength($name, $len)`, `requireParameterIntPositive($n, $name)`
|
||||
- Formats/enums: `requireDateFormat($date, FORMAT_DATE())`, `requireInArray($val, $allowed)`
|
||||
|
||||
6. Authentication and authorization (core rules)
|
||||
- Default posture: endpoints are protected and require authentication unless explicitly public.
|
||||
- To check authentication without a specific permission: `isAuthenticated()`.
|
||||
- Enforce permissions inside handlers using:
|
||||
- `requirePermission('some_permission')` — throws 401/403 as appropriate.
|
||||
- `hasPermission('some_permission', $customerNumber = null)` — boolean check (do not throw).
|
||||
- Register permissions for discoverability/administration by supplying the third `$permissions` argument when declaring the route. Keys are permission names; values are human‑readable descriptions. You should still call `requirePermission()` inside the handler to actually enforce.
|
||||
```php
|
||||
$this->get('/modules/self-serve/lane/status', function () {
|
||||
global $response;
|
||||
self::requirePermission('modules_selfserve_lane_status_view');
|
||||
// ...
|
||||
$response->success(['status' => 'OK']);
|
||||
}, [
|
||||
'modules_selfserve_lane_status_view' => 'View self-serve lane status',
|
||||
]);
|
||||
```
|
||||
|
||||
7. Subusers, permission nodes, and customer context
|
||||
- When a permission corresponds to a subuser permission node, define it with `definePermission($perm, subusers_permission_node_key::CASE)` to keep mapping explicit.
|
||||
- Subuser contexts may target a specific customer via the `X-Customer-Number` header or a `customer_number` parameter; `route_t` exposes this as response meta (`target_customer_number`) when applicable.
|
||||
- Use `allowOwnOrDepartmentAccess($ownPerm, $deptPerm, $targetCustomerNumber, $departmentId, $ownGuard = null, $denyMessage = null)` for the common pattern: allow the principal to act on their own customer scope or fall back to a department/admin permission.
|
||||
- Use `isOwnCustomerContext($targetCustomerNumber)` and `resolveEffectiveCustomerNumber()` when building conditional logic.
|
||||
|
||||
8. Department, order, and special access helpers
|
||||
- Department access: `requireDepartmentAccess($departmentId, $permissionSuffix = null)`, `hasDepartmentAccess(...)`.
|
||||
- Order access: `requireOrderAccess($orderId, $permissionSuffix = null)` (note: indirect access rules may evolve; keep logic minimal in routes).
|
||||
- Plate scanners (API key based): protect endpoints with `requirePlateScannerAuth()`.
|
||||
- Bot protection (public forms): use `requireRecaptcha()` and expect `g_recaptcha_response` in the request.
|
||||
|
||||
9. Public endpoints
|
||||
- Keep public endpoints extremely limited. If an endpoint must be public, you must:
|
||||
- Validate all inputs thoroughly using the helpers above.
|
||||
- Add rate‑limit or abuse‑mitigation where relevant (Redis is available as `classes\redis` for counters/locks; coordinate design with maintainers before introducing new limits).
|
||||
- Prefer `requireRecaptcha()` for anonymous form submissions.
|
||||
|
||||
10. Side effects and idempotency
|
||||
- For POST/PUT/PATCH with side effects, make operations idempotent when feasible (e.g., by honoring a client idempotency key header). If you add such behavior, document it in the route’s docblock and in `openapi.yaml`.
|
||||
- Wrap multi‑step operations with proper validation and permission checks before any external calls (Stripe, e‑conomic, etc.). Do not leak third‑party error payloads directly; map to concise API errors and log details.
|
||||
|
||||
11. Logging and observability
|
||||
- On auth/permission denials, the helpers already log to `logs_o` with structured messages. Avoid duplicating logs for the same event.
|
||||
- For unexpected states you handle gracefully, add targeted logs via `objects\logs_o` with a clear category/key.
|
||||
|
||||
12. OpenAPI contract synchronization
|
||||
- Every public surface change must be reflected in `openapi.yaml` at repo root. Keep paths, methods, parameters, request/response schemas, and error statuses up to date.
|
||||
- Use the same path strings as in the route declaration (remember Traefik’s `/api` is stripped before hitting the app).
|
||||
- If you add or change permissions that users must hold, document them in the endpoint description.
|
||||
|
||||
13. Example: secure command endpoint
|
||||
```php
|
||||
<?php
|
||||
namespace routes;
|
||||
use traits\route_t;
|
||||
use classes\authentication;
|
||||
|
||||
class LaneCommandRoute
|
||||
{
|
||||
use route_t;
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->post('/modules/self-serve/lane/command', function () {
|
||||
global $response;
|
||||
|
||||
// AuthZ
|
||||
self::requirePermission('modules_selfserve_lane_command_execute');
|
||||
|
||||
// Validate input
|
||||
self::requireParameters(['lane_id', 'command']);
|
||||
$laneId = (int) self::getParameter('lane_id');
|
||||
self::requireType($laneId, self::type_int());
|
||||
self::requireMinValue($laneId, 1);
|
||||
|
||||
$cmd = (string) self::getParameter('command');
|
||||
self::requireType($cmd, self::type_string());
|
||||
self::requireInArray($cmd, ['START','STOP','RESERVE','RELEASE','RESET']);
|
||||
|
||||
// Business logic (call into modules/classes)
|
||||
// ...
|
||||
|
||||
$response->success(['ok' => true]);
|
||||
}, [
|
||||
'modules_selfserve_lane_command_execute' => 'Execute self‑serve lane commands',
|
||||
]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
14. Quick reference (local)
|
||||
- Bring up minimal stack: `docker compose up -d traefik redis php1 caddy`
|
||||
- Access an app route locally:
|
||||
- `http://localhost/<path>` or `http://localhost/api/<path>`
|
||||
- Tests are CLI scripts under `services/nginx/app/tests`. Prefer unit‑style tests for core logic; see “Creating and maintaining tests — Rules”.
|
||||
|
||||
15. Code style and hygiene
|
||||
- Follow `.editorconfig` (UTF‑8, CRLF, 4‑space indents).
|
||||
- Keep route files cohesive; avoid adding unrelated endpoints to the same class. If a class exceeds ~200–300 lines or mixes domains, split it.
|
||||
- Do not perform heavy bootstrap in route files. Delegate to classes/modules and keep handlers thin: validate → authorize → invoke → respond.
|
||||
1. Keep route handlers thin: validate input, enforce permissions, call domain code, and write the response.
|
||||
2. Default to protected endpoints. Use the existing authentication and permission helpers instead of ad hoc access checks.
|
||||
3. Add or update backend tests in the `php1` container whenever route behavior changes.
|
||||
4. Update `openapi.yaml` whenever paths, parameters, request bodies, response envelopes, or permissions change.
|
||||
5. Prefer deterministic route tests and avoid live external integrations in route coverage.
|
||||
6. Use concise API errors and keep sensitive implementation details out of the response body.
|
||||
7. When a change touches department, order, or subuser authorization, cover both the allow path and the deny path.
|
||||
|
||||
Canonical workflow reference: `.ai-workflow/workflow.md`.
|
||||
|
||||
@@ -3,31 +3,46 @@ version = 1
|
||||
name = "api"
|
||||
|
||||
[setup]
|
||||
script = "./scripts/setup.sh"
|
||||
script = '''
|
||||
./scripts/setup.sh
|
||||
'''
|
||||
|
||||
[setup.win32]
|
||||
script = "powershell -ExecutionPolicy Bypass -File .\\scripts\\setup.ps1"
|
||||
script = '''
|
||||
powershell -ExecutionPolicy Bypass -File .\scripts\setup.ps1
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "Start API"
|
||||
icon = "run"
|
||||
command = "powershell -ExecutionPolicy Bypass -File .\\scripts\\run.ps1 -Action start"
|
||||
platform = "win32"
|
||||
command = '''
|
||||
docker compose up -d traefik redis mysql-debug php1 caddy
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "Stop API"
|
||||
icon = "run"
|
||||
command = "powershell -ExecutionPolicy Bypass -File .\\scripts\\run.ps1 -Action stop"
|
||||
platform = "win32"
|
||||
command = '''
|
||||
docker compose down
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "PHP logs"
|
||||
icon = "debug"
|
||||
command = "powershell -ExecutionPolicy Bypass -File .\\scripts\\run.ps1 -Action logs -Service php1"
|
||||
platform = "win32"
|
||||
command = '''
|
||||
docker compose logs -f --tail=200 php1
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "Unit tests"
|
||||
name = "PHP unit tests"
|
||||
icon = "test"
|
||||
command = "powershell -ExecutionPolicy Bypass -File .\\scripts\\run.ps1 -Action test"
|
||||
platform = "win32"
|
||||
command = '''
|
||||
docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:unit"
|
||||
'''
|
||||
|
||||
[[actions]]
|
||||
name = "AI workflow check"
|
||||
icon = "debug"
|
||||
command = '''
|
||||
node scripts/sync-ai-workflow.mjs --check
|
||||
'''
|
||||
|
||||
@@ -31,6 +31,9 @@ CONFIG_DB_DATABASE=
|
||||
CONFIG_DB_PORT=3306
|
||||
CONFIG_DB_SSL_MODE=DISABLED
|
||||
|
||||
# Browser origins allowed to call the API. Path-like entries are normalized to origins by the PHP CORS policy.
|
||||
CORS=https://truckwash.io,https://www.truckwash.io,https://api.truckwash.io,https://api.truckwash.io:4433,https://api-v2.truckwash.io,https://web.truckwash.dk,https://api.truckwash.dk,https://truckwash.dk,https://www.truckwash.dk,https://staging.truckwash.io,http://localhost,https://localhost,http://localhost:4433,https://localhost:4433,https://twdev.jeppeb.dk,http://localhost:5173
|
||||
|
||||
# Debug DB credentials (used when CONFIG_DB_TARGET=debug)
|
||||
# Any blank debug value falls back to the live value above.
|
||||
CONFIG_DB_DEBUG_HOST=mysql-debug
|
||||
@@ -40,6 +43,19 @@ CONFIG_DB_DEBUG_DATABASE=nnks_db_debug
|
||||
CONFIG_DB_DEBUG_PORT=3306
|
||||
CONFIG_DB_DEBUG_SSL_MODE=DISABLED
|
||||
|
||||
# e-conomic credentials
|
||||
# Required: ECONOMIC_API_APP_ACCESS_GRANT and ECONOMIC_API_APP_SECRET_TOKEN
|
||||
# Optional-but-recommended: ECONOMIC_API_APP_ACCESS_GRANT2 (falls back to primary grant when blank)
|
||||
ECONOMIC_API_APP_ACCESS_GRANT=
|
||||
ECONOMIC_API_APP_ACCESS_GRANT2=
|
||||
ECONOMIC_API_APP_SECRET_TOKEN=
|
||||
|
||||
# Edge broker defaults for shell relay and gateway dispatch.
|
||||
EDGE_BROKER_URL=http://edge-broker:4300
|
||||
EDGE_PUBLIC_BROKER_URL=http://localhost/api/edge-broker
|
||||
EDGE_AUTH_MODE=manager
|
||||
EDGE_BROKER_SHARED_SECRET=truckwash-edge-dev
|
||||
|
||||
# Redis credentials
|
||||
REDIS_CONFIG_HOST=redis
|
||||
REDIS_CONFIG_DATABASE=0
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
* text=auto eol=lf
|
||||
|
||||
*.png binary
|
||||
*.jpg binary
|
||||
*.jpeg binary
|
||||
*.gif binary
|
||||
*.ico binary
|
||||
*.webp binary
|
||||
*.woff binary
|
||||
*.woff2 binary
|
||||
*.ttf binary
|
||||
*.otf binary
|
||||
*.pdf binary
|
||||
*.zip binary
|
||||
*.webm binary
|
||||
@@ -0,0 +1,48 @@
|
||||
USE_ENV=true
|
||||
DEBUG=true
|
||||
ENCRYPTION_KEY=ci-test-encryption-key
|
||||
CORS=*
|
||||
CONFIG_TIMEZONE=Europe/Copenhagen
|
||||
|
||||
CONFIG_DB_TARGET=debug
|
||||
CONFIG_DB_HOST=mysql-debug
|
||||
CONFIG_DB_USER=root
|
||||
CONFIG_DB_PASSWORD=debug_root_password
|
||||
CONFIG_DB_DATABASE=nnks_db_debug
|
||||
CONFIG_DB_PORT=3306
|
||||
CONFIG_DB_SSL_MODE=DISABLED
|
||||
CONFIG_DB_DEBUG_HOST=mysql-debug
|
||||
CONFIG_DB_DEBUG_USER=root
|
||||
CONFIG_DB_DEBUG_PASSWORD=debug_root_password
|
||||
CONFIG_DB_DEBUG_DATABASE=nnks_db_debug
|
||||
CONFIG_DB_DEBUG_PORT=3306
|
||||
CONFIG_DB_DEBUG_SSL_MODE=DISABLED
|
||||
|
||||
REDIS_CONFIG_HOST=redis
|
||||
REDIS_CONFIG_USER=default
|
||||
REDIS_CONFIG_DATABASE=0
|
||||
REDIS_CONFIG_PASSWORD=
|
||||
REDIS_CONFIG_PORT=6379
|
||||
REDIS_CONFIG_DEBUG_HOST=redis
|
||||
REDIS_CONFIG_DEBUG_USER=default
|
||||
REDIS_CONFIG_DEBUG_DATABASE=0
|
||||
REDIS_CONFIG_DEBUG_PASSWORD=
|
||||
REDIS_CONFIG_DEBUG_PORT=6379
|
||||
|
||||
ECONOMIC_API_APP_ACCESS_GRANT=ci-test
|
||||
ECONOMIC_API_APP_ACCESS_GRANT2=ci-test-secondary
|
||||
ECONOMIC_API_APP_SECRET_TOKEN=ci-test-secret
|
||||
WORDPRESS_STATIC_TOKEN=ci-test
|
||||
EMAIL_WASH_CERTIFICATE_TOKEN=ci-test
|
||||
WORDPRESS_API_URL=http://localhost
|
||||
MINIO_ENDPOINT=
|
||||
MINIO_ACCESS_KEY=
|
||||
MINIO_SECRET_KEY=
|
||||
SLACK_DEFAULT_WEBHOOK=
|
||||
|
||||
EDGE_BROKER_URL=http://edge-broker:4300
|
||||
EDGE_PUBLIC_BROKER_URL=http://localhost/api/edge-broker
|
||||
EDGE_AUTH_MODE=manager
|
||||
EDGE_BROKER_SHARED_SECRET=truckwash-edge-ci
|
||||
EDGE_GATEWAY_VIEW_CACHE_TTL=0
|
||||
TRUCKWASH_TEST_BLOCK_REAL_SHELLY=1
|
||||
@@ -0,0 +1,48 @@
|
||||
USE_ENV=true
|
||||
DEBUG=true
|
||||
ENCRYPTION_KEY=ci-test-encryption-key
|
||||
CORS=*
|
||||
CONFIG_TIMEZONE=Europe/Copenhagen
|
||||
|
||||
CONFIG_DB_TARGET=debug
|
||||
CONFIG_DB_HOST=mysql-debug
|
||||
CONFIG_DB_USER=root
|
||||
CONFIG_DB_PASSWORD=debug_root_password
|
||||
CONFIG_DB_DATABASE=nnks_db_debug
|
||||
CONFIG_DB_PORT=3306
|
||||
CONFIG_DB_SSL_MODE=DISABLED
|
||||
CONFIG_DB_DEBUG_HOST=mysql-debug
|
||||
CONFIG_DB_DEBUG_USER=root
|
||||
CONFIG_DB_DEBUG_PASSWORD=debug_root_password
|
||||
CONFIG_DB_DEBUG_DATABASE=nnks_db_debug
|
||||
CONFIG_DB_DEBUG_PORT=3306
|
||||
CONFIG_DB_DEBUG_SSL_MODE=DISABLED
|
||||
|
||||
REDIS_CONFIG_HOST=redis
|
||||
REDIS_CONFIG_USER=default
|
||||
REDIS_CONFIG_DATABASE=0
|
||||
REDIS_CONFIG_PASSWORD=
|
||||
REDIS_CONFIG_PORT=6379
|
||||
REDIS_CONFIG_DEBUG_HOST=redis
|
||||
REDIS_CONFIG_DEBUG_USER=default
|
||||
REDIS_CONFIG_DEBUG_DATABASE=0
|
||||
REDIS_CONFIG_DEBUG_PASSWORD=
|
||||
REDIS_CONFIG_DEBUG_PORT=6379
|
||||
|
||||
ECONOMIC_API_APP_ACCESS_GRANT=ci-test
|
||||
ECONOMIC_API_APP_ACCESS_GRANT2=ci-test-secondary
|
||||
ECONOMIC_API_APP_SECRET_TOKEN=ci-test-secret
|
||||
WORDPRESS_STATIC_TOKEN=ci-test
|
||||
EMAIL_WASH_CERTIFICATE_TOKEN=ci-test
|
||||
WORDPRESS_API_URL=http://localhost
|
||||
MINIO_ENDPOINT=
|
||||
MINIO_ACCESS_KEY=
|
||||
MINIO_SECRET_KEY=
|
||||
SLACK_DEFAULT_WEBHOOK=
|
||||
|
||||
EDGE_BROKER_URL=http://edge-broker:4300
|
||||
EDGE_PUBLIC_BROKER_URL=http://localhost/api/edge-broker
|
||||
EDGE_AUTH_MODE=manager
|
||||
EDGE_BROKER_SHARED_SECRET=truckwash-edge-ci
|
||||
EDGE_GATEWAY_VIEW_CACHE_TTL=0
|
||||
TRUCKWASH_TEST_BLOCK_REAL_SHELLY=1
|
||||
@@ -0,0 +1,82 @@
|
||||
services:
|
||||
traefik:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-traefik"
|
||||
|
||||
redis:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-redis"
|
||||
|
||||
mysql-debug:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-mysql-debug"
|
||||
ports: !reset []
|
||||
|
||||
edge-broker:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-edge-broker"
|
||||
labels:
|
||||
- "traefik.http.routers.edge-broker-local-ci.rule=PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local-ci.entrypoints=web"
|
||||
- "traefik.http.routers.edge-broker-local-ci.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local-ci.priority=190"
|
||||
- "traefik.http.routers.edge-broker-local-ci.service=edge-broker"
|
||||
|
||||
caddy:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-caddy"
|
||||
depends_on: !reset []
|
||||
labels:
|
||||
- "traefik.http.routers.local-api-ci.rule=PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-api-ci.entrypoints=web"
|
||||
- "traefik.http.routers.local-api-ci.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-api-ci.priority=90"
|
||||
- "traefik.http.routers.local-api-ci.service=caddy"
|
||||
volumes:
|
||||
- ci_php_app:/var/www/html
|
||||
|
||||
php1:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-php1"
|
||||
depends_on: !reset []
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
USE_ENV: "true"
|
||||
CONFIG_DB_TARGET: "debug"
|
||||
CONFIG_DB_HOST: "mysql-debug"
|
||||
CONFIG_DB_USER: "root"
|
||||
CONFIG_DB_PASSWORD: "debug_root_password"
|
||||
CONFIG_DB_DATABASE: "nnks_db_debug"
|
||||
CONFIG_DB_PORT: "3306"
|
||||
CONFIG_DB_DEBUG_HOST: "mysql-debug"
|
||||
CONFIG_DB_DEBUG_USER: "root"
|
||||
CONFIG_DB_DEBUG_PASSWORD: "debug_root_password"
|
||||
CONFIG_DB_DEBUG_DATABASE: "nnks_db_debug"
|
||||
CONFIG_DB_DEBUG_PORT: "3306"
|
||||
REDIS_CONFIG_HOST: "redis"
|
||||
REDIS_CONFIG_PORT: "6379"
|
||||
REDIS_CONFIG_DATABASE: "0"
|
||||
REDIS_CONFIG_DEBUG_HOST: "redis"
|
||||
REDIS_CONFIG_DEBUG_PORT: "6379"
|
||||
REDIS_CONFIG_DEBUG_DATABASE: "0"
|
||||
TRUCKWASH_TEST_BLOCK_REAL_SHELLY: "1"
|
||||
EDGE_GATEWAY_VIEW_CACHE_TTL: "0"
|
||||
volumes:
|
||||
- ci_php_app:/var/www/html
|
||||
|
||||
php2:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-php2"
|
||||
volumes:
|
||||
- ci_php_app:/var/www/html
|
||||
|
||||
php3:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-php3"
|
||||
volumes:
|
||||
- ci_php_app:/var/www/html
|
||||
|
||||
php4:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-php4"
|
||||
volumes:
|
||||
- ci_php_app:/var/www/html
|
||||
|
||||
php5:
|
||||
container_name: "${COMPOSE_PROJECT_NAME:-api}-php5"
|
||||
volumes:
|
||||
- ci_php_app:/var/www/html
|
||||
|
||||
volumes:
|
||||
ci_php_app:
|
||||
@@ -9,20 +9,43 @@ on:
|
||||
|
||||
jobs:
|
||||
qodana:
|
||||
runs-on: ubuntu-latest
|
||||
# Run on our self-hosted runner to avoid GitHub-hosted Actions budget limits.
|
||||
runs-on: [self-hosted, Linux, X64, default]
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
checks: write
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }} # to check out the actual pull request commit, not the merge commit
|
||||
ref: ${{ github.event.pull_request.head.sha || github.sha }} # Use PR head when available, otherwise the pushed SHA.
|
||||
fetch-depth: 0 # a full history is required for pull request analysis
|
||||
- name: Mark repository as safe for Git
|
||||
run: git config --global --add safe.directory "$GITHUB_WORKSPACE"
|
||||
- name: Prepare Qodana cache directories
|
||||
run: |
|
||||
mkdir -p "${RUNNER_TEMP}/qodana/caches"
|
||||
mkdir -p "${RUNNER_TEMP}/qodana/results"
|
||||
- name: Detect Qodana Cloud token
|
||||
id: qodana-token
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
run: |
|
||||
if [ -n "${QODANA_TOKEN:-}" ]; then
|
||||
echo "present=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "present=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: 'Qodana Scan'
|
||||
uses: JetBrains/qodana-action@v2025.3
|
||||
if: ${{ steps.qodana-token.outputs.present == 'true' }}
|
||||
uses: JetBrains/qodana-action@v2026.1
|
||||
with:
|
||||
pr-mode: false
|
||||
env:
|
||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||
QODANA_ENDPOINT: 'https://qodana.cloud'
|
||||
|
||||
- name: 'Skip Qodana Scan (missing cloud token)'
|
||||
if: ${{ steps.qodana-token.outputs.present != 'true' }}
|
||||
run: echo "Skipping Qodana because QODANA_TOKEN is not configured for this repository."
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
issues: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create GitHub Issue for Copilot
|
||||
env:
|
||||
@@ -22,7 +22,18 @@ jobs:
|
||||
TASK: ${{ github.event.inputs.task }}
|
||||
ACTOR: ${{ github.actor }}
|
||||
run: |
|
||||
BODY="$(cat <<EOF
|
||||
Assigned to Copilot by @$ACTOR.
|
||||
|
||||
Task
|
||||
$TASK
|
||||
|
||||
AI workflow notes
|
||||
- Generated assistant metadata is synchronized from the canonical AI workflow bundle.
|
||||
- Run `node scripts/sync-ai-workflow.mjs --check` if assistant metadata changed.
|
||||
EOF
|
||||
)"
|
||||
gh issue create \
|
||||
--title "Copilot Task: $TASK" \
|
||||
--body "Assigned to Copilot by @$ACTOR. Description: $TASK" \
|
||||
--body "$BODY" \
|
||||
--label "copilot-task"
|
||||
|
||||
+254
-60
@@ -3,90 +3,284 @@ name: Tests
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
jobs:
|
||||
unit:
|
||||
name: Unit (required)
|
||||
php:
|
||||
name: PHP ${{ matrix.suite }} (required)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
suite: [unit, integration, api, legacy]
|
||||
env:
|
||||
COMPOSE_PROJECT_NAME: php-${{ github.run_id }}-${{ github.job }}-${{ matrix.suite }}-${{ github.run_attempt }}
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
- name: Setup Node.js
|
||||
if: ${{ matrix.suite == 'unit' }}
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
php-version: '8.2'
|
||||
extensions: mysqli, curl, openssl, json, redis, pcov
|
||||
coverage: pcov
|
||||
node-version: 22
|
||||
|
||||
- name: Resolve dependencies
|
||||
working-directory: services/nginx/app
|
||||
run: composer update --no-interaction --prefer-dist
|
||||
- name: Check AI workflow sync
|
||||
if: ${{ matrix.suite == 'unit' }}
|
||||
run: node scripts/sync-ai-workflow.mjs --check
|
||||
|
||||
- name: Run unit tests
|
||||
working-directory: services/nginx/app
|
||||
run: composer test:unit
|
||||
- name: Run PHP ${{ matrix.suite }} suite
|
||||
run: bash scripts/php-ci-test.sh ${{ matrix.suite }}
|
||||
|
||||
- name: Generate coverage report
|
||||
working-directory: services/nginx/app
|
||||
run: composer test:coverage
|
||||
|
||||
- name: Upload coverage artifact
|
||||
- name: Upload PHP suite logs
|
||||
if: ${{ failure() }}
|
||||
continue-on-error: true
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unit-coverage-clover
|
||||
path: services/nginx/app/build/logs/clover.xml
|
||||
name: php-${{ matrix.suite }}-logs
|
||||
path: .tmp/ci-logs/${{ matrix.suite }}
|
||||
if-no-files-found: warn
|
||||
retention-days: 3
|
||||
|
||||
integration:
|
||||
name: Integration (advisory)
|
||||
edge-agent:
|
||||
name: Edge Agent (required)
|
||||
runs-on: ubuntu-latest
|
||||
continue-on-error: true
|
||||
|
||||
services:
|
||||
redis:
|
||||
image: redis:7
|
||||
ports:
|
||||
- 6379:6379
|
||||
mysql:
|
||||
image: mysql:8
|
||||
env:
|
||||
MYSQL_DATABASE: app_test
|
||||
MYSQL_ROOT_PASSWORD: root
|
||||
ports:
|
||||
- 3306:3306
|
||||
options: >-
|
||||
--health-cmd="mysqladmin ping -h 127.0.0.1 -proot"
|
||||
--health-interval=10s
|
||||
--health-timeout=5s
|
||||
--health-retries=10
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
php-version: '8.2'
|
||||
extensions: mysqli, curl, openssl, json, redis
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: services/edge-agent/package-lock.json
|
||||
|
||||
- name: Install native build tools
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if command -v make >/dev/null 2>&1 && command -v g++ >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if ! command -v apt-get >/dev/null 2>&1; then
|
||||
echo "make and g++ are required to install node-pty, but apt-get is not available on this runner." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
apt_cmd=(apt-get)
|
||||
if [ "$(id -u)" -ne 0 ]; then
|
||||
if ! command -v sudo >/dev/null 2>&1; then
|
||||
echo "make and g++ are missing, and sudo is not available to install them." >&2
|
||||
exit 1
|
||||
fi
|
||||
apt_cmd=(sudo apt-get)
|
||||
fi
|
||||
|
||||
"${apt_cmd[@]}" update
|
||||
"${apt_cmd[@]}" install -y --no-install-recommends build-essential python3
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: services/edge-agent
|
||||
run: npm ci
|
||||
|
||||
- name: Run edge agent tests
|
||||
working-directory: services/edge-agent
|
||||
run: npm test
|
||||
|
||||
edge-broker:
|
||||
name: Edge Broker (required)
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Materialize CI compose env files
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cp .github/ci.env .env
|
||||
cp .github/ci.env.staging .env.staging
|
||||
|
||||
- name: Validate compose contracts
|
||||
run: |
|
||||
docker compose -f docker-compose.yml -f docker-compose.prod.yml config > /dev/null
|
||||
docker compose -f docker-compose.example.yml config > /dev/null
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
cache: npm
|
||||
cache-dependency-path: services/edge-broker/package-lock.json
|
||||
|
||||
- name: Install dependencies
|
||||
working-directory: services/edge-broker
|
||||
run: npm ci
|
||||
|
||||
- name: Run edge broker tests
|
||||
working-directory: services/edge-broker
|
||||
run: npm test
|
||||
|
||||
edge-gateway-backend:
|
||||
name: Edge Gateway Backend (required)
|
||||
runs-on: ubuntu-latest
|
||||
env:
|
||||
COMPOSE_FILE: docker-compose.yml:.github/docker-compose.ci.yml
|
||||
COMPOSE_PROJECT_NAME: edge-gateway-backend-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
TRAEFIK_WEB_PORT: "18080"
|
||||
TRAEFIK_WEBSECURE_PORT: "18443"
|
||||
TRAEFIK_WEBSECURE_STAGING_PORT: "18433"
|
||||
TRAEFIK_METRICS_PORT: "19100"
|
||||
EDGE_GATEWAY_E2E_BASE_URL: "http://localhost:18080/api"
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Materialize CI compose env files
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cp .github/ci.env .env
|
||||
cp .github/ci.env.staging .env.staging
|
||||
printf '\nEDGE_PUBLIC_BROKER_URL=http://edge-broker:4300\n' >> .env
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22
|
||||
|
||||
- name: Boot local stack
|
||||
run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml up -d traefik redis mysql-debug edge-broker php1 php2 php3 php4 php5 caddy
|
||||
|
||||
- name: Sync PHP app checkout
|
||||
run: >
|
||||
tar
|
||||
--exclude='./vendor'
|
||||
--exclude='./.phpunit.cache'
|
||||
--exclude='./build/logs'
|
||||
-C services/nginx/app -cf - .
|
||||
| docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 tar -C /var/www/html -xf -
|
||||
|
||||
- name: Resolve dependencies
|
||||
working-directory: services/nginx/app
|
||||
run: composer update --no-interaction --prefer-dist
|
||||
run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc "cd /var/www/html && composer install --no-interaction --prefer-dist --no-progress"
|
||||
|
||||
- name: Run integration tests
|
||||
working-directory: services/nginx/app
|
||||
- name: Verify edge gateway test files
|
||||
run: >
|
||||
docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc
|
||||
"cd /var/www/html &&
|
||||
php -r '\$composer = json_decode(file_get_contents(\"composer.json\"), true); echo \"Composer scripts: \", implode(\",\", array_keys(\$composer[\"scripts\"] ?? [])), PHP_EOL;' &&
|
||||
find tests/Api -maxdepth 1 -type f -name 'EdgeGateway*ApiTest.php' -print &&
|
||||
test -f tests/Api/EdgeGatewayAgentApiTest.php &&
|
||||
test -f tests/Api/EdgeGatewayBrokerApiTest.php &&
|
||||
test -f tests/Api/EdgeGatewayOperatorApiTest.php"
|
||||
|
||||
- name: Run edge gateway API tests
|
||||
run: >
|
||||
docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc
|
||||
"cd /var/www/html &&
|
||||
RUN_API_TESTS=1
|
||||
API_TEST_BOOTSTRAP_SCHEMA=1
|
||||
API_TEST_ALLOW_LIVE_DB=1
|
||||
CONFIG_DB_TARGET=debug
|
||||
CONFIG_DB_HOST=mysql-debug
|
||||
CONFIG_DB_USER=\${CONFIG_DB_USER:-root}
|
||||
CONFIG_DB_PASSWORD=\${CONFIG_DB_PASSWORD:-debug_root_password}
|
||||
CONFIG_DB_DATABASE=\${CONFIG_DB_DATABASE:-nnks_db_debug}
|
||||
CONFIG_DB_PORT=3306
|
||||
CONFIG_DB_DEBUG_HOST=mysql-debug
|
||||
CONFIG_DB_DEBUG_USER=\${CONFIG_DB_DEBUG_USER:-root}
|
||||
CONFIG_DB_DEBUG_PASSWORD=\${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}
|
||||
CONFIG_DB_DEBUG_DATABASE=\${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}
|
||||
CONFIG_DB_DEBUG_PORT=3306
|
||||
API_TEST_REQUEST_TIMEOUT=180
|
||||
EDGE_GATEWAY_VIEW_CACHE_TTL=0
|
||||
EDGE_BROKER_URL=
|
||||
vendor/bin/pest
|
||||
tests/Api/EdgeGatewayAgentApiTest.php
|
||||
tests/Api/EdgeGatewayBrokerApiTest.php
|
||||
tests/Api/EdgeGatewayOperatorApiTest.php
|
||||
--colors=always"
|
||||
|
||||
- name: Run edge gateway integration tests
|
||||
run: >
|
||||
docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml exec -T php1 sh -lc
|
||||
"cd /var/www/html &&
|
||||
RUN_INTEGRATION_TESTS=1
|
||||
CONFIG_DB_TARGET=debug
|
||||
CONFIG_DB_HOST=mysql-debug
|
||||
CONFIG_DB_USER=\${CONFIG_DB_USER:-root}
|
||||
CONFIG_DB_PASSWORD=\${CONFIG_DB_PASSWORD:-debug_root_password}
|
||||
CONFIG_DB_DATABASE=\${CONFIG_DB_DATABASE:-nnks_db_debug}
|
||||
CONFIG_DB_PORT=3306
|
||||
CONFIG_DB_DEBUG_HOST=mysql-debug
|
||||
CONFIG_DB_DEBUG_USER=\${CONFIG_DB_DEBUG_USER:-root}
|
||||
CONFIG_DB_DEBUG_PASSWORD=\${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}
|
||||
CONFIG_DB_DEBUG_DATABASE=\${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}
|
||||
CONFIG_DB_DEBUG_PORT=3306
|
||||
EDGE_BROKER_URL=
|
||||
vendor/bin/pest tests/Integration/EdgeGateway --colors=always"
|
||||
|
||||
- name: Run edge gateway E2E smoke
|
||||
run: |
|
||||
set -euo pipefail
|
||||
compose_project="${COMPOSE_PROJECT_NAME:-$(basename "$PWD")}"
|
||||
runner="edge-e2e-runner-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
|
||||
docker rm -f "$runner" >/dev/null 2>&1 || true
|
||||
trap 'docker rm -f "$runner" >/dev/null 2>&1 || true' EXIT
|
||||
docker create \
|
||||
--name "$runner" \
|
||||
--network "${compose_project}_default" \
|
||||
-e COMPOSE_FILE="$COMPOSE_FILE" \
|
||||
-e COMPOSE_PROJECT_NAME="$compose_project" \
|
||||
-e TRAEFIK_WEB_PORT="${TRAEFIK_WEB_PORT:-18080}" \
|
||||
-e TRAEFIK_WEBSECURE_PORT="${TRAEFIK_WEBSECURE_PORT:-18443}" \
|
||||
-e TRAEFIK_WEBSECURE_STAGING_PORT="${TRAEFIK_WEBSECURE_STAGING_PORT:-18433}" \
|
||||
-e TRAEFIK_METRICS_PORT="${TRAEFIK_METRICS_PORT:-19100}" \
|
||||
-e EDGE_GATEWAY_E2E_BASE_URL="http://caddy" \
|
||||
-e EDGE_GATEWAY_E2E_COMPOSE_PROJECT="$compose_project" \
|
||||
-e EDGE_GATEWAY_E2E_COPY_CONFIG="true" \
|
||||
-e EDGE_GATEWAY_E2E_SKIP_COMPOSE_UP="true" \
|
||||
-v /var/run/docker.sock:/var/run/docker.sock \
|
||||
-w /workspace \
|
||||
node:22-alpine \
|
||||
sh -lc "apk add --no-cache docker-cli docker-cli-compose >/dev/null && node scripts/edge-gateway-e2e.mjs"
|
||||
docker cp . "$runner:/workspace"
|
||||
docker start "$runner" >/dev/null
|
||||
docker logs -f "$runner"
|
||||
exit_code="$(docker wait "$runner")"
|
||||
exit "$exit_code"
|
||||
|
||||
- name: Tear down local stack
|
||||
if: always()
|
||||
run: docker compose -f docker-compose.yml -f .github/docker-compose.ci.yml down -v
|
||||
|
||||
release-manager-gate:
|
||||
name: Release Manager gate
|
||||
runs-on: ubuntu-latest
|
||||
needs: [php, edge-agent, edge-broker, edge-gateway-backend]
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
|
||||
steps:
|
||||
- name: Record Release Manager API gate
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test -n "$RELEASE_MANAGER_GATE_TOKEN" || (echo "RELEASE_MANAGER_GATE_TOKEN is required" >&2; exit 1)
|
||||
curl --fail --show-error --silent \
|
||||
--connect-timeout 10 \
|
||||
--retry 5 \
|
||||
--retry-all-errors \
|
||||
--retry-delay 15 \
|
||||
--retry-max-time 300 \
|
||||
-X POST "$RELEASE_MANAGER_GATE_URL" \
|
||||
-H "Authorization: Bearer $RELEASE_MANAGER_GATE_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
--data "{\"channel_slug\":\"stable\",\"app\":\"api\",\"repository\":\"$RELEASE_REPOSITORY\",\"branch\":\"$RELEASE_BRANCH\",\"expected_commit\":\"$RELEASE_EXPECTED_COMMIT\",\"workflow_url\":\"$RELEASE_WORKFLOW_URL\",\"auto_sync\":true,\"wait_timeout_seconds\":300,\"poll_interval_seconds\":10,\"required_checks\":[]}"
|
||||
env:
|
||||
RUN_INTEGRATION_TESTS: '1'
|
||||
REDIS_CONFIG_HOST: 127.0.0.1
|
||||
REDIS_CONFIG_DATABASE: '0'
|
||||
REDIS_CONFIG_PASSWORD: ''
|
||||
CONFIG_DB_HOST: 127.0.0.1
|
||||
CONFIG_DB_USER: root
|
||||
CONFIG_DB_PASSWORD: root
|
||||
CONFIG_DB_DATABASE: app_test
|
||||
run: composer test:integration
|
||||
RELEASE_MANAGER_GATE_URL: ${{ secrets.RELEASE_MANAGER_GATE_URL || 'https://api.truckwash.io/release/gate/test-runs' }}
|
||||
RELEASE_MANAGER_GATE_TOKEN: ${{ secrets.RELEASE_MANAGER_GATE_TOKEN }}
|
||||
RELEASE_REPOSITORY: ${{ github.repository }}
|
||||
RELEASE_BRANCH: ${{ github.ref_name }}
|
||||
RELEASE_EXPECTED_COMMIT: ${{ github.sha }}
|
||||
RELEASE_WORKFLOW_URL: https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
||||
|
||||
+5
-1
@@ -9,4 +9,8 @@
|
||||
/services/php/logs/
|
||||
/.idea/
|
||||
.env
|
||||
/services/caddy/logs*
|
||||
/services/caddy/logs*
|
||||
.env.old
|
||||
/.tmp/
|
||||
/.env.staging
|
||||
/services/nginx/app/storage/replication-bootstrap.json
|
||||
+19
-139
@@ -1,148 +1,28 @@
|
||||
### Copenhagen Truck Wash API — Development Guidelines (Project‑specific)
|
||||
<!-- AUTOGENERATED: Run `node scripts/sync-ai-workflow.mjs --write`. -->
|
||||
|
||||
#### Scope
|
||||
This document captures project‑specific knowledge for building, configuring, testing, and extending the API. It assumes an advanced developer familiar with Docker, PHP 8.2, and HTTP APIs.
|
||||
# Copenhagen Truck Wash API Development Guidelines
|
||||
|
||||
---
|
||||
This file is generated from the canonical AI workflow and is the supported Junie-facing reference for the backend repository.
|
||||
|
||||
### Build and Configuration
|
||||
## Build And Run
|
||||
|
||||
- Stack overview (local):
|
||||
- Reverse proxy/router: Traefik 2.x (`docker-compose.yml` service `traefik`).
|
||||
- Web server: Caddy (`caddy`) serving the PHP app from `services/nginx/app` and proxied by Traefik.
|
||||
- PHP runtime: Multiple PHP‑FPM containers (`php1`..`php5`), sharing the bind‑mounted app directory.
|
||||
- Redis: `redis` for caching/queues/locks.
|
||||
- Project root: `services/nginx/app` is the effective PHP application root.
|
||||
- Setup: use `./scripts/setup.sh` on POSIX or `powershell -ExecutionPolicy Bypass -File .\scripts\setup.ps1` on Windows.
|
||||
- Start local API stack: `docker compose up -d traefik redis mysql-debug php1 caddy`.
|
||||
- Tail logs with `docker compose logs -f --tail=200 php1`.
|
||||
|
||||
- App location: `services/nginx/app` is the effective application root (many scripts/tests derive `WD` to point here).
|
||||
## Testing
|
||||
|
||||
- Composer and dependencies:
|
||||
- `services/php/Dockerfile` installs Composer and PHP extensions.
|
||||
- `services/php/docker-entrypoint.sh` performs a guarded Composer install on `php1` at container start when `AUTO_COMPOSER_INSTALL=true` and `composer.json` is present.
|
||||
- App dependencies live under `services/nginx/app/composer.json` (note: very light, primarily runtime libs; dev tool `rector/rector`).
|
||||
- Supported backend validation runs in `php1`.
|
||||
- Unit tests: `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:unit"`.
|
||||
- Integration tests: `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:integration"`.
|
||||
- API tests: `docker compose exec -T php1 sh -lc "cd /var/www/html && composer test:api"`.
|
||||
- Prefer the narrowest suite that proves the change, then run the broader suite that matches the risk.
|
||||
|
||||
- Configuration source of truth during containerized runs is environment variables consumed by `services/nginx/app/config.php`.
|
||||
- `config.php` requires `USE_ENV=true`; otherwise it throws an exception. Many CLI scripts/tests bypass `config.php` entirely to remain env‑agnostic.
|
||||
## Workflow Notes
|
||||
|
||||
- Local run targets and routing:
|
||||
- Traefik exposes:
|
||||
- `http://localhost` → routes to Caddy → app (HTTP only for dev).
|
||||
- `https://localhost` → also mapped, using Traefik’s default/self‑signed dev cert.
|
||||
- `http(s)://localhost/api/*` → Traefik strip‑prefix middleware forwards to Caddy; the app sees paths without the `/api` prefix.
|
||||
- For production‑like HTTPS with real certs, see `README.md` for `LETSENCRYPT_PATH` mounting strategy (only needed if you want the exact `api.truckwash.dk` TLS behavior locally).
|
||||
- Generated assistant metadata is checked with `node scripts/sync-ai-workflow.mjs --check`.
|
||||
- Route and schema changes require matching updates to `openapi.yaml`.
|
||||
- Runtime OpenAI product behavior is out of scope for this workflow bundle unless a task explicitly changes product code.
|
||||
|
||||
- Minimal bring‑up for local development:
|
||||
- Prerequisites: Docker Desktop 4.x+.
|
||||
- First run will build PHP images and start dependent services. Composer install runs automatically on `php1`.
|
||||
- Recommended minimal set:
|
||||
- `docker compose up -d traefik redis php1 caddy`
|
||||
- Full set (scale out PHP or add observability as needed):
|
||||
- `docker compose up -d` (starts `traefik`, `redis`, `caddy`, `php1`..`php5`, and other declared services).
|
||||
- Logs:
|
||||
- `docker compose logs -f caddy`
|
||||
- `docker compose logs -f php1`
|
||||
- `docker compose logs -f traefik`
|
||||
|
||||
- Security note: `docker-compose.yml` currently embeds sensitive env values (DB, API tokens). Treat the file as secret in private repos; never re‑publish as is. Prefer `.env` overrides and secrets providers for wider teams.
|
||||
|
||||
---
|
||||
|
||||
### Testing
|
||||
|
||||
The repository does not use PHPUnit for the app. Instead, tests are lightweight CLI scripts under `services/nginx/app/tests`. Conventions:
|
||||
|
||||
- Test style
|
||||
- Self‑contained procedural PHP scripts intended to be executed with `php`. No framework required.
|
||||
- Many tests define the `WD` constant to the app root and then `require_once` specific class/trait/interface files they exercise.
|
||||
- Integration‑style scripts that need configuration will rely on `services/nginx/app/config.php` and so must run within a properly provisioned environment (Docker containers with `USE_ENV=true`).
|
||||
- Fast unit‑style scripts should avoid `config.php` and any I/O; they manually include only what’s needed and/or use test doubles.
|
||||
|
||||
- Running tests from host (fastest path)
|
||||
- Prereq: PHP CLI available on host. Verified with:
|
||||
- `php -v` → observed on our env: `PHP 8.2.30 (cli)`
|
||||
- Example: an existing, fully self‑contained test validating subuser permission wiring:
|
||||
- Command:
|
||||
- `php services/nginx/app/tests/subusers/SelfservePermissionInitTest.php`
|
||||
- Verified output (captured):
|
||||
-
|
||||
```
|
||||
✔ SELFSERVE_ADD is granted as expected
|
||||
✔ SELFSERVE_LIST is not granted as expected
|
||||
✔ SELFSERVE_EDIT is not granted as expected
|
||||
✔ SELFSERVEDELETE is not granted as expected
|
||||
SelfservePermissionInitTest completed.
|
||||
```
|
||||
|
||||
- Running tests inside Docker (when host PHP is unavailable or when env is required)
|
||||
- Ensure containers are up: `docker compose up -d traefik redis php1 caddy`
|
||||
- Execute a test within `php1`:
|
||||
- `docker compose exec -T php1 php /var/www/html/tests/subusers/SelfservePermissionInitTest.php`
|
||||
- For integration tests that depend on `config.php` (e.g., Redis/DB), environment variables are preconfigured in `docker-compose.yml` for the PHP services. Running inside `php1` will satisfy `USE_ENV=true`.
|
||||
|
||||
- Adding a new test
|
||||
- Place the file under `services/nginx/app/tests/<domain>/YourTest.php`.
|
||||
- At top of the file, define `WD` if not already defined:
|
||||
```php
|
||||
if (!defined('WD')) { define('WD', dirname(__DIR__, 2)); }
|
||||
```
|
||||
- Prefer self‑contained tests that avoid I/O. If you need to touch internal classes without Composer autoloading, include files directly, mirroring existing tests.
|
||||
- If the test must hit Redis/DB or rely on globals from `config.php`, run it inside a PHP container (`php1`) with `USE_ENV=true`.
|
||||
|
||||
- Demonstration: creating and running a minimal test
|
||||
- We created a temporary, env‑agnostic test at `services/nginx/app/tests/examples/HelloWorldTest.php` with these semantics:
|
||||
- Define `WD`, perform trivial assertions, and print success markers.
|
||||
- Command executed and verified output:
|
||||
- Command:
|
||||
- `php services/nginx/app/tests/examples/HelloWorldTest.php`
|
||||
- Output (captured):
|
||||
-
|
||||
```
|
||||
✔ Basic arithmetic works (2 + 2 = 4)
|
||||
✔ WD points to the application root: C:\\Users\\2jepp\\PhpstormProjects\\api\\services\\nginx\\app
|
||||
HelloWorldTest completed.
|
||||
```
|
||||
- The example file was removed afterwards to keep the repository unchanged. You can replicate by creating a similar file and removing it after execution.
|
||||
|
||||
---
|
||||
|
||||
### Additional Development Information
|
||||
|
||||
- Routing and HTTP surface
|
||||
- Routes live under `services/nginx/app/routes`. Example route `exampleRoute.php` exposes `GET /example` returning `{"message":"Hello World!"}`. Local access paths (with Traefik):
|
||||
- `http://localhost/example` (direct)
|
||||
- `http://localhost/api/example` (Traefik strip‑prefix `/api` → still routes to `/example` in the app).
|
||||
- The OpenAPI contract is at the repo root `openapi.yaml` (large, authoritative). Keep it synchronized with implemented routes and payloads.
|
||||
|
||||
- Module layout and traits
|
||||
- Domain modules live in `services/nginx/app/modules/*` and are heavily trait‑based. Tests often pull in precise files from here to avoid full app bootstrap.
|
||||
- Example: subusers module (`modules/subusers/...`) provides permission node containers and helpers. The test `tests/subusers/SelfservePermissionInitTest.php` demonstrates overriding DB‑backed methods to inject permissions for fast, deterministic checks.
|
||||
|
||||
- Config and globals
|
||||
- `services/nginx/app/config.php` populates globals like `$CONFIG_DB`, `$REDIS_CONFIG`, etc., but only when `USE_ENV=true`. If you see tests throwing “Environment variables are not set”, run them inside Docker or set required envs for host PHP.
|
||||
|
||||
- Code style
|
||||
- `.editorconfig` at repo root configures formatting. Key PHP rules:
|
||||
- Encoding: UTF‑8 with CRLF line endings.
|
||||
- Indent: 4 spaces; continuation indent 4.
|
||||
- Class brace style: next_line; function/method blank lines: 1.
|
||||
- Import sorting: alphabetic; various alignment toggles are disabled.
|
||||
- Match existing patterns (procedural scripts for tests; namespacing in app code; traits for cross‑cutting concerns). Avoid adding new frameworks for tests unless explicitly requested.
|
||||
|
||||
- Composer / Autoload
|
||||
- There is no Composer autoload configured for the app code in `composer.json`; tests include files directly. If you introduce autoloading, coordinate with Docker entrypoint behaviors and ensure zero‑downtime for existing scripts.
|
||||
|
||||
- Performance & reliability hints for tests
|
||||
- Prefer small, deterministic CLI scripts; inject dependencies and override I/O methods (as shown in `SelfservePermissionInitTest.php`).
|
||||
- Avoid hitting external services (Stripe, e‑conomic, MinIO) from tests; instead, stub/override or provide fakes.
|
||||
|
||||
---
|
||||
|
||||
### Quick Reference
|
||||
|
||||
- Bring up minimal dev stack:
|
||||
- `docker compose up -d traefik redis php1 caddy`
|
||||
|
||||
- Run a fast, self‑contained test (host PHP):
|
||||
- `php services/nginx/app/tests/subusers/SelfservePermissionInitTest.php`
|
||||
|
||||
- Run a test inside Docker (env‑backed):
|
||||
- `docker compose exec -T php1 php /var/www/html/tests/subusers/SelfservePermissionInitTest.php`
|
||||
Canonical workflow reference: `.ai-workflow/workflow.md`.
|
||||
|
||||
Binary file not shown.
+3
-2
@@ -46,7 +46,8 @@ COPY --from=composer:2.6 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
# Runtime bootstrap: lightweight entrypoint to ensure Composer deps exist when app is bind-mounted
|
||||
COPY services/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
RUN sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh \
|
||||
&& chmod +x /usr/local/bin/docker-entrypoint.sh
|
||||
|
||||
# Install PHP dependencies through Composer (only where composer.json exists)
|
||||
# Main app dependencies
|
||||
@@ -72,4 +73,4 @@ EXPOSE 80 443
|
||||
ENTRYPOINT ["docker-entrypoint.sh"]
|
||||
|
||||
# Start services when no command is provided (docker-compose overrides this with ["php-fpm"])
|
||||
CMD ["php-fpm"]
|
||||
CMD ["php-fpm"]
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
FROM php:8.2.15-fpm
|
||||
|
||||
WORKDIR /var/www/html
|
||||
|
||||
COPY --from=composer:2.6 /usr/bin/composer /usr/bin/composer
|
||||
|
||||
RUN set -eux; \
|
||||
apt-get update; \
|
||||
apt-get install -y --no-install-recommends \
|
||||
$PHPIZE_DEPS \
|
||||
ca-certificates \
|
||||
curl \
|
||||
default-mysql-client \
|
||||
git \
|
||||
imagemagick \
|
||||
libfreetype6-dev \
|
||||
libjpeg62-turbo-dev \
|
||||
libmagickcore-dev \
|
||||
libmagickwand-dev \
|
||||
libonig-dev \
|
||||
libpng-dev \
|
||||
libssl-dev \
|
||||
libxml2-dev \
|
||||
libzip-dev \
|
||||
mariadb-client \
|
||||
nginx \
|
||||
pkg-config \
|
||||
redis-tools \
|
||||
unzip \
|
||||
zip; \
|
||||
update-ca-certificates; \
|
||||
docker-php-ext-configure gd --with-freetype --with-jpeg; \
|
||||
docker-php-ext-install -j"$(nproc)" \
|
||||
bcmath \
|
||||
exif \
|
||||
gd \
|
||||
mbstring \
|
||||
mysqli \
|
||||
pcntl \
|
||||
pdo_mysql \
|
||||
sockets \
|
||||
zip; \
|
||||
pecl install imagick-3.7.0 redis; \
|
||||
docker-php-ext-enable imagick redis; \
|
||||
apt-get purge -y --auto-remove -o APT::AutoRemove::RecommendsImportant=false $PHPIZE_DEPS; \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
COPY services/nginx/app/ /var/www/html/
|
||||
COPY services/php/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh
|
||||
COPY services/coolify/api/nginx.conf /etc/nginx/nginx.conf
|
||||
COPY services/coolify/api/start.sh /usr/local/bin/coolify-api-start
|
||||
|
||||
RUN set -eux; \
|
||||
sed -i 's/\r$//' /usr/local/bin/docker-entrypoint.sh /usr/local/bin/coolify-api-start; \
|
||||
chmod +x /usr/local/bin/docker-entrypoint.sh /usr/local/bin/coolify-api-start; \
|
||||
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction -d /var/www/html; \
|
||||
if [ -f /var/www/html/modules/washcertificates/composer.json ]; then \
|
||||
COMPOSER_ALLOW_SUPERUSER=1 composer install --no-dev --prefer-dist --optimize-autoloader --no-interaction -d /var/www/html/modules/washcertificates; \
|
||||
fi; \
|
||||
COMPOSER_ALLOW_SUPERUSER=1 composer dump-autoload --no-dev --optimize --no-interaction -d /var/www/html; \
|
||||
php -d display_errors=1 -r 'require "/var/www/html/vendor/autoload.php"; exit(interface_exists("Psr\\Http\\Message\\UriInterface") && interface_exists("Psr\\Http\\Message\\StreamInterface") ? 0 : 1);'; \
|
||||
chown -R www-data:www-data /var/www/html; \
|
||||
chmod -R 755 /var/www/html
|
||||
|
||||
ENV APP_DIR=/var/www/html \
|
||||
MODULE_DIR=/var/www/html/modules/washcertificates \
|
||||
AUTO_COMPOSER_INSTALL=false \
|
||||
COMPOSER_ALLOW_SUPERUSER=1
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"]
|
||||
CMD ["coolify-api-start"]
|
||||
@@ -26,6 +26,32 @@ The API is accessible at:
|
||||
- `https://localhost` (using Traefik default cert)
|
||||
- `http(s)://localhost/api/` (proxied with `/api` prefix stripped)
|
||||
|
||||
### Test Gateway Container
|
||||
To run a real PHP edge agent as a disposable Dockerized test gateway against the local stack, first create an install token from the edge gateway admin UI, then start the helper:
|
||||
|
||||
```powershell
|
||||
.\scripts\test-gateway.ps1 start --install-token <token>
|
||||
```
|
||||
|
||||
```bash
|
||||
./scripts/test-gateway.sh start --install-token <token>
|
||||
```
|
||||
|
||||
The helper will:
|
||||
- start the local compose dependencies if needed
|
||||
- claim a gateway through `http://localhost/api/edge-agent/claim`
|
||||
- write the generated config to `.tmp/test-gateway/test-gateway.json`
|
||||
- build `services/edge-agent/Dockerfile.test-gateway`
|
||||
- run the PHP agent container on the local compose network
|
||||
|
||||
Useful follow-up commands:
|
||||
|
||||
```powershell
|
||||
.\scripts\test-gateway.ps1 logs
|
||||
.\scripts\test-gateway.ps1 status
|
||||
.\scripts\test-gateway.ps1 stop
|
||||
```
|
||||
|
||||
To start all services including multiple PHP workers and development tools (Jaeger, Portainer):
|
||||
```powershell
|
||||
docker compose --profile dev up -d
|
||||
@@ -37,6 +63,18 @@ Configuration is primarily managed via environment variables.
|
||||
- `services/nginx/app/config.php` loads configuration from the environment (requires `USE_ENV=true`).
|
||||
- `php1` performs an automatic `composer install` on startup if `AUTO_COMPOSER_INSTALL=true`.
|
||||
|
||||
#### Environment Change Runbook
|
||||
When updating `.env` values used by PHP containers (for example e-conomic tokens), recreate affected services so Docker applies the new env:
|
||||
|
||||
```powershell
|
||||
docker compose up -d --force-recreate php1 php2 php3 php4 php5 php-cron
|
||||
```
|
||||
|
||||
#### Edge Broker Public URL
|
||||
Set `EDGE_PUBLIC_BROKER_URL` to the public route that serves the edge broker, including the path prefix handled by the proxy. Local Traefik uses `http://localhost/api/edge-broker`; production routes use the public broker prefix, for example `https://api.truckwash.dk/edge-broker`.
|
||||
|
||||
The browser terminal connects to the exact advertised `EDGE_PUBLIC_BROKER_URL` plus `/ws/browser-shell`. That URL must be routable through the proxy to the edge-broker service. Do not rely on derived `/api/edge-broker` fallback paths outside the local Traefik setup.
|
||||
|
||||
## Testing
|
||||
|
||||
The project now uses [Pest](https://pestphp.com/) as the primary test runner in `services/nginx/app`.
|
||||
@@ -48,9 +86,20 @@ All commands are run from `services/nginx/app`:
|
||||
composer test
|
||||
composer test:unit
|
||||
composer test:integration
|
||||
composer test:api
|
||||
composer test:coverage
|
||||
```
|
||||
|
||||
For local Docker development, run the PHP suites inside `php1`:
|
||||
|
||||
```powershell
|
||||
docker exec php1 sh -lc "cd /var/www/html && composer test:unit"
|
||||
docker exec php1 sh -lc "cd /var/www/html && composer test:integration"
|
||||
docker exec php1 sh -lc "cd /var/www/html && composer test:api"
|
||||
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:api:edge"
|
||||
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:integration:edge"
|
||||
```
|
||||
|
||||
Integration tests are opt-in and should be run with required services available:
|
||||
|
||||
```powershell
|
||||
@@ -58,6 +107,66 @@ $env:RUN_INTEGRATION_TESTS='1'
|
||||
composer test:integration
|
||||
```
|
||||
|
||||
### Edge Gateway Regression Coverage
|
||||
The dedicated backend regression lane for the PHP edge gateway stack is split into:
|
||||
|
||||
- API contract tests for operator, agent, and broker-facing routes
|
||||
- DB-backed integration tests for install sessions, heartbeats, tasks, logs, statistics, and shell persistence
|
||||
- a local dockerized smoke that runs the real PHP edge agent against the local backend and broker
|
||||
|
||||
Run the targeted PHP suites inside `php1`:
|
||||
|
||||
```powershell
|
||||
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:api:edge"
|
||||
docker exec php1 sh -lc "cd /var/www/html && composer run-script test:integration:edge"
|
||||
```
|
||||
|
||||
Run the full local smoke from `backend-php` on the host:
|
||||
|
||||
```powershell
|
||||
node .\scripts\edge-gateway-e2e.mjs
|
||||
```
|
||||
|
||||
The E2E smoke expects the local compose stack and Docker daemon to be available. It boots a disposable gateway container, waits for a real heartbeat, validates live operations and telemetry, and verifies browser shell transcript persistence.
|
||||
|
||||
### Public Staging Edge-Gateway Smoke
|
||||
`api.truckwash.io:4433` is the public staging ingress. For Edge Gateways v2, the router must serve the canonical artifacts from `services/nginx/app/resources/edge-gateway-agent`, not from the legacy `dist/agent.mjs` output or a separate runtime mount.
|
||||
|
||||
To verify the public staging stack after a deploy, use a real installer token and run:
|
||||
|
||||
```powershell
|
||||
node .\scripts\staging-edge-gateway-smoke.mjs --install-token <token>
|
||||
```
|
||||
|
||||
```bash
|
||||
node ./scripts/staging-edge-gateway-smoke.mjs --install-token <token>
|
||||
```
|
||||
|
||||
The smoke check fails unless all of these return `200` from the public domain:
|
||||
- `/ping`
|
||||
- `/edge-agent/artifacts/agent.php`
|
||||
- `/edge-agent/artifacts/truckwash-edge-agent.service`
|
||||
- `/edge-agent/install.sh?token=<real token>`
|
||||
|
||||
### API Test Suite
|
||||
The `Api` suite exercises real HTTP endpoints instead of calling route handlers in-process.
|
||||
|
||||
- `composer test:api` enables `RUN_API_TESTS=1` automatically.
|
||||
- By default the suite starts a temporary PHP server with `php -S 127.0.0.1:18080 index.php` and hits the app over HTTP.
|
||||
- The suite covers the real router, request parsing, auth headers, status codes, and JSON response envelopes.
|
||||
- Tests run serially and use explicit database/Redis fixtures plus reverse-order cleanup instead of transaction rollbacks.
|
||||
- The default phase-1 coverage includes `/ping`, auth session routes, departments, department categories, and orders CRUD including the legacy `PUT /order` alias.
|
||||
- API server logs are written to `services/nginx/app/build/logs/api-server.out.log` and `services/nginx/app/build/logs/api-server.err.log`.
|
||||
|
||||
To point the suite at an already-running base URL instead of the self-started PHP server:
|
||||
|
||||
```powershell
|
||||
$env:API_TEST_BASE_URL='http://127.0.0.1:18080'
|
||||
composer test:api
|
||||
```
|
||||
|
||||
The suite requires an initialized application schema. Redis-backed flows are used when Redis is configured, but the suite can still boot without `caddy` or the full reverse-proxy stack.
|
||||
|
||||
### Run Tests Against A Cloned Live DB (Docker-Isolated)
|
||||
Use the helper scripts in `scripts/` to:
|
||||
1. Clone the configured live DB into a local MySQL Docker container.
|
||||
@@ -96,6 +205,8 @@ FORCE=1 ./scripts/clone-live-to-debug-db.sh
|
||||
### Test Layout
|
||||
- `tests/Unit/*`: isolated unit and route-level behavior tests.
|
||||
- `tests/Integration/*`: Redis/DB-backed tests intended for Docker/CI environments.
|
||||
- `tests/Api/*`: real HTTP endpoint tests that boot a temporary PHP server and assert full request/response behavior.
|
||||
- `tests/Api/api_coverage_manifest.php`: selected phase-1 endpoint manifest used by the API meta-test to enforce happy-path and failure coverage.
|
||||
- `tests/<legacy-domain>/*`: legacy procedural scripts retained during migration; keep them runnable until matching Pest coverage exists.
|
||||
|
||||
## Logs & Monitoring
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@ $CONFIG_DB = [
|
||||
$DEBUG = true; // Set to true to enable debugging (Error messages will be shown, and this should never be used in production)
|
||||
$USE_PROD_ECONOMIC_IN_DEBUG = true; // Set to true to use the production economic API in debug mode
|
||||
$ENCRYPTION_KEY = ''; // 44 Characters long encryption key
|
||||
$CORS = '*'; // Set to the domain that should be allowed to access the API e.g. https://example.com
|
||||
$CORS = '*'; // Set to comma-separated allowed origins e.g. https://example.com,https://api-v2.truckwash.io
|
||||
$ECONOMIC_API = [
|
||||
'app_access_grant' => '', // Economic API access grant token (1)
|
||||
'app_access_grant2' => '', // Economic API access grant token (2)
|
||||
|
||||
@@ -46,6 +46,37 @@ services:
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
|
||||
edge-broker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/edge-broker/Dockerfile
|
||||
container_name: edge-broker
|
||||
environment:
|
||||
<<<<<<< HEAD
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
|
||||
ports:
|
||||
- "127.0.0.1:4300:4300"
|
||||
=======
|
||||
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-manager}
|
||||
EDGE_MANAGER_URL: ${EDGE_MANAGER_URL:-http://caddy}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.edge-broker-api.rule=Host(`api.example.com`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-api.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api.tls.certresolver=le"
|
||||
- "traefik.http.routers.edge-broker-api.middlewares=edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local.entrypoints=web"
|
||||
- "traefik.http.routers.edge-broker-local.middlewares=edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local.service=edge-broker"
|
||||
- "traefik.http.middlewares.edge-broker-strip.stripPrefix.prefixes=/edge-broker"
|
||||
- "traefik.http.middlewares.edge-broker-strip-local.stripPrefix.prefixes=/api/edge-broker"
|
||||
- "traefik.http.services.edge-broker.loadbalancer.server.port=4300"
|
||||
>>>>>>> origin/master
|
||||
|
||||
caddy:
|
||||
image: caddy:2.7.6-alpine
|
||||
container_name: caddy
|
||||
@@ -81,11 +112,18 @@ services:
|
||||
depends_on:
|
||||
- redis
|
||||
- mysql
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env.example
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "true"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
<<<<<<< HEAD
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-}
|
||||
=======
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
|
||||
>>>>>>> origin/master
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
@@ -99,11 +137,18 @@ services:
|
||||
depends_on:
|
||||
- redis
|
||||
- mysql
|
||||
- edge-broker
|
||||
command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"]
|
||||
env_file:
|
||||
- .env.example
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
<<<<<<< HEAD
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-}
|
||||
=======
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?set EDGE_BROKER_SHARED_SECRET in .env}
|
||||
>>>>>>> origin/master
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
@@ -140,4 +185,4 @@ services:
|
||||
volumes:
|
||||
mysql_data:
|
||||
redis_data:
|
||||
n8n_data:
|
||||
n8n_data:
|
||||
|
||||
@@ -0,0 +1,440 @@
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.11
|
||||
container_name: traefik
|
||||
ports:
|
||||
- "80:80"
|
||||
- "443:443"
|
||||
- "4433:4433"
|
||||
- "9100:9100"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./services/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
||||
- ./services/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
|
||||
- ./services/traefik/acme.json:/acme.json
|
||||
- ./services/traefik/acme-io.json:/acme-io.json
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.truckwash.dk`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.tls=true"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=le"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.middlewares=dashboard-allow-local@file,dashboard-auth@file"
|
||||
- "traefik.http.routers.traefik-http.rule=Host(`traefik.truckwash.dk`)"
|
||||
- "traefik.http.routers.traefik-http.entrypoints=web"
|
||||
- "traefik.http.routers.traefik-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.traefik-http.service=api@internal"
|
||||
- "traefik.http.routers.traefik-local.rule=Host(`traefik.localhost`)"
|
||||
- "traefik.http.routers.traefik-local.entrypoints=web"
|
||||
- "traefik.http.routers.traefik-local.service=api@internal"
|
||||
- "traefik.http.routers.traefik-local.middlewares=dashboard-allow-local@file,dashboard-auth@file"
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
container_name: redis
|
||||
volumes:
|
||||
- nnks_redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
redis-staging:
|
||||
image: redis:7
|
||||
container_name: redis-staging
|
||||
volumes:
|
||||
- nnks_redis_staging:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
mysql-debug:
|
||||
image: mysql:8.4
|
||||
container_name: mysql-debug
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${CONFIG_DB_DEBUG_PASSWORD:-debug_root_password}
|
||||
MYSQL_DATABASE: ${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}
|
||||
ports:
|
||||
- "3307:3306"
|
||||
volumes:
|
||||
- db_debug_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "MYSQL_PWD=$$MYSQL_ROOT_PASSWORD mysqladmin -u root ping --silent"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
|
||||
edge-broker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/edge-broker/Dockerfile
|
||||
container_name: edge-broker
|
||||
environment:
|
||||
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-manager}
|
||||
EDGE_MANAGER_URL: ${EDGE_MANAGER_URL:-http://caddy}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.edge-broker-api.rule=Host(`api.truckwash.dk`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-api.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api.tls.certresolver=le"
|
||||
- "traefik.http.routers.edge-broker-api.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-api-io.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api-io.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-api-io.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api-io.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-api-io.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api-io.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api-io.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-api-v2.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api-v2.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-api-v2.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api-v2.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-api-v2.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api-v2.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api-v2.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-api-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.edge-broker-api-staging.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api-staging.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-api-staging.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api-staging.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api-staging.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local.entrypoints=web"
|
||||
- "traefik.http.routers.edge-broker-local.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local.priority=200"
|
||||
- "traefik.http.routers.edge-broker-local.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local-secure.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-local-secure.tls=true"
|
||||
- "traefik.http.routers.edge-broker-local-secure.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local-secure.priority=200"
|
||||
- "traefik.http.routers.edge-broker-local-secure.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local-staging.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.edge-broker-local-staging.tls=true"
|
||||
- "traefik.http.routers.edge-broker-local-staging.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local-staging.priority=200"
|
||||
- "traefik.http.routers.edge-broker-local-staging.service=edge-broker"
|
||||
- "traefik.http.middlewares.edge-broker-strip.stripPrefix.prefixes=/edge-broker"
|
||||
- "traefik.http.middlewares.edge-broker-strip-local.stripPrefix.prefixes=/api/edge-broker"
|
||||
- "traefik.http.services.edge-broker.loadbalancer.server.port=4300"
|
||||
|
||||
caddy:
|
||||
image: caddy:2.7.6-alpine
|
||||
container_name: caddy
|
||||
depends_on:
|
||||
- php1
|
||||
- php2
|
||||
- php3
|
||||
- php4
|
||||
- php5
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||
- ./services/caddy/logs:/var/log/caddy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.api.rule=Host(`api.truckwash.dk`)"
|
||||
- "traefik.http.routers.api.entrypoints=websecure"
|
||||
- "traefik.http.routers.api.tls=true"
|
||||
- "traefik.http.routers.api.tls.domains[0].main=api.truckwash.dk"
|
||||
- "traefik.http.routers.api.tls.certresolver=le"
|
||||
- "traefik.http.routers.api.service=caddy"
|
||||
- "traefik.http.routers.api.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
- "traefik.http.routers.api-io.rule=Host(`api.truckwash.io`)"
|
||||
- "traefik.http.routers.api-io.entrypoints=websecure"
|
||||
- "traefik.http.routers.api-io.tls=true"
|
||||
- "traefik.http.routers.api-io.tls.domains[0].main=api.truckwash.io"
|
||||
- "traefik.http.routers.api-io.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.api-io.service=caddy"
|
||||
- "traefik.http.routers.api-io.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
- "traefik.http.routers.api-v2.rule=Host(`api-v2.truckwash.io`)"
|
||||
- "traefik.http.routers.api-v2.entrypoints=websecure"
|
||||
- "traefik.http.routers.api-v2.tls=true"
|
||||
- "traefik.http.routers.api-v2.tls.domains[0].main=api-v2.truckwash.io"
|
||||
- "traefik.http.routers.api-v2.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.api-v2.service=caddy"
|
||||
- "traefik.http.routers.api-v2.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
- "traefik.http.routers.api-http.rule=Host(`api.truckwash.dk`) || Host(`api.truckwash.io`) || Host(`api-v2.truckwash.io`)"
|
||||
- "traefik.http.routers.api-http.entrypoints=web"
|
||||
- "traefik.http.routers.api-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.api-http.service=caddy"
|
||||
- "traefik.http.routers.local.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local.entrypoints=web"
|
||||
- "traefik.http.routers.local.service=caddy"
|
||||
- "traefik.http.routers.local.middlewares=secure-headers@file"
|
||||
- "traefik.http.routers.local-secure.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.local-secure.tls=true"
|
||||
- "traefik.http.routers.local-secure.service=caddy"
|
||||
- "traefik.http.routers.local-secure.middlewares=secure-headers@file"
|
||||
- "traefik.http.routers.local-api.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-api.entrypoints=web"
|
||||
- "traefik.http.routers.local-api.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-api.service=caddy"
|
||||
- "traefik.http.routers.local-api.priority=100"
|
||||
- "traefik.http.routers.local-api-secure.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-api-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.local-api-secure.tls=true"
|
||||
- "traefik.http.routers.local-api-secure.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-api-secure.service=caddy"
|
||||
- "traefik.http.routers.local-api-secure.priority=100"
|
||||
- "traefik.http.services.caddy.loadbalancer.server.port=80"
|
||||
|
||||
caddy-staging:
|
||||
image: caddy:2.7.6-alpine
|
||||
container_name: caddy-staging
|
||||
depends_on:
|
||||
- php-staging
|
||||
volumes:
|
||||
- ./services/nginx/staging:/var/www/html
|
||||
- ./services/caddy/Caddyfile-staging:/etc/caddy/Caddyfile:ro
|
||||
- ./services/caddy/logs-staging:/var/log/caddy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.api-staging.rule=Host(`api.truckwash.io`)"
|
||||
- "traefik.http.routers.api-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.api-staging.tls=true"
|
||||
- "traefik.http.routers.api-staging.tls.domains[0].main=api.truckwash.io"
|
||||
- "traefik.http.routers.api-staging.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.api-staging.service=caddy-staging"
|
||||
- "traefik.http.routers.api-staging.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
- "traefik.http.routers.local-staging.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging.middlewares=secure-headers@file"
|
||||
- "traefik.http.routers.local-staging-secure.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local-staging-secure.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging-secure.tls=true"
|
||||
- "traefik.http.routers.local-staging-secure.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging-secure.middlewares=secure-headers@file"
|
||||
- "traefik.http.routers.local-staging-api.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-staging-api.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging-api.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-staging-api.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging-api.priority=100"
|
||||
- "traefik.http.routers.local-staging-api-secure.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-staging-api-secure.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging-api-secure.tls=true"
|
||||
- "traefik.http.routers.local-staging-api-secure.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-staging-api-secure.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging-api-secure.priority=100"
|
||||
- "traefik.http.services.caddy-staging.loadbalancer.server.port=80"
|
||||
|
||||
php1:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php1
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "true"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php2:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php2
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php3:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php3
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php4:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php4
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php5:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php5
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php-staging:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php-staging
|
||||
depends_on:
|
||||
- redis-staging
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env.staging
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
volumes:
|
||||
- ./services/nginx/staging:/var/www/html
|
||||
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs-staging:/var/log/php
|
||||
|
||||
php-cron:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php-cron
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:-truckwash-edge-dev}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/edge-agent/dist:/services/edge-agent/dist:ro
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
portainer:
|
||||
image: portainer/portainer-ce:2.21.4
|
||||
container_name: portainer
|
||||
profiles:
|
||||
- dev
|
||||
ports:
|
||||
- "9443:9443"
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- portainer_data:/data
|
||||
|
||||
jaeger:
|
||||
image: jaegertracing/all-in-one:1.53
|
||||
container_name: jaeger
|
||||
profiles:
|
||||
- dev
|
||||
environment:
|
||||
- COLLECTOR_ZIPKIN_HTTP_PORT=9411
|
||||
ports:
|
||||
- "16686:16686"
|
||||
|
||||
n8n:
|
||||
image: n8nio/n8n:latest
|
||||
container_name: n8n
|
||||
restart: always
|
||||
environment:
|
||||
- N8N_HOST=n8n.truckwash.io
|
||||
- N8N_PORT=5678
|
||||
- N8N_PROTOCOL=https
|
||||
- NODE_ENV=production
|
||||
- WEBHOOK_URL=https://n8n.truckwash.io/
|
||||
- GENERIC_TIMEZONE=${CONFIG_TIMEZONE:-Europe/Copenhagen}
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.n8n.rule=Host(`n8n.truckwash.io`)"
|
||||
- "traefik.http.routers.n8n.entrypoints=websecure"
|
||||
- "traefik.http.routers.n8n.tls=true"
|
||||
- "traefik.http.routers.n8n.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.n8n.service=n8n"
|
||||
- "traefik.http.routers.n8n-http.rule=Host(`n8n.truckwash.io`)"
|
||||
- "traefik.http.routers.n8n-http.entrypoints=web"
|
||||
- "traefik.http.routers.n8n-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.n8n-http.service=n8n"
|
||||
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
db_debug_data:
|
||||
nnks_redis:
|
||||
nnks_redis_staging:
|
||||
es_data:
|
||||
portainer_data:
|
||||
fleet-server-data:
|
||||
elastic-agent-data:
|
||||
n8n_data:
|
||||
@@ -27,5 +27,5 @@ services:
|
||||
## docker compose up -d traefik caddy php1 php2 php3 php4 php5 db redis
|
||||
##
|
||||
## Notes:
|
||||
## - Traefik uses Let’s Encrypt production. Ensure DNS A/AAAA records for api.truckwash.dk, api.truckwash.io and traefik.truckwash.dk point to this host and ports 80/443 are reachable.
|
||||
## - The dashboard is protected by basic auth and an IP allowlist (defined in dynamic.yml). Replace the bcrypt hash before enabling in production.
|
||||
## - Traefik uses Let’s Encrypt production. Ensure DNS A/AAAA records for api.truckwash.dk, api.truckwash.io, api-v2.truckwash.io and traefik.truckwash.dk point to the expected ingress and ports 80/443 are reachable.
|
||||
## - The dashboard is protected by basic auth and an IP allowlist (defined in dynamic.yml). Replace the bcrypt hash before enabling in production.
|
||||
|
||||
@@ -0,0 +1,498 @@
|
||||
|
||||
services:
|
||||
traefik:
|
||||
image: traefik:2.11
|
||||
container_name: traefik
|
||||
ports:
|
||||
- "${TRAEFIK_WEB_PORT:-80}:80"
|
||||
- "${TRAEFIK_WEBSECURE_PORT:-443}:443"
|
||||
- "${TRAEFIK_WEBSECURE_STAGING_PORT:-4433}:4433"
|
||||
# Prometheus metrics endpoint (local dev)
|
||||
- "${TRAEFIK_METRICS_PORT:-9100}:9100"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||
- ./services/traefik/traefik.yml:/etc/traefik/traefik.yml:ro
|
||||
- ./services/traefik/dynamic.yml:/etc/traefik/dynamic.yml:ro
|
||||
- ./services/traefik/acme.json:/acme.json
|
||||
- ./services/traefik/acme-io.json:/acme-io.json
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# Dashboard over HTTPS (production)
|
||||
- "traefik.http.routers.traefik.rule=Host(`traefik.truckwash.dk`)"
|
||||
- "traefik.http.routers.traefik.entrypoints=websecure"
|
||||
- "traefik.http.routers.traefik.tls=true"
|
||||
- "traefik.http.routers.traefik.tls.certresolver=le"
|
||||
- "traefik.http.routers.traefik.service=api@internal"
|
||||
- "traefik.http.routers.traefik.middlewares=dashboard-allow-local@file,dashboard-auth@file"
|
||||
# Dashboard over HTTP (dev) -> redirect to HTTPS
|
||||
- "traefik.http.routers.traefik-http.rule=Host(`traefik.truckwash.dk`)"
|
||||
- "traefik.http.routers.traefik-http.entrypoints=web"
|
||||
- "traefik.http.routers.traefik-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.traefik-http.service=api@internal"
|
||||
# Local dashboard on traefik.localhost (HTTP only for dev)
|
||||
- "traefik.http.routers.traefik-local.rule=Host(`traefik.localhost`)"
|
||||
- "traefik.http.routers.traefik-local.entrypoints=web"
|
||||
- "traefik.http.routers.traefik-local.service=api@internal"
|
||||
- "traefik.http.routers.traefik-local.middlewares=dashboard-allow-local@file,dashboard-auth@file"
|
||||
# Broker API (handled in edge-broker service)
|
||||
- "traefik.http.routers.edge-broker.rule=Host(`api.truckwash.dk`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker.tls=true"
|
||||
- "traefik.http.routers.edge-broker.tls.certresolver=le"
|
||||
- "traefik.http.routers.edge-broker.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker.priority=200"
|
||||
- "traefik.http.routers.edge-broker.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-io.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-io.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-io.tls=true"
|
||||
- "traefik.http.routers.edge-broker-io.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-io.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-io.priority=200"
|
||||
- "traefik.http.routers.edge-broker-io.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-v2.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-v2.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-v2.tls=true"
|
||||
- "traefik.http.routers.edge-broker-v2.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-v2.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-v2.priority=200"
|
||||
- "traefik.http.routers.edge-broker-v2.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.edge-broker-staging.tls=true"
|
||||
- "traefik.http.routers.edge-broker-staging.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-staging.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-staging.priority=200"
|
||||
- "traefik.http.routers.edge-broker-staging.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local.entrypoints=web"
|
||||
- "traefik.http.routers.edge-broker-local.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local.priority=200"
|
||||
- "traefik.http.routers.edge-broker-local.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local-secure.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
|
||||
redis:
|
||||
image: redis:7
|
||||
container_name: redis
|
||||
# ports:
|
||||
# - "6379:6379"
|
||||
volumes:
|
||||
- nnks_redis:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
redis-staging:
|
||||
image: redis:7
|
||||
container_name: redis-staging
|
||||
# ports:
|
||||
# - "6380:6379"
|
||||
volumes:
|
||||
- nnks_redis_staging:/data
|
||||
healthcheck:
|
||||
test: ["CMD", "redis-cli", "ping"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
start_period: 10s
|
||||
|
||||
mysql-debug:
|
||||
image: mysql:8.4
|
||||
container_name: mysql-debug
|
||||
profiles: [dev]
|
||||
environment:
|
||||
MYSQL_ROOT_PASSWORD: ${CONFIG_DB_DEBUG_PASSWORD:?CONFIG_DB_DEBUG_PASSWORD is required for mysql-debug}
|
||||
MYSQL_DATABASE: ${CONFIG_DB_DEBUG_DATABASE:-nnks_db_debug}
|
||||
ports:
|
||||
- "3307:3306"
|
||||
volumes:
|
||||
- db_debug_data:/var/lib/mysql
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "MYSQL_PWD=$$MYSQL_ROOT_PASSWORD mysqladmin -u root ping --silent"]
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
start_period: 20s
|
||||
|
||||
edge-broker:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/edge-broker/Dockerfile
|
||||
container_name: edge-broker
|
||||
environment:
|
||||
EDGE_AUTH_MODE: ${EDGE_AUTH_MODE:-manager}
|
||||
EDGE_MANAGER_URL: ${EDGE_MANAGER_URL:-http://caddy}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
- "traefik.http.routers.edge-broker-api.rule=Host(`api.truckwash.dk`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-api.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api.tls.certresolver=le"
|
||||
- "traefik.http.routers.edge-broker-api.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-api-io.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api-io.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-api-io.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api-io.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-api-io.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api-io.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api-io.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-api-v2.rule=Host(`api-v2.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api-v2.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-api-v2.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api-v2.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-api-v2.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api-v2.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api-v2.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-api-staging.rule=Host(`api.truckwash.io`) && PathPrefix(`/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-api-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.edge-broker-api-staging.tls=true"
|
||||
- "traefik.http.routers.edge-broker-api-staging.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.edge-broker-api-staging.middlewares=secure-headers@file,edge-broker-strip"
|
||||
- "traefik.http.routers.edge-broker-api-staging.priority=200"
|
||||
- "traefik.http.routers.edge-broker-api-staging.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local.entrypoints=web"
|
||||
- "traefik.http.routers.edge-broker-local.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local.priority=200"
|
||||
- "traefik.http.routers.edge-broker-local.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local-secure.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.edge-broker-local-secure.tls=true"
|
||||
- "traefik.http.routers.edge-broker-local-secure.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local-secure.priority=200"
|
||||
- "traefik.http.routers.edge-broker-local-secure.service=edge-broker"
|
||||
- "traefik.http.routers.edge-broker-local-staging.rule=Host(`localhost`) && PathPrefix(`/api/edge-broker`)"
|
||||
- "traefik.http.routers.edge-broker-local-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.edge-broker-local-staging.tls=true"
|
||||
- "traefik.http.routers.edge-broker-local-staging.middlewares=secure-headers@file,edge-broker-strip-local"
|
||||
- "traefik.http.routers.edge-broker-local-staging.priority=200"
|
||||
- "traefik.http.routers.edge-broker-local-staging.service=edge-broker"
|
||||
- "traefik.http.middlewares.edge-broker-strip.stripPrefix.prefixes=/edge-broker"
|
||||
- "traefik.http.middlewares.edge-broker-strip-local.stripPrefix.prefixes=/api/edge-broker"
|
||||
- "traefik.http.services.edge-broker.loadbalancer.server.port=4300"
|
||||
|
||||
caddy:
|
||||
image: caddy:2.7.6-alpine
|
||||
container_name: caddy
|
||||
depends_on:
|
||||
- php1
|
||||
- php2
|
||||
- php3
|
||||
- php4
|
||||
- php5
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/caddy:/etc/caddy:ro
|
||||
- ./services/caddy/logs:/var/log/caddy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# Public API (HTTPS via Traefik + LE)
|
||||
- "traefik.http.routers.api.rule=Host(`api.truckwash.dk`)"
|
||||
- "traefik.http.routers.api.entrypoints=websecure"
|
||||
- "traefik.http.routers.api.tls=true"
|
||||
- "traefik.http.routers.api.tls.domains[0].main=api.truckwash.dk"
|
||||
- "traefik.http.routers.api.tls.certresolver=le"
|
||||
- "traefik.http.routers.api.service=caddy"
|
||||
- "traefik.http.routers.api.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
# Public API (.io version)
|
||||
- "traefik.http.routers.api-io.rule=Host(`api.truckwash.io`)"
|
||||
- "traefik.http.routers.api-io.entrypoints=websecure"
|
||||
- "traefik.http.routers.api-io.tls=true"
|
||||
- "traefik.http.routers.api-io.tls.domains[0].main=api.truckwash.io"
|
||||
- "traefik.http.routers.api-io.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.api-io.service=caddy"
|
||||
- "traefik.http.routers.api-io.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
# Public API (.io load-balanced gateway)
|
||||
- "traefik.http.routers.api-v2.rule=Host(`api-v2.truckwash.io`)"
|
||||
- "traefik.http.routers.api-v2.entrypoints=websecure"
|
||||
- "traefik.http.routers.api-v2.tls=true"
|
||||
- "traefik.http.routers.api-v2.tls.domains[0].main=api-v2.truckwash.io"
|
||||
- "traefik.http.routers.api-v2.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.api-v2.service=caddy"
|
||||
- "traefik.http.routers.api-v2.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
# HTTP to HTTPS redirect for both API domains
|
||||
- "traefik.http.routers.api-http.rule=Host(`api.truckwash.dk`) || Host(`api.truckwash.io`) || Host(`api-v2.truckwash.io`)"
|
||||
- "traefik.http.routers.api-http.entrypoints=web"
|
||||
- "traefik.http.routers.api-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.api-http.service=caddy"
|
||||
# Local development (HTTP only)
|
||||
- "traefik.http.routers.local.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local.entrypoints=web"
|
||||
- "traefik.http.routers.local.service=caddy"
|
||||
- "traefik.http.routers.local.middlewares=secure-headers@file"
|
||||
# Local development over HTTPS (self-signed/default Traefik cert)
|
||||
- "traefik.http.routers.local-secure.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.local-secure.tls=true"
|
||||
- "traefik.http.routers.local-secure.service=caddy"
|
||||
- "traefik.http.routers.local-secure.middlewares=secure-headers@file"
|
||||
# Local alias: http://localhost/api -> Caddy (strip /api prefix)
|
||||
- "traefik.http.routers.local-api.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-api.entrypoints=web"
|
||||
- "traefik.http.routers.local-api.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-api.service=caddy"
|
||||
- "traefik.http.routers.local-api.priority=100"
|
||||
# Local alias over HTTPS as well: https://localhost/api -> Caddy (strip /api prefix)
|
||||
- "traefik.http.routers.local-api-secure.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-api-secure.entrypoints=websecure"
|
||||
- "traefik.http.routers.local-api-secure.tls=true"
|
||||
- "traefik.http.routers.local-api-secure.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-api-secure.service=caddy"
|
||||
- "traefik.http.routers.local-api-secure.priority=100"
|
||||
# Tell Traefik which port Caddy listens on
|
||||
- "traefik.http.services.caddy.loadbalancer.server.port=80"
|
||||
|
||||
caddy-staging:
|
||||
image: caddy:2.7.6-alpine
|
||||
container_name: caddy-staging
|
||||
depends_on:
|
||||
- php-staging
|
||||
command: ["caddy", "run", "--config", "/etc/caddy/Caddyfile-staging", "--adapter", "caddyfile"]
|
||||
volumes:
|
||||
- ./services/nginx/staging:/var/www/html
|
||||
- ./services/caddy:/etc/caddy:ro
|
||||
- ./services/caddy/logs-staging:/var/log/caddy
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# Staging API (.io version on port 4433)
|
||||
- "traefik.http.routers.api-staging.rule=Host(`api.truckwash.io`)"
|
||||
- "traefik.http.routers.api-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.api-staging.tls=true"
|
||||
- "traefik.http.routers.api-staging.tls.domains[0].main=api.truckwash.io"
|
||||
- "traefik.http.routers.api-staging.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.api-staging.service=caddy-staging"
|
||||
- "traefik.http.routers.api-staging.middlewares=secure-headers@file,api-ratelimit@file"
|
||||
# Local staging development (HTTP on port 4433)
|
||||
- "traefik.http.routers.local-staging.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local-staging.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging.middlewares=secure-headers@file"
|
||||
# Local staging development (HTTPS on port 4433)
|
||||
- "traefik.http.routers.local-staging-secure.rule=Host(`localhost`)"
|
||||
- "traefik.http.routers.local-staging-secure.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging-secure.tls=true"
|
||||
- "traefik.http.routers.local-staging-secure.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging-secure.middlewares=secure-headers@file"
|
||||
# Local staging alias: http://localhost:4433/api -> Caddy staging (strip /api prefix)
|
||||
- "traefik.http.routers.local-staging-api.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-staging-api.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging-api.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-staging-api.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging-api.priority=100"
|
||||
# Local staging alias over HTTPS: https://localhost:4433/api -> Caddy staging (strip /api prefix)
|
||||
- "traefik.http.routers.local-staging-api-secure.rule=Host(`localhost`) && PathPrefix(`/api`)"
|
||||
- "traefik.http.routers.local-staging-api-secure.entrypoints=websecure-staging"
|
||||
- "traefik.http.routers.local-staging-api-secure.tls=true"
|
||||
- "traefik.http.routers.local-staging-api-secure.middlewares=strip-api-prefix@file,secure-headers@file"
|
||||
- "traefik.http.routers.local-staging-api-secure.service=caddy-staging"
|
||||
- "traefik.http.routers.local-staging-api-secure.priority=100"
|
||||
# Tell Traefik which port Caddy listens on
|
||||
- "traefik.http.services.caddy-staging.loadbalancer.server.port=80"
|
||||
|
||||
php1:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php1
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "true"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php2:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php2
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php3:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php3
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php4:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php4
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php5:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php5
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
|
||||
php-staging:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php-staging
|
||||
depends_on:
|
||||
- redis-staging
|
||||
- edge-broker
|
||||
command: ["php-fpm"]
|
||||
env_file:
|
||||
- .env.staging
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
volumes:
|
||||
- ./services/nginx/staging:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs-staging:/var/log/php
|
||||
|
||||
php-cron:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: services/php/Dockerfile
|
||||
container_name: php-cron
|
||||
depends_on:
|
||||
- redis
|
||||
- edge-broker
|
||||
command: ["sh", "-c", "while true; do php index.php run cron; sleep 60; done"]
|
||||
env_file:
|
||||
- .env
|
||||
environment:
|
||||
AUTO_COMPOSER_INSTALL: "false"
|
||||
EDGE_BROKER_URL: ${EDGE_BROKER_URL:-http://edge-broker:4300}
|
||||
EDGE_BROKER_SHARED_SECRET: ${EDGE_BROKER_SHARED_SECRET:?EDGE_BROKER_SHARED_SECRET is required}
|
||||
volumes:
|
||||
- ./services/nginx/app:/var/www/html
|
||||
- ./services/php/php.ini:/usr/local/etc/php/conf.d/zz-custom.ini:ro
|
||||
- ./services/php/logs:/var/log/php
|
||||
portainer:
|
||||
image: portainer/portainer-ce:2.21.4
|
||||
container_name: portainer
|
||||
profiles:
|
||||
- dev
|
||||
ports:
|
||||
- "9443:9443"
|
||||
- "9000:9000"
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
- portainer_data:/data
|
||||
|
||||
# Jaeger all-in-one for local tracing (Traefik → Jaeger)
|
||||
jaeger:
|
||||
image: jaegertracing/all-in-one:1.53
|
||||
container_name: jaeger
|
||||
profiles:
|
||||
- dev
|
||||
environment:
|
||||
- COLLECTOR_ZIPKIN_HTTP_PORT=9411
|
||||
ports:
|
||||
- "16686:16686" # Jaeger UI
|
||||
# No volumes needed for dev; data is ephemeral
|
||||
|
||||
n8n:
|
||||
image: n8nio/n8n:latest
|
||||
container_name: n8n
|
||||
restart: always
|
||||
environment:
|
||||
- N8N_HOST=n8n.truckwash.io
|
||||
- N8N_PORT=5678
|
||||
- N8N_PROTOCOL=https
|
||||
- NODE_ENV=production
|
||||
- WEBHOOK_URL=https://n8n.truckwash.io/
|
||||
- GENERIC_TIMEZONE=${CONFIG_TIMEZONE:-Europe/Copenhagen}
|
||||
volumes:
|
||||
- n8n_data:/home/node/.n8n
|
||||
labels:
|
||||
- "traefik.enable=true"
|
||||
# n8n over HTTPS (le_io cert resolver)
|
||||
- "traefik.http.routers.n8n.rule=Host(`n8n.truckwash.io`)"
|
||||
- "traefik.http.routers.n8n.entrypoints=websecure"
|
||||
- "traefik.http.routers.n8n.tls=true"
|
||||
- "traefik.http.routers.n8n.tls.certresolver=le_io"
|
||||
- "traefik.http.routers.n8n.service=n8n"
|
||||
# n8n HTTP to HTTPS redirect
|
||||
- "traefik.http.routers.n8n-http.rule=Host(`n8n.truckwash.io`)"
|
||||
- "traefik.http.routers.n8n-http.entrypoints=web"
|
||||
- "traefik.http.routers.n8n-http.middlewares=redirect-to-https@file"
|
||||
- "traefik.http.routers.n8n-http.service=n8n"
|
||||
# n8n service port
|
||||
- "traefik.http.services.n8n.loadbalancer.server.port=5678"
|
||||
|
||||
volumes:
|
||||
db_data:
|
||||
db_debug_data:
|
||||
nnks_redis:
|
||||
nnks_redis_staging:
|
||||
es_data:
|
||||
portainer_data:
|
||||
fleet-server-data:
|
||||
elastic-agent-data:
|
||||
n8n_data:
|
||||
+3
-3
@@ -38,14 +38,14 @@ http {
|
||||
location / {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
# If OPTIONS method is needed for preflight
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
|
||||
return 204; # No Content
|
||||
}
|
||||
|
||||
@@ -65,4 +65,4 @@ http {
|
||||
location ~* \.(cgi|shtml|phtml)$ {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-3
@@ -52,14 +52,14 @@ http {
|
||||
location / {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
|
||||
add_header Access-Control-Allow-Credentials true;
|
||||
|
||||
# If OPTIONS method is needed for preflight
|
||||
if ($request_method = 'OPTIONS') {
|
||||
add_header Access-Control-Allow-Origin *;
|
||||
add_header Access-Control-Allow-Methods "GET, POST, OPTIONS";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number";
|
||||
add_header Access-Control-Allow-Headers "Authorization, Content-Type, X-Requested-With, X-Customer-Number, X-Release-Trace, X-Release-Channel, X-Frontend-Version";
|
||||
return 204; # No Content
|
||||
}
|
||||
|
||||
@@ -80,4 +80,4 @@ http {
|
||||
# Additional SSL options or configurations can be placed here, if necessary.
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4465
-169
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
suite="${1:-}"
|
||||
case "$suite" in
|
||||
unit|integration|api|legacy|all)
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 <unit|integration|api|legacy|all>" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
repo_root="$(CDPATH= cd -- "$script_dir/.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
compose_files="-f docker-compose.yml -f .github/docker-compose.ci.yml"
|
||||
project_suffix="$(date +%s)-$$"
|
||||
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-php-local-${suite}-${project_suffix}}"
|
||||
|
||||
log_dir=".tmp/ci-logs/$suite"
|
||||
mkdir -p "$log_dir"
|
||||
|
||||
env_backup_dir=".tmp/php-ci-env-backup-$project_suffix"
|
||||
mkdir -p "$env_backup_dir"
|
||||
had_env=0
|
||||
had_env_staging=0
|
||||
if [ -f .env ]; then
|
||||
cp .env "$env_backup_dir/env"
|
||||
had_env=1
|
||||
fi
|
||||
if [ -f .env.staging ]; then
|
||||
cp .env.staging "$env_backup_dir/env.staging"
|
||||
had_env_staging=1
|
||||
fi
|
||||
|
||||
cp .github/ci.env .env
|
||||
cp .github/ci.env.staging .env.staging
|
||||
|
||||
collect_logs() {
|
||||
status="$1"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
mkdir -p "$log_dir"
|
||||
docker compose $compose_files ps > "$log_dir/docker-compose-ps.txt" 2>&1 || true
|
||||
docker compose $compose_files logs --no-color > "$log_dir/docker-compose.log" 2>&1 || true
|
||||
docker compose $compose_files cp php1:/var/www/html/build/logs "$log_dir/app-build-logs" >/dev/null 2>&1 || true
|
||||
docker compose $compose_files cp php1:/var/log/php "$log_dir/php-logs" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
status="$?"
|
||||
collect_logs "$status"
|
||||
docker compose $compose_files down -v >/dev/null 2>&1 || true
|
||||
if [ "$had_env" -eq 1 ]; then
|
||||
cp "$env_backup_dir/env" .env
|
||||
else
|
||||
rm -f .env
|
||||
fi
|
||||
if [ "$had_env_staging" -eq 1 ]; then
|
||||
cp "$env_backup_dir/env.staging" .env.staging
|
||||
else
|
||||
rm -f .env.staging
|
||||
fi
|
||||
rm -rf "$env_backup_dir"
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
docker compose $compose_files up -d redis mysql-debug php1
|
||||
|
||||
docker compose $compose_files exec -T php1 sh -lc '
|
||||
set -eu
|
||||
for i in $(seq 1 90); do
|
||||
if MYSQL_PWD="${CONFIG_DB_PASSWORD:-debug_root_password}" mysqladmin \
|
||||
-h "${CONFIG_DB_HOST:-mysql-debug}" \
|
||||
-P "${CONFIG_DB_PORT:-3306}" \
|
||||
-u "${CONFIG_DB_USER:-root}" \
|
||||
ping --silent >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Timed out waiting for mysql-debug" >&2
|
||||
exit 1
|
||||
'
|
||||
|
||||
tar \
|
||||
--exclude='./vendor' \
|
||||
--exclude='./.phpunit.cache' \
|
||||
--exclude='./build/logs' \
|
||||
-C services/nginx/app -cf - . \
|
||||
| docker compose $compose_files exec -T php1 tar -C /var/www/html -xf -
|
||||
|
||||
docker compose $compose_files exec -T php1 sh -lc \
|
||||
'cd /var/www/html && composer install --no-interaction --prefer-dist --no-progress'
|
||||
|
||||
docker compose $compose_files exec -T php1 sh -lc \
|
||||
"cd /var/www/html && composer test:ci:$suite"
|
||||
@@ -0,0 +1,912 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { execFile as execFileCallback, spawn as spawnCallback } from "node:child_process";
|
||||
import { randomUUID } from "node:crypto";
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import { DEFAULT_CONFIG_FILE_NAME, DEFAULT_HOST_API_URL } from "./test-gateway.mjs";
|
||||
|
||||
const execFile = promisify(execFileCallback);
|
||||
const COMPOSE_SERVICES = ["traefik", "redis", "mysql-debug", "edge-broker", "php1", "php2", "php3", "php4", "php5", "caddy"];
|
||||
|
||||
function composeArgs(projectName, args) {
|
||||
return ["compose", "-p", projectName, ...args];
|
||||
}
|
||||
|
||||
async function resolveRootDir(scriptPath) {
|
||||
const cwd = process.cwd();
|
||||
|
||||
try {
|
||||
await fs.access(path.join(cwd, "docker-compose.yml"));
|
||||
return cwd;
|
||||
} catch {
|
||||
return path.resolve(path.dirname(scriptPath), "..");
|
||||
}
|
||||
}
|
||||
|
||||
async function runCommand(command, args, { cwd, allowFailure = false, stdio = "pipe" } = {}) {
|
||||
if (stdio === "inherit") {
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawnCallback(command, args, {
|
||||
cwd,
|
||||
stdio: "inherit",
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
if (code === 0 || allowFailure) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code}`));
|
||||
});
|
||||
child.on("error", reject);
|
||||
});
|
||||
|
||||
return { stdout: "", stderr: "", code: 0 };
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await execFile(command, args, {
|
||||
cwd,
|
||||
windowsHide: true,
|
||||
encoding: "utf8",
|
||||
});
|
||||
|
||||
return { stdout: result.stdout, stderr: result.stderr, code: 0 };
|
||||
} catch (error) {
|
||||
if (!allowFailure) {
|
||||
throw error;
|
||||
}
|
||||
|
||||
return {
|
||||
stdout: error.stdout || "",
|
||||
stderr: error.stderr || "",
|
||||
code: typeof error.code === "number" ? error.code : 1,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeBaseUrl(url) {
|
||||
return String(url || "").replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function baseUrlWithHost(baseUrl, host, port = null) {
|
||||
const url = new URL(normalizeBaseUrl(baseUrl));
|
||||
url.hostname = host;
|
||||
if (port !== null) {
|
||||
url.port = port;
|
||||
}
|
||||
return normalizeBaseUrl(url.toString());
|
||||
}
|
||||
|
||||
function directCaddyBaseUrl(baseUrl) {
|
||||
const url = new URL(normalizeBaseUrl(baseUrl));
|
||||
url.hostname = "caddy";
|
||||
url.port = "";
|
||||
if (url.pathname === "/api" || url.pathname === "/api/") {
|
||||
url.pathname = "/";
|
||||
}
|
||||
return normalizeBaseUrl(url.toString());
|
||||
}
|
||||
|
||||
function resolveBrokerWebSocketUrl(rawUrl, apiBaseUrl) {
|
||||
const websocketUrl = new URL(String(rawUrl));
|
||||
const apiUrl = new URL(normalizeBaseUrl(apiBaseUrl));
|
||||
|
||||
if (apiUrl.hostname === "caddy" && websocketUrl.hostname === "caddy") {
|
||||
websocketUrl.hostname = "edge-broker";
|
||||
websocketUrl.port = "4300";
|
||||
websocketUrl.pathname = websocketUrl.pathname.replace(/^\/edge-broker(?=\/|$)/, "") || "/";
|
||||
}
|
||||
|
||||
return websocketUrl.toString();
|
||||
}
|
||||
|
||||
async function readDefaultGatewayHost() {
|
||||
if (process.platform === "win32") {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
const routeTable = await fs.readFile("/proc/net/route", "utf8");
|
||||
const route = routeTable
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim().split(/\s+/))
|
||||
.find((fields) => fields[1] === "00000000" && /^[0-9A-Fa-f]{8}$/.test(fields[2] || ""));
|
||||
|
||||
if (!route) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gateway = route[2];
|
||||
const octets = [
|
||||
gateway.slice(6, 8),
|
||||
gateway.slice(4, 6),
|
||||
gateway.slice(2, 4),
|
||||
gateway.slice(0, 2),
|
||||
].map((octet) => Number.parseInt(octet, 16));
|
||||
|
||||
if (octets.some((octet) => !Number.isInteger(octet)) || octets.every((octet) => octet === 0)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return octets.join(".");
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function composeNetworkName(composeProject) {
|
||||
return `${composeProject}_default`;
|
||||
}
|
||||
|
||||
async function readCurrentContainerRef() {
|
||||
if (process.platform === "win32") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const candidates = [];
|
||||
const envHostname = String(process.env.HOSTNAME || "").trim();
|
||||
if (envHostname !== "") {
|
||||
candidates.push(envHostname);
|
||||
}
|
||||
|
||||
try {
|
||||
const hostname = (await fs.readFile("/etc/hostname", "utf8")).trim();
|
||||
if (hostname !== "") {
|
||||
candidates.push(hostname);
|
||||
}
|
||||
} catch {
|
||||
// Not running in a container with /etc/hostname available.
|
||||
}
|
||||
|
||||
try {
|
||||
const cgroup = await fs.readFile("/proc/self/cgroup", "utf8");
|
||||
const matches = cgroup.match(/[0-9a-f]{64}/gi) || [];
|
||||
candidates.push(...matches);
|
||||
} catch {
|
||||
// cgroup metadata is optional in local development.
|
||||
}
|
||||
|
||||
return candidates.find((candidate) => /^[a-zA-Z0-9][a-zA-Z0-9_.-]{1,127}$/.test(candidate)) || null;
|
||||
}
|
||||
|
||||
async function connectCurrentContainerToComposeNetwork(rootDir, composeProject) {
|
||||
const containerRef = await readCurrentContainerRef();
|
||||
if (!containerRef) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const inspection = await runCommand("docker", ["inspect", containerRef], {
|
||||
cwd: rootDir,
|
||||
allowFailure: true,
|
||||
});
|
||||
if (inspection.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const networkName = composeNetworkName(composeProject);
|
||||
const connection = await runCommand("docker", ["network", "connect", networkName, containerRef], {
|
||||
cwd: rootDir,
|
||||
allowFailure: true,
|
||||
});
|
||||
const stderr = String(connection.stderr || "");
|
||||
if (connection.code === 0) {
|
||||
process.stdout.write(`Attached runner container ${containerRef} to ${networkName}.\n`);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (/already exists|already connected/i.test(stderr)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async function disconnectCurrentContainerFromComposeNetwork(rootDir, composeProject) {
|
||||
const containerRef = await readCurrentContainerRef();
|
||||
if (!containerRef) {
|
||||
return;
|
||||
}
|
||||
|
||||
await runCommand("docker", ["network", "disconnect", composeNetworkName(composeProject), containerRef], {
|
||||
cwd: rootDir,
|
||||
allowFailure: true,
|
||||
});
|
||||
}
|
||||
|
||||
async function readComposeServiceHost(rootDir, composeProject, serviceName) {
|
||||
const ps = await runCommand("docker", composeArgs(composeProject, ["ps", "-q", serviceName]), {
|
||||
cwd: rootDir,
|
||||
allowFailure: true,
|
||||
});
|
||||
const containerId = String(ps.stdout || "").trim().split(/\s+/).find(Boolean);
|
||||
const containerRefs = [
|
||||
...(containerId ? [containerId] : []),
|
||||
serviceName,
|
||||
];
|
||||
|
||||
for (const containerRef of [...new Set(containerRefs)]) {
|
||||
const inspection = await runCommand("docker", [
|
||||
"inspect",
|
||||
"-f",
|
||||
"{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}",
|
||||
containerRef,
|
||||
], {
|
||||
cwd: rootDir,
|
||||
allowFailure: true,
|
||||
});
|
||||
const host = String(inspection.stdout || "").trim().split(/\s+/).find((value) => /^\d{1,3}(?:\.\d{1,3}){3}$/.test(value));
|
||||
|
||||
if (host) {
|
||||
return host;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function candidateApiBaseUrls(baseUrl, rootDir, composeProject, useComposeNetwork = false) {
|
||||
const normalized = normalizeBaseUrl(baseUrl);
|
||||
const candidates = [normalized];
|
||||
const url = new URL(normalized);
|
||||
|
||||
if (["localhost", "127.0.0.1", "::1"].includes(url.hostname)) {
|
||||
if (rootDir && composeProject) {
|
||||
if (useComposeNetwork) {
|
||||
candidates.push(directCaddyBaseUrl(normalized));
|
||||
candidates.push(baseUrlWithHost(normalized, "traefik", ""));
|
||||
}
|
||||
|
||||
const traefikHost = await readComposeServiceHost(rootDir, composeProject, "traefik");
|
||||
if (traefikHost) {
|
||||
candidates.push(baseUrlWithHost(normalized, traefikHost, ""));
|
||||
}
|
||||
}
|
||||
|
||||
const gatewayHost = await readDefaultGatewayHost();
|
||||
if (gatewayHost) {
|
||||
candidates.push(baseUrlWithHost(normalized, gatewayHost));
|
||||
}
|
||||
|
||||
candidates.push(baseUrlWithHost(normalized, "host.docker.internal"));
|
||||
}
|
||||
|
||||
return [...new Set(candidates)];
|
||||
}
|
||||
|
||||
async function waitForCondition(predicate, { timeoutMs = 30_000, intervalMs = 500, message = "Timed out" } = {}) {
|
||||
const deadline = Date.now() + timeoutMs;
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
if (await predicate()) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, intervalMs));
|
||||
}
|
||||
|
||||
throw new Error(message);
|
||||
}
|
||||
|
||||
async function ensureComposeServices(rootDir, composeProject) {
|
||||
await runCommand("docker", composeArgs(composeProject, ["up", "-d", ...COMPOSE_SERVICES]), {
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForApiReady(baseUrl, rootDir, composeProject, useComposeNetwork = false, attempts = 60) {
|
||||
const candidates = await candidateApiBaseUrls(baseUrl, rootDir, composeProject, useComposeNetwork);
|
||||
let lastError = "API never responded";
|
||||
|
||||
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
||||
for (const root of candidates) {
|
||||
try {
|
||||
const response = await fetch(`${root}/ping`, {
|
||||
signal: AbortSignal.timeout(1000),
|
||||
});
|
||||
if (response.ok) {
|
||||
return root;
|
||||
}
|
||||
|
||||
lastError = `${root}/ping returned HTTP ${response.status}`;
|
||||
} catch (error) {
|
||||
lastError = `${root}/ping failed: ${error instanceof Error ? error.message : String(error)}`;
|
||||
}
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
throw new Error(`API did not become ready at ${candidates.map((candidate) => `${candidate}/ping`).join(", ")}: ${lastError}`);
|
||||
}
|
||||
|
||||
function parseLastJsonLine(output) {
|
||||
const lines = String(output || "")
|
||||
.split(/\r?\n/)
|
||||
.map((line) => line.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
for (let index = lines.length - 1; index >= 0; index -= 1) {
|
||||
try {
|
||||
return JSON.parse(lines[index]);
|
||||
} catch {
|
||||
// Continue scanning backwards for the JSON payload.
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(`Unable to parse JSON from command output:\n${output}`);
|
||||
}
|
||||
|
||||
async function runPhpFixture(rootDir, composeProject, action, payload = null) {
|
||||
const encodedPayload = payload === null
|
||||
? ""
|
||||
: ` ${Buffer.from(JSON.stringify(payload), "utf8").toString("base64url")}`;
|
||||
const command = `cd /var/www/html && CONFIG_DB_TARGET=debug php tests/Support/EdgeGatewayE2eFixture.php ${action}${encodedPayload}`;
|
||||
|
||||
const result = await runCommand("docker", composeArgs(composeProject, [
|
||||
"exec",
|
||||
"-T",
|
||||
"php1",
|
||||
"sh",
|
||||
"-lc",
|
||||
command,
|
||||
]), {
|
||||
cwd: rootDir,
|
||||
});
|
||||
|
||||
return parseLastJsonLine(result.stdout);
|
||||
}
|
||||
|
||||
async function apiRequest(baseUrl, method, endpoint, { token = null, body = null, headers = {} } = {}) {
|
||||
const response = await fetch(`${normalizeBaseUrl(baseUrl)}${endpoint}`, {
|
||||
method,
|
||||
headers: {
|
||||
...(body === null ? {} : { "content-type": "application/json" }),
|
||||
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
||||
...headers,
|
||||
},
|
||||
body: body === null ? undefined : JSON.stringify(body),
|
||||
});
|
||||
|
||||
const rawBody = await response.text();
|
||||
let json = null;
|
||||
|
||||
if (rawBody !== "") {
|
||||
try {
|
||||
json = JSON.parse(rawBody);
|
||||
} catch {
|
||||
json = null;
|
||||
}
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
const message =
|
||||
json?.data?.message ||
|
||||
json?.error ||
|
||||
rawBody ||
|
||||
`HTTP ${response.status}`;
|
||||
throw new Error(`${method} ${endpoint} failed: ${message}`);
|
||||
}
|
||||
|
||||
return json;
|
||||
}
|
||||
|
||||
async function loadWebSocketImplementation() {
|
||||
if (typeof WebSocket !== "undefined") {
|
||||
return WebSocket;
|
||||
}
|
||||
|
||||
const module = await import("ws");
|
||||
return module.default;
|
||||
}
|
||||
|
||||
function onSocket(socket, eventName, handler) {
|
||||
if (typeof socket.addEventListener === "function") {
|
||||
socket.addEventListener(eventName, (event) => {
|
||||
if (eventName === "message") {
|
||||
handler(event.data);
|
||||
return;
|
||||
}
|
||||
|
||||
handler(event);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
socket.on(eventName, handler);
|
||||
}
|
||||
|
||||
function collectSocketMessages(socket) {
|
||||
const messages = [];
|
||||
|
||||
onSocket(socket, "message", (payload) => {
|
||||
const text = typeof payload === "string"
|
||||
? payload
|
||||
: Buffer.isBuffer(payload)
|
||||
? payload.toString("utf8")
|
||||
: typeof payload?.toString === "function"
|
||||
? payload.toString()
|
||||
: "";
|
||||
|
||||
if (text === "") {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
messages.push(JSON.parse(text));
|
||||
} catch {
|
||||
// Ignore non-JSON frames.
|
||||
}
|
||||
});
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
async function waitForSocketOpen(socket) {
|
||||
if (socket.readyState === 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject(new Error("Timed out waiting for websocket open.")), 10_000);
|
||||
const socketUrl = typeof socket.url === "string" && socket.url !== "" ? ` (${socket.url})` : "";
|
||||
|
||||
const onOpen = () => {
|
||||
clearTimeout(timeout);
|
||||
resolve();
|
||||
};
|
||||
|
||||
const onError = (error) => {
|
||||
clearTimeout(timeout);
|
||||
if (error instanceof Error) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const readyState = typeof socket.readyState === "number" ? socket.readyState : "unknown";
|
||||
reject(new Error(`Websocket failed to open${socketUrl}; readyState=${readyState}.`));
|
||||
};
|
||||
|
||||
const onClose = (event) => {
|
||||
clearTimeout(timeout);
|
||||
const code = event && typeof event === "object" && "code" in event ? event.code : "unknown";
|
||||
const reason = event && typeof event === "object" && "reason" in event ? event.reason : "";
|
||||
reject(new Error(`Websocket closed before open${socketUrl}; code=${code} reason=${reason || "none"}.`));
|
||||
};
|
||||
|
||||
onSocket(socket, "open", onOpen);
|
||||
onSocket(socket, "error", onError);
|
||||
onSocket(socket, "close", onClose);
|
||||
});
|
||||
}
|
||||
|
||||
async function waitForSocketMessage(messages, predicate, options) {
|
||||
await waitForCondition(() => messages.some(predicate), options);
|
||||
}
|
||||
|
||||
function buildSocketUrl(wsUrl, token) {
|
||||
const url = new URL(String(wsUrl));
|
||||
url.searchParams.set("token", token);
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
function closeSocket(socket) {
|
||||
if (!socket || typeof socket.close !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
const readyState = typeof socket.readyState === "number" ? socket.readyState : null;
|
||||
if (readyState !== null && readyState >= 2) {
|
||||
return;
|
||||
}
|
||||
|
||||
socket.close();
|
||||
}
|
||||
|
||||
function shouldCopyGatewayConfig() {
|
||||
return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_COPY_CONFIG || "").trim());
|
||||
}
|
||||
|
||||
function shouldSkipComposeUp() {
|
||||
return /^(1|true|yes)$/i.test(String(process.env.EDGE_GATEWAY_E2E_SKIP_COMPOSE_UP || "").trim());
|
||||
}
|
||||
|
||||
function collectMessages(rows) {
|
||||
return Array.isArray(rows)
|
||||
? rows
|
||||
.map((row) => (row && typeof row === "object" ? row.message : null))
|
||||
.filter((value) => typeof value === "string")
|
||||
: [];
|
||||
}
|
||||
|
||||
function summarizeStreamMessages(messages, limit = 12) {
|
||||
return messages
|
||||
.slice(-limit)
|
||||
.map((message) => {
|
||||
if (!message || typeof message !== "object") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const summary = {
|
||||
type: message.type || "unknown",
|
||||
};
|
||||
|
||||
if (message.operationId !== undefined) {
|
||||
summary.operationId = Number(message.operationId || 0);
|
||||
}
|
||||
|
||||
if (message.operation && typeof message.operation === "object") {
|
||||
summary.operationStatus = message.operation.status || null;
|
||||
}
|
||||
|
||||
if (message.gateway && typeof message.gateway === "object") {
|
||||
summary.gatewayStatus = message.gateway.status || null;
|
||||
}
|
||||
|
||||
return summary;
|
||||
})
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const scriptPath = fileURLToPath(import.meta.url);
|
||||
const rootDir = await resolveRootDir(scriptPath);
|
||||
const runId = randomUUID().slice(0, 8);
|
||||
const containerName = `truckwash-edge-e2e-${runId}`;
|
||||
const configDir = path.join(rootDir, ".tmp", "edge-gateway-e2e", runId);
|
||||
const configFilePath = path.join(configDir, DEFAULT_CONFIG_FILE_NAME);
|
||||
let baseUrl = process.env.EDGE_GATEWAY_E2E_BASE_URL || DEFAULT_HOST_API_URL;
|
||||
const composeProject =
|
||||
process.env.EDGE_GATEWAY_E2E_COMPOSE_PROJECT
|
||||
|| path.basename(rootDir);
|
||||
|
||||
let fixture = null;
|
||||
let gatewayId = null;
|
||||
let streamSocket = null;
|
||||
let shellSocket = null;
|
||||
let runnerNetworkAttached = false;
|
||||
|
||||
try {
|
||||
if (!shouldSkipComposeUp()) {
|
||||
await ensureComposeServices(rootDir, composeProject);
|
||||
}
|
||||
runnerNetworkAttached = await connectCurrentContainerToComposeNetwork(rootDir, composeProject);
|
||||
baseUrl = await waitForApiReady(baseUrl, rootDir, composeProject, runnerNetworkAttached);
|
||||
process.stdout.write(`Using API base URL ${baseUrl}\n`);
|
||||
|
||||
fixture = await runPhpFixture(rootDir, composeProject, "create");
|
||||
const authToken = String(fixture.auth_token || "");
|
||||
const departmentId = Number(fixture.department_id || 0);
|
||||
|
||||
assert.ok(authToken !== "", "Fixture helper did not return an auth token.");
|
||||
assert.ok(departmentId > 0, "Fixture helper did not return a department id.");
|
||||
|
||||
const installTokenResponse = await apiRequest(baseUrl, "POST", "/edge-gateways/install-token", {
|
||||
token: authToken,
|
||||
body: {
|
||||
department_id: departmentId,
|
||||
label: `Edge Gateway E2E ${runId}`,
|
||||
},
|
||||
});
|
||||
const installToken = String(installTokenResponse?.data?.token || "");
|
||||
assert.ok(installToken !== "", "Install token creation did not return a token.");
|
||||
|
||||
await runCommand(process.execPath, [
|
||||
"scripts/test-gateway.mjs",
|
||||
"start",
|
||||
"--install-token",
|
||||
installToken,
|
||||
"--host-api-url",
|
||||
baseUrl,
|
||||
"--container-name",
|
||||
containerName,
|
||||
"--config-dir",
|
||||
configDir,
|
||||
"--heartbeat-seconds",
|
||||
"3",
|
||||
"--skip-compose-up",
|
||||
...(shouldCopyGatewayConfig() ? ["--copy-config"] : []),
|
||||
], {
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
await waitForCondition(
|
||||
async () => {
|
||||
try {
|
||||
await fs.access(configFilePath);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{ message: `Gateway config file was not created at ${configFilePath}` }
|
||||
);
|
||||
|
||||
const config = JSON.parse(await fs.readFile(configFilePath, "utf8"));
|
||||
gatewayId = Number(config.gatewayId || 0);
|
||||
assert.ok(gatewayId > 0, "Gateway config did not include a gateway id.");
|
||||
|
||||
await waitForCondition(
|
||||
async () => {
|
||||
const result = await runCommand("docker", [
|
||||
"exec",
|
||||
containerName,
|
||||
"test",
|
||||
"-f",
|
||||
"/opt/truckwash-edge-agent/runtime/last-heartbeat-ok.txt",
|
||||
], {
|
||||
allowFailure: true,
|
||||
});
|
||||
|
||||
return result.code === 0;
|
||||
},
|
||||
{
|
||||
timeoutMs: 60_000,
|
||||
message: "Gateway never wrote the successful heartbeat marker.",
|
||||
}
|
||||
);
|
||||
|
||||
await waitForCondition(
|
||||
async () => {
|
||||
const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
|
||||
token: authToken,
|
||||
});
|
||||
|
||||
return detail?.data?.status === "ONLINE"
|
||||
&& Object.keys(detail?.data?.metadata?.system_metrics || {}).length > 0;
|
||||
},
|
||||
{
|
||||
timeoutMs: 60_000,
|
||||
message: "Gateway detail never transitioned to ONLINE with fresh system metrics after install.",
|
||||
}
|
||||
);
|
||||
|
||||
await waitForCondition(
|
||||
async () => {
|
||||
const detail = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}`, {
|
||||
token: authToken,
|
||||
});
|
||||
|
||||
return Boolean(
|
||||
detail?.data?.channel_status?.broker?.connected
|
||||
|| detail?.data?.metadata?.broker_connected
|
||||
);
|
||||
},
|
||||
{
|
||||
timeoutMs: 90_000,
|
||||
message: "Gateway never established a live broker connection after install.",
|
||||
}
|
||||
);
|
||||
|
||||
const WebSocketImpl = await loadWebSocketImplementation();
|
||||
const streamSession = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/stream-session`, {
|
||||
token: authToken,
|
||||
body: {
|
||||
scopes: ["overview", "tasks", "logs", "statistics"],
|
||||
},
|
||||
});
|
||||
const streamWsUrl = buildSocketUrl(
|
||||
resolveBrokerWebSocketUrl(String(streamSession?.data?.ws_url || ""), baseUrl),
|
||||
String(streamSession?.data?.token || "")
|
||||
);
|
||||
streamSocket = new WebSocketImpl(streamWsUrl);
|
||||
const streamMessages = collectSocketMessages(streamSocket);
|
||||
|
||||
await waitForSocketOpen(streamSocket);
|
||||
await waitForSocketMessage(
|
||||
streamMessages,
|
||||
(message) => message?.type === "gateway.stream.ready",
|
||||
{ timeoutMs: 15_000, message: "Gateway stream never became ready." }
|
||||
);
|
||||
|
||||
const readyMessage = streamMessages.find((message) => message?.type === "gateway.stream.ready");
|
||||
assert.equal(
|
||||
Boolean(readyMessage?.connected),
|
||||
true,
|
||||
"Gateway stream became ready before the broker reported the gateway as connected."
|
||||
);
|
||||
|
||||
const operationResponse = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/operations`, {
|
||||
token: authToken,
|
||||
body: {
|
||||
type: "DISCOVERY",
|
||||
request: {
|
||||
inventory: [{
|
||||
device_id: `edge-e2e-${runId}`,
|
||||
local_ip: "10.70.80.90",
|
||||
model: "TruckWash Edge E2E",
|
||||
channel_count: 1,
|
||||
online: true,
|
||||
capabilities: {
|
||||
gateway_management_v2: true,
|
||||
},
|
||||
metadata: {
|
||||
hostname: `edge-e2e-${runId}`,
|
||||
},
|
||||
}],
|
||||
},
|
||||
},
|
||||
});
|
||||
const operationId = Number(operationResponse?.data?.operation?.id || 0);
|
||||
assert.ok(operationId > 0, "Operation creation did not return an operation id.");
|
||||
|
||||
try {
|
||||
await waitForSocketMessage(
|
||||
streamMessages,
|
||||
(message) => message?.type === "task.updated" && Number(message?.operationId || 0) === operationId,
|
||||
{ timeoutMs: 180_000, message: "Live gateway stream never emitted task.updated for the queued operation." }
|
||||
);
|
||||
} catch (error) {
|
||||
let operationSnapshot = null;
|
||||
try {
|
||||
const operations = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/operations`, {
|
||||
token: authToken,
|
||||
});
|
||||
operationSnapshot = Array.isArray(operations?.data)
|
||||
? operations.data.find((item) => Number(item?.id || 0) === operationId) || null
|
||||
: null;
|
||||
} catch {
|
||||
operationSnapshot = null;
|
||||
}
|
||||
|
||||
const diagnostic = [
|
||||
error instanceof Error ? error.message : String(error),
|
||||
`Recent stream messages: ${JSON.stringify(summarizeStreamMessages(streamMessages))}`,
|
||||
`Operation snapshot: ${JSON.stringify(operationSnapshot)}`,
|
||||
].join("\n");
|
||||
throw new Error(diagnostic);
|
||||
}
|
||||
|
||||
await waitForCondition(
|
||||
async () => {
|
||||
const operations = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/operations`, {
|
||||
token: authToken,
|
||||
});
|
||||
|
||||
const operation = Array.isArray(operations?.data)
|
||||
? operations.data.find((item) => Number(item?.id || 0) === operationId)
|
||||
: null;
|
||||
|
||||
return operation?.status === "COMPLETED";
|
||||
},
|
||||
{ timeoutMs: 180_000, message: "Gateway operation never completed through the live agent." }
|
||||
);
|
||||
|
||||
await waitForSocketMessage(
|
||||
streamMessages,
|
||||
(message) => message?.type === "gateway.telemetry" || message?.type === "stats.updated",
|
||||
{ timeoutMs: 15_000, message: "Live gateway stream never emitted telemetry or statistics updates." }
|
||||
);
|
||||
|
||||
await waitForCondition(
|
||||
async () => {
|
||||
const logs = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/logs`, {
|
||||
token: authToken,
|
||||
});
|
||||
|
||||
const timeline = Array.isArray(logs?.data?.entries)
|
||||
? logs.data.entries
|
||||
: (Array.isArray(logs?.data?.timeline) ? logs.data.timeline : []);
|
||||
|
||||
return timeline.some((entry) => {
|
||||
const nestedEntry = entry?.entry && typeof entry.entry === "object" ? entry.entry : null;
|
||||
const directOperationId = Number(nestedEntry?.operation_id || 0);
|
||||
const contextualOperationId = Number(nestedEntry?.context?.operation_id || 0);
|
||||
|
||||
return directOperationId === operationId || contextualOperationId === operationId;
|
||||
});
|
||||
},
|
||||
{ timeoutMs: 30_000, message: "Gateway logs page never reflected the live operation timeline." }
|
||||
);
|
||||
|
||||
const statistics = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/statistics`, {
|
||||
token: authToken,
|
||||
});
|
||||
assert.ok(
|
||||
Object.keys(statistics?.data?.system_metrics || {}).length > 0,
|
||||
"Gateway statistics page did not expose system metrics after live telemetry."
|
||||
);
|
||||
|
||||
const shellSession = await apiRequest(baseUrl, "POST", `/edge-gateways/${gatewayId}/shell-sessions`, {
|
||||
token: authToken,
|
||||
body: {
|
||||
reason: "Edge gateway E2E shell validation",
|
||||
cwd: "/opt/truckwash-edge-agent",
|
||||
cols: 120,
|
||||
rows: 40,
|
||||
},
|
||||
});
|
||||
const shellWsUrl = buildSocketUrl(
|
||||
resolveBrokerWebSocketUrl(String(shellSession?.data?.ws_url || ""), baseUrl),
|
||||
String(shellSession?.data?.token || "")
|
||||
);
|
||||
shellSocket = new WebSocketImpl(shellWsUrl);
|
||||
const shellMessages = collectSocketMessages(shellSocket);
|
||||
|
||||
await waitForSocketOpen(shellSocket);
|
||||
await waitForSocketMessage(
|
||||
shellMessages,
|
||||
(message) => message?.type === "opened",
|
||||
{ timeoutMs: 20_000, message: "Browser shell never opened against the live gateway." }
|
||||
);
|
||||
|
||||
shellSocket.send(JSON.stringify({
|
||||
type: "input",
|
||||
data: "printf 'edge-e2e-shell\\n'; exit\n",
|
||||
}));
|
||||
|
||||
await waitForSocketMessage(
|
||||
shellMessages,
|
||||
(message) => message?.type === "output" && String(message?.data || "").includes("edge-e2e-shell"),
|
||||
{ timeoutMs: 20_000, message: "Browser shell never returned the expected command output." }
|
||||
);
|
||||
|
||||
await waitForSocketMessage(
|
||||
shellMessages,
|
||||
(message) => message?.type === "closed",
|
||||
{ timeoutMs: 20_000, message: "Browser shell never closed cleanly." }
|
||||
);
|
||||
|
||||
const logsAfterShell = await apiRequest(baseUrl, "GET", `/edge-gateways/${gatewayId}/logs`, {
|
||||
token: authToken,
|
||||
});
|
||||
const shellTranscripts = Array.isArray(logsAfterShell?.data?.shell_sessions)
|
||||
? logsAfterShell.data.shell_sessions.map((session) => String(session?.transcript || ""))
|
||||
: [];
|
||||
|
||||
assert.ok(
|
||||
shellTranscripts.some((transcript) => transcript.includes("edge-e2e-shell")),
|
||||
"Gateway logs page did not persist the shell transcript."
|
||||
);
|
||||
|
||||
const timelineMessages = collectMessages(logsAfterShell?.data?.timeline || []);
|
||||
assert.ok(
|
||||
timelineMessages.includes("GATEWAY_SHELL_SESSION_CLOSED"),
|
||||
"Gateway logs page did not include the shell close audit event."
|
||||
);
|
||||
|
||||
process.stdout.write("Edge gateway E2E smoke completed successfully.\n");
|
||||
} finally {
|
||||
closeSocket(shellSocket);
|
||||
closeSocket(streamSocket);
|
||||
|
||||
await runCommand(process.execPath, [
|
||||
"scripts/test-gateway.mjs",
|
||||
"stop",
|
||||
"--container-name",
|
||||
containerName,
|
||||
], {
|
||||
cwd: rootDir,
|
||||
allowFailure: true,
|
||||
}).catch(() => {});
|
||||
|
||||
if (gatewayId !== null && fixture?.auth_token) {
|
||||
await apiRequest(baseUrl, "DELETE", `/edge-gateways/${gatewayId}`, {
|
||||
token: String(fixture.auth_token),
|
||||
}).catch(() => {});
|
||||
}
|
||||
|
||||
if (fixture !== null) {
|
||||
await runPhpFixture(rootDir, composeProject, "cleanup", fixture).catch(() => {});
|
||||
}
|
||||
|
||||
await fs.rm(configDir, { recursive: true, force: true }).catch(() => {});
|
||||
|
||||
if (runnerNetworkAttached) {
|
||||
await disconnectCurrentContainerFromComposeNetwork(rootDir, composeProject).catch(() => {});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env sh
|
||||
set -eu
|
||||
|
||||
suite="${1:-}"
|
||||
case "$suite" in
|
||||
unit|integration|api|legacy|all)
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 <unit|integration|api|legacy|all>" >&2
|
||||
exit 2
|
||||
;;
|
||||
esac
|
||||
|
||||
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
repo_root="$(CDPATH= cd -- "$script_dir/.." && pwd)"
|
||||
cd "$repo_root"
|
||||
|
||||
compose_files="-f docker-compose.yml -f .github/docker-compose.ci.yml"
|
||||
project_suffix="$(date +%s)-$$"
|
||||
export COMPOSE_PROJECT_NAME="${COMPOSE_PROJECT_NAME:-php-local-${suite}-${project_suffix}}"
|
||||
|
||||
log_dir=".tmp/ci-logs/$suite"
|
||||
mkdir -p "$log_dir"
|
||||
|
||||
env_backup_dir=".tmp/php-ci-env-backup-$project_suffix"
|
||||
mkdir -p "$env_backup_dir"
|
||||
had_env=0
|
||||
had_env_staging=0
|
||||
if [ -f .env ]; then
|
||||
cp .env "$env_backup_dir/env"
|
||||
had_env=1
|
||||
fi
|
||||
if [ -f .env.staging ]; then
|
||||
cp .env.staging "$env_backup_dir/env.staging"
|
||||
had_env_staging=1
|
||||
fi
|
||||
|
||||
cp .github/ci.env .env
|
||||
cp .github/ci.env.staging .env.staging
|
||||
|
||||
collect_logs() {
|
||||
status="$1"
|
||||
if [ "$status" -eq 0 ]; then
|
||||
return
|
||||
fi
|
||||
|
||||
mkdir -p "$log_dir"
|
||||
docker compose $compose_files ps > "$log_dir/docker-compose-ps.txt" 2>&1 || true
|
||||
docker compose $compose_files logs --no-color > "$log_dir/docker-compose.log" 2>&1 || true
|
||||
docker compose $compose_files cp php1:/var/www/html/build/logs "$log_dir/app-build-logs" >/dev/null 2>&1 || true
|
||||
docker compose $compose_files cp php1:/var/log/php "$log_dir/php-logs" >/dev/null 2>&1 || true
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
status="$?"
|
||||
collect_logs "$status"
|
||||
docker compose $compose_files down -v >/dev/null 2>&1 || true
|
||||
if [ "$had_env" -eq 1 ]; then
|
||||
cp "$env_backup_dir/env" .env
|
||||
else
|
||||
rm -f .env
|
||||
fi
|
||||
if [ "$had_env_staging" -eq 1 ]; then
|
||||
cp "$env_backup_dir/env.staging" .env.staging
|
||||
else
|
||||
rm -f .env.staging
|
||||
fi
|
||||
rm -rf "$env_backup_dir"
|
||||
exit "$status"
|
||||
}
|
||||
trap cleanup EXIT INT TERM
|
||||
|
||||
docker compose $compose_files up -d redis mysql-debug php1
|
||||
|
||||
docker compose $compose_files exec -T php1 sh -lc '
|
||||
set -eu
|
||||
for i in $(seq 1 90); do
|
||||
if MYSQL_PWD="${CONFIG_DB_PASSWORD:-debug_root_password}" mysqladmin \
|
||||
-h "${CONFIG_DB_HOST:-mysql-debug}" \
|
||||
-P "${CONFIG_DB_PORT:-3306}" \
|
||||
-u "${CONFIG_DB_USER:-root}" \
|
||||
ping --silent >/dev/null 2>&1; then
|
||||
exit 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Timed out waiting for mysql-debug" >&2
|
||||
exit 1
|
||||
'
|
||||
|
||||
tar \
|
||||
--exclude='./vendor' \
|
||||
--exclude='./.phpunit.cache' \
|
||||
--exclude='./build/logs' \
|
||||
-C services/nginx/app -cf - . \
|
||||
| docker compose $compose_files exec -T php1 tar -C /var/www/html -xf -
|
||||
|
||||
docker compose $compose_files exec -T php1 sh -lc \
|
||||
'cd /var/www/html && composer install --no-interaction --prefer-dist --no-progress'
|
||||
|
||||
docker compose $compose_files exec -T php1 sh -lc \
|
||||
"cd /var/www/html && composer test:ci:$suite"
|
||||
@@ -0,0 +1,160 @@
|
||||
import process from "node:process";
|
||||
import path from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
export const DEFAULT_STAGING_BASE_URL = "https://api.truckwash.io:4433";
|
||||
export const INSTALLER_SCRIPT_REQUIRED_SNIPPETS = [
|
||||
"/edge-agent/install-token/status",
|
||||
"report_install_status",
|
||||
'begin_install_phase "VERIFY_TOKEN"',
|
||||
'begin_install_phase "WAIT_FOR_CLAIM"',
|
||||
'report_install_status "FAILED"',
|
||||
];
|
||||
|
||||
export function normalizeBaseUrl(url) {
|
||||
return String(url || "").trim().replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
export function parseArgs(argv = process.argv.slice(2)) {
|
||||
const options = {
|
||||
baseUrl: DEFAULT_STAGING_BASE_URL,
|
||||
installToken: "",
|
||||
help: false,
|
||||
};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
const next = argv[index + 1];
|
||||
|
||||
switch (arg) {
|
||||
case "--base-url":
|
||||
options.baseUrl = String(next || "").trim() || DEFAULT_STAGING_BASE_URL;
|
||||
index += 1;
|
||||
break;
|
||||
case "--install-token":
|
||||
options.installToken = String(next || "").trim();
|
||||
index += 1;
|
||||
break;
|
||||
case "--help":
|
||||
case "-h":
|
||||
options.help = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
export function buildChecks(baseUrl, installToken) {
|
||||
const normalizedBaseUrl = normalizeBaseUrl(baseUrl);
|
||||
|
||||
return [
|
||||
{
|
||||
name: "Ping",
|
||||
url: `${normalizedBaseUrl}/ping`,
|
||||
},
|
||||
{
|
||||
name: "Agent PHP artifact",
|
||||
url: `${normalizedBaseUrl}/edge-agent/artifacts/agent.php`,
|
||||
},
|
||||
{
|
||||
name: "Service unit artifact",
|
||||
url: `${normalizedBaseUrl}/edge-agent/artifacts/truckwash-edge-agent.service`,
|
||||
},
|
||||
{
|
||||
name: "Installer script",
|
||||
url: `${normalizedBaseUrl}/edge-agent/install.sh?token=${encodeURIComponent(installToken)}`,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
export function validateInstallerScriptBody(body) {
|
||||
const source = String(body || "");
|
||||
const missingSnippets = INSTALLER_SCRIPT_REQUIRED_SNIPPETS.filter((snippet) => !source.includes(snippet));
|
||||
|
||||
if (missingSnippets.length) {
|
||||
throw new Error(`Installer script is missing required status wiring: ${missingSnippets.join(", ")}`);
|
||||
}
|
||||
|
||||
return INSTALLER_SCRIPT_REQUIRED_SNIPPETS;
|
||||
}
|
||||
|
||||
function printUsage() {
|
||||
process.stdout.write(`Usage:
|
||||
node scripts/staging-edge-gateway-smoke.mjs --install-token <token> [--base-url <url>]
|
||||
|
||||
Options:
|
||||
--install-token <token> Real edge-gateway install token used to validate install.sh.
|
||||
--base-url <url> Public staging base URL. Default: ${DEFAULT_STAGING_BASE_URL}
|
||||
--help Show this help text.
|
||||
`);
|
||||
}
|
||||
|
||||
function previewBody(body, limit = 200) {
|
||||
const normalized = String(body || "").replace(/\s+/g, " ").trim();
|
||||
if (normalized.length <= limit) {
|
||||
return normalized;
|
||||
}
|
||||
return `${normalized.slice(0, limit)}...`;
|
||||
}
|
||||
|
||||
export async function runSmoke({ baseUrl, installToken }) {
|
||||
if (String(installToken || "").trim() === "") {
|
||||
throw new Error("Missing required --install-token value.");
|
||||
}
|
||||
|
||||
const checks = buildChecks(baseUrl, installToken);
|
||||
const results = [];
|
||||
|
||||
for (const check of checks) {
|
||||
process.stdout.write(`[staging-smoke] GET ${check.url}\n`);
|
||||
const response = await fetch(check.url);
|
||||
const body = await response.text();
|
||||
const result = {
|
||||
...check,
|
||||
status: response.status,
|
||||
ok: response.ok,
|
||||
bodyPreview: previewBody(body),
|
||||
};
|
||||
results.push(result);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
`${check.name} failed with HTTP ${response.status} at ${check.url}. ` +
|
||||
`Body preview: ${result.bodyPreview || "<empty>"}`
|
||||
);
|
||||
}
|
||||
|
||||
if (check.name === "Installer script") {
|
||||
result.verifiedSnippets = validateInstallerScriptBody(body);
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseArgs();
|
||||
|
||||
if (options.help) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
const results = await runSmoke(options);
|
||||
for (const result of results) {
|
||||
process.stdout.write(`[staging-smoke] OK ${result.status} ${result.name}\n`);
|
||||
}
|
||||
}
|
||||
|
||||
const isDirectExecution = process.argv[1]
|
||||
&& import.meta.url === pathToFileURL(path.resolve(process.argv[1])).href;
|
||||
|
||||
if (isDirectExecution) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`[staging-smoke] ERROR: ${error instanceof Error ? error.message : String(error)}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
DEFAULT_STAGING_BASE_URL,
|
||||
INSTALLER_SCRIPT_REQUIRED_SNIPPETS,
|
||||
buildChecks,
|
||||
normalizeBaseUrl,
|
||||
parseArgs,
|
||||
validateInstallerScriptBody,
|
||||
} from "./staging-edge-gateway-smoke.mjs";
|
||||
|
||||
test("normalizeBaseUrl strips trailing slashes", () => {
|
||||
assert.equal(normalizeBaseUrl("https://api.truckwash.io:4433///"), DEFAULT_STAGING_BASE_URL);
|
||||
});
|
||||
|
||||
test("parseArgs accepts base URL and install token", () => {
|
||||
const options = parseArgs([
|
||||
"--base-url",
|
||||
"https://staging.example.test/",
|
||||
"--install-token",
|
||||
"token-123",
|
||||
]);
|
||||
|
||||
assert.equal(options.baseUrl, "https://staging.example.test/");
|
||||
assert.equal(options.installToken, "token-123");
|
||||
assert.equal(options.help, false);
|
||||
});
|
||||
|
||||
test("buildChecks targets the public staging endpoints", () => {
|
||||
const checks = buildChecks(DEFAULT_STAGING_BASE_URL, "abc 123");
|
||||
|
||||
assert.deepEqual(checks.map((check) => check.url), [
|
||||
"https://api.truckwash.io:4433/ping",
|
||||
"https://api.truckwash.io:4433/edge-agent/artifacts/agent.php",
|
||||
"https://api.truckwash.io:4433/edge-agent/artifacts/truckwash-edge-agent.service",
|
||||
"https://api.truckwash.io:4433/edge-agent/install.sh?token=abc%20123",
|
||||
]);
|
||||
});
|
||||
|
||||
test("validateInstallerScriptBody requires install-session reporting wiring", () => {
|
||||
const script = `
|
||||
INSTALL_STATUS_URL="https://api.truckwash.io:4433/edge-agent/install-token/status"
|
||||
report_install_status "FAILED"
|
||||
begin_install_phase "VERIFY_TOKEN" "Verifying install token"
|
||||
begin_install_phase "WAIT_FOR_CLAIM" "Waiting for gateway heartbeat and claim"
|
||||
`;
|
||||
|
||||
assert.deepEqual(validateInstallerScriptBody(script), INSTALLER_SCRIPT_REQUIRED_SNIPPETS);
|
||||
});
|
||||
|
||||
test("validateInstallerScriptBody rejects missing installer status hooks", () => {
|
||||
assert.throws(
|
||||
() => validateInstallerScriptBody("echo hello"),
|
||||
/Installer script is missing required status wiring/
|
||||
);
|
||||
});
|
||||
@@ -0,0 +1,414 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const scriptSource = await fs.readFile(__filename, "utf8");
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2));
|
||||
const workspaceRoot = path.resolve(args.root ?? path.join(__dirname, ".."));
|
||||
const workflowPath = path.join(workspaceRoot, ".ai-workflow", "workflow.md");
|
||||
const manifestPath = path.join(workspaceRoot, ".ai-workflow", "manifest.json");
|
||||
|
||||
const workflow = await fs.readFile(workflowPath, "utf8");
|
||||
const manifest = JSON.parse(await fs.readFile(manifestPath, "utf8"));
|
||||
|
||||
validateManifest(manifest);
|
||||
|
||||
const outputs = buildOutputs({
|
||||
manifest,
|
||||
workflow,
|
||||
scriptSource,
|
||||
});
|
||||
|
||||
if (args.mode === "check") {
|
||||
const drifted = await findDriftedOutputs(workspaceRoot, outputs);
|
||||
if (drifted.length > 0) {
|
||||
console.error("AI workflow outputs are out of sync:");
|
||||
for (const drift of drifted) {
|
||||
console.error(`- ${drift}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("AI workflow outputs are in sync.");
|
||||
return;
|
||||
}
|
||||
|
||||
const changed = await writeOutputs(workspaceRoot, outputs);
|
||||
if (changed.length === 0) {
|
||||
console.log("AI workflow outputs are already up to date.");
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("Updated AI workflow outputs:");
|
||||
for (const changedPath of changed) {
|
||||
console.log(`- ${changedPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
function parseArgs(argv) {
|
||||
const args = {
|
||||
mode: null,
|
||||
root: null,
|
||||
};
|
||||
|
||||
for (let index = 0; index < argv.length; index += 1) {
|
||||
const arg = argv[index];
|
||||
|
||||
if (arg === "--write") {
|
||||
args.mode = "write";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--check") {
|
||||
args.mode = "check";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg === "--root") {
|
||||
args.root = argv[index + 1];
|
||||
index += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Error(`Unsupported argument: ${arg}`);
|
||||
}
|
||||
|
||||
if (!args.mode) {
|
||||
throw new Error("Expected --write or --check.");
|
||||
}
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
function validateManifest(manifest) {
|
||||
const requiredKeys = ["projects", "assistants", "commands", "generated_outputs", "sync_targets"];
|
||||
for (const key of requiredKeys) {
|
||||
if (!(key in manifest)) {
|
||||
throw new Error(`Manifest is missing required key: ${key}`);
|
||||
}
|
||||
}
|
||||
|
||||
const seenPaths = new Set();
|
||||
for (const output of manifest.generated_outputs) {
|
||||
if (!output.path || !output.template) {
|
||||
throw new Error("Each generated output must define path and template.");
|
||||
}
|
||||
|
||||
if (seenPaths.has(output.path)) {
|
||||
throw new Error(`Duplicate generated output path: ${output.path}`);
|
||||
}
|
||||
|
||||
seenPaths.add(output.path);
|
||||
}
|
||||
}
|
||||
|
||||
function buildOutputs(context) {
|
||||
const outputs = [];
|
||||
for (const output of context.manifest.generated_outputs) {
|
||||
outputs.push({
|
||||
path: normalizeRelativePath(output.path),
|
||||
content: renderTemplate(output, context),
|
||||
});
|
||||
}
|
||||
return outputs;
|
||||
}
|
||||
|
||||
function renderTemplate(output, context) {
|
||||
const templateMap = {
|
||||
codex_workspace_environment: () => renderWorkspaceCodexEnvironment(context.manifest),
|
||||
codex_project_environment: () => renderProjectCodexEnvironment(context.manifest, output.project),
|
||||
aiassistant_backend_tests_rule: () =>
|
||||
renderAiAssistantRule(context.manifest.projects["backend-php"].generated_content.aiassistant_tests_lines),
|
||||
aiassistant_backend_routes_rule: () =>
|
||||
renderAiAssistantRule(context.manifest.projects["backend-php"].generated_content.aiassistant_routes_lines),
|
||||
aiassistant_frontend_tests_rule: () =>
|
||||
renderAiAssistantRule(context.manifest.projects["front-end-vue"].generated_content.aiassistant_tests_lines),
|
||||
junie_backend_guidelines: () =>
|
||||
renderJunieGuidelines(context.manifest.projects["backend-php"].generated_content.junie_lines),
|
||||
junie_frontend_guidelines: () =>
|
||||
renderJunieGuidelines(context.manifest.projects["front-end-vue"].generated_content.junie_lines),
|
||||
copilot_dispatcher_workflow: () => renderCopilotWorkflow(context.manifest.copilot),
|
||||
workflow_snapshot: () => renderWorkflowSnapshot(context.workflow, output.project),
|
||||
project_snapshot_manifest: () => renderProjectSnapshotManifest(context.manifest, output.project),
|
||||
project_sync_script: () => context.scriptSource,
|
||||
};
|
||||
|
||||
const renderer = templateMap[output.template];
|
||||
if (!renderer) {
|
||||
throw new Error(`Unsupported template: ${output.template}`);
|
||||
}
|
||||
|
||||
return ensureTrailingNewline(renderer());
|
||||
}
|
||||
|
||||
function renderWorkspaceCodexEnvironment(manifest) {
|
||||
return renderCodexEnvironment({
|
||||
name: manifest.workspace.name,
|
||||
setup: manifest.workspace.codex_environment.setup,
|
||||
actions: manifest.workspace.codex_environment.actions,
|
||||
manifest,
|
||||
workspaceScoped: true,
|
||||
});
|
||||
}
|
||||
|
||||
function renderProjectCodexEnvironment(manifest, projectKey) {
|
||||
const project = manifest.projects[projectKey];
|
||||
return renderCodexEnvironment({
|
||||
name: project.codex_environment.name,
|
||||
setup: project.commands.setup,
|
||||
actions: project.codex_environment.actions,
|
||||
manifest,
|
||||
projectKey,
|
||||
workspaceScoped: false,
|
||||
});
|
||||
}
|
||||
|
||||
function renderCodexEnvironment({ name, setup, actions, manifest, projectKey = null, workspaceScoped }) {
|
||||
const lines = [
|
||||
"# THIS IS AUTOGENERATED. DO NOT EDIT MANUALLY",
|
||||
"version = 1",
|
||||
`name = "${name}"`,
|
||||
"",
|
||||
];
|
||||
|
||||
appendTomlCommandBlock(lines, "setup", setup);
|
||||
|
||||
for (const action of actions) {
|
||||
lines.push("");
|
||||
lines.push("[[actions]]");
|
||||
lines.push(`name = "${escapeTomlString(action.name)}"`);
|
||||
lines.push(`icon = "${escapeTomlString(action.icon)}"`);
|
||||
lines.push(`command = ${renderTomlMultiline(resolveActionCommand(action, manifest, projectKey, workspaceScoped))}`);
|
||||
|
||||
if (action.platform) {
|
||||
lines.push(`platform = "${escapeTomlString(action.platform)}"`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
function appendTomlCommandBlock(lines, key, commandSpec = {}) {
|
||||
lines.push(`[${key}]`);
|
||||
lines.push(`script = ${renderTomlMultiline(commandSpec.default ?? "")}`);
|
||||
|
||||
if (commandSpec.win32 !== undefined) {
|
||||
lines.push("");
|
||||
lines.push(`[${key}.win32]`);
|
||||
lines.push(`script = ${renderTomlMultiline(commandSpec.win32)}`);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveActionCommand(action, manifest, projectKey, workspaceScoped) {
|
||||
if (action.literal_command) {
|
||||
return action.literal_command.default ?? "";
|
||||
}
|
||||
|
||||
if (!action.command_id) {
|
||||
throw new Error(`Action ${action.name} is missing command_id or literal_command.`);
|
||||
}
|
||||
|
||||
const resolvedProjectKey = action.project ?? projectKey;
|
||||
if (!resolvedProjectKey) {
|
||||
throw new Error(`Action ${action.name} does not resolve to a project.`);
|
||||
}
|
||||
|
||||
const project = manifest.projects[resolvedProjectKey];
|
||||
const command = project.commands[action.command_id]?.default ?? "";
|
||||
|
||||
if (!workspaceScoped) {
|
||||
return command;
|
||||
}
|
||||
|
||||
return joinCommands([`cd ${project.relative_root}`, command]);
|
||||
}
|
||||
|
||||
function renderAiAssistantRule(lines) {
|
||||
return [
|
||||
"---",
|
||||
"apply: always",
|
||||
"---",
|
||||
"",
|
||||
"<!-- AUTOGENERATED: Run `node scripts/sync-ai-workflow.mjs --write`. -->",
|
||||
"",
|
||||
...lines,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderJunieGuidelines(lines) {
|
||||
return [
|
||||
"<!-- AUTOGENERATED: Run `node scripts/sync-ai-workflow.mjs --write`. -->",
|
||||
"",
|
||||
...lines,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderCopilotWorkflow(copilot) {
|
||||
const body = copilot.body_lines
|
||||
.map((line) => line.replaceAll("{{ACTOR}}", "$ACTOR").replaceAll("{{TASK}}", "$TASK"))
|
||||
.join("\n");
|
||||
|
||||
return [
|
||||
`name: ${copilot.workflow_name}`,
|
||||
"on:",
|
||||
" workflow_dispatch:",
|
||||
" inputs:",
|
||||
" task:",
|
||||
` description: '${copilot.input_description}'`,
|
||||
" required: true",
|
||||
" type: string",
|
||||
"",
|
||||
"jobs:",
|
||||
" assign-task:",
|
||||
" runs-on: ubuntu-latest",
|
||||
" permissions:",
|
||||
" issues: write",
|
||||
" steps:",
|
||||
" - name: Checkout repository",
|
||||
" uses: actions/checkout@v4",
|
||||
"",
|
||||
" - name: Create GitHub Issue for Copilot",
|
||||
" env:",
|
||||
" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}",
|
||||
" TASK: ${{ github.event.inputs.task }}",
|
||||
" ACTOR: ${{ github.actor }}",
|
||||
" run: |",
|
||||
" BODY=\"$(cat <<EOF",
|
||||
...body.split("\n").map((line) => ` ${line}`),
|
||||
" EOF",
|
||||
" )\"",
|
||||
" gh issue create \\",
|
||||
` --title "${copilot.title_prefix} $TASK" \\`,
|
||||
" --body \"$BODY\" \\",
|
||||
` --label "${copilot.issue_label}"`,
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderWorkflowSnapshot(workflow, projectKey) {
|
||||
return [
|
||||
`<!-- AUTOGENERATED SNAPSHOT for ${projectKey}: refresh from the canonical workspace with \`node scripts/sync-ai-workflow.mjs --write\`. -->`,
|
||||
"",
|
||||
workflow.trimEnd(),
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
function renderProjectSnapshotManifest(manifest, projectKey) {
|
||||
const snapshotManifest = buildProjectSnapshotManifest(manifest, projectKey);
|
||||
return `${JSON.stringify(snapshotManifest, null, 2)}\n`;
|
||||
}
|
||||
|
||||
function buildProjectSnapshotManifest(manifest, projectKey) {
|
||||
const project = JSON.parse(JSON.stringify(manifest.projects[projectKey]));
|
||||
const prefix = `${manifest.projects[projectKey].relative_root}/`;
|
||||
|
||||
project.relative_root = ".";
|
||||
|
||||
const snapshotOutputs = manifest.generated_outputs
|
||||
.filter((output) => output.project === projectKey)
|
||||
.filter((output) => output.template !== "workflow_snapshot")
|
||||
.filter((output) => output.template !== "project_snapshot_manifest")
|
||||
.filter((output) => output.template !== "project_sync_script")
|
||||
.map((output) => ({
|
||||
...output,
|
||||
path: normalizeRelativePath(output.path.slice(prefix.length)),
|
||||
}));
|
||||
|
||||
const snapshotManifest = {
|
||||
"version": manifest.version,
|
||||
"snapshot_of": projectKey,
|
||||
"projects": {
|
||||
[projectKey]: project,
|
||||
},
|
||||
"assistants": manifest.assistants,
|
||||
"commands": manifest.commands,
|
||||
"generated_outputs": snapshotOutputs,
|
||||
"sync_targets": {
|
||||
[projectKey]: {
|
||||
"source_root": ".",
|
||||
"destination": manifest.sync_targets[projectKey]?.destination ?? "",
|
||||
"supported_metadata_dirs": manifest.sync_targets[projectKey]?.supported_metadata_dirs ?? [],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (projectKey === "backend-php" && manifest.copilot) {
|
||||
snapshotManifest.copilot = manifest.copilot;
|
||||
}
|
||||
|
||||
if (projectKey === "front-end-vue" && manifest.unsupported) {
|
||||
snapshotManifest.unsupported = manifest.unsupported;
|
||||
}
|
||||
|
||||
return snapshotManifest;
|
||||
}
|
||||
|
||||
async function findDriftedOutputs(workspaceRoot, outputs) {
|
||||
const drifted = [];
|
||||
for (const output of outputs) {
|
||||
const absolutePath = path.join(workspaceRoot, output.path);
|
||||
const currentContent = await readIfExists(absolutePath);
|
||||
if (currentContent !== output.content) {
|
||||
drifted.push(output.path);
|
||||
}
|
||||
}
|
||||
return drifted;
|
||||
}
|
||||
|
||||
async function writeOutputs(workspaceRoot, outputs) {
|
||||
const changed = [];
|
||||
for (const output of outputs) {
|
||||
const absolutePath = path.join(workspaceRoot, output.path);
|
||||
const currentContent = await readIfExists(absolutePath);
|
||||
if (currentContent === output.content) {
|
||||
continue;
|
||||
}
|
||||
|
||||
await fs.mkdir(path.dirname(absolutePath), { recursive: true });
|
||||
await fs.writeFile(absolutePath, output.content, "utf8");
|
||||
changed.push(output.path);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
async function readIfExists(filePath) {
|
||||
try {
|
||||
return await fs.readFile(filePath, "utf8");
|
||||
} catch (error) {
|
||||
if (error && error.code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function joinCommands(commands) {
|
||||
return commands.filter((command) => command && command.trim() !== "").join("\n");
|
||||
}
|
||||
|
||||
function renderTomlMultiline(value) {
|
||||
return `'''\n${value ?? ""}\n'''`;
|
||||
}
|
||||
|
||||
function escapeTomlString(value) {
|
||||
return String(value).replaceAll("\\", "\\\\").replaceAll("\"", "\\\"");
|
||||
}
|
||||
|
||||
function normalizeRelativePath(filePath) {
|
||||
return filePath.replaceAll("\\", "/");
|
||||
}
|
||||
|
||||
function ensureTrailingNewline(value) {
|
||||
return value.endsWith("\n") ? value : `${value}\n`;
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(error.message);
|
||||
process.exit(1);
|
||||
});
|
||||
@@ -0,0 +1,547 @@
|
||||
import { execFile as execFileCallback, spawn as spawnCallback } from "node:child_process";
|
||||
import { promises as fs } from "node:fs";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
const execFile = promisify(execFileCallback);
|
||||
|
||||
export const DEFAULT_CONTAINER_NAME = "truckwash-test-gateway";
|
||||
export const DEFAULT_IMAGE_TAG = "truckwash-edge-agent:test-gateway";
|
||||
export const DEFAULT_CONFIG_FILE_NAME = "test-gateway.json";
|
||||
export const DEFAULT_HOST_API_URL = "http://localhost/api";
|
||||
export const DEFAULT_CONTAINER_API_URL = "http://caddy";
|
||||
export const DEFAULT_CONTAINER_BROKER_URL = "http://edge-broker:4300";
|
||||
export const DEFAULT_INSTALL_DIR = "/opt/truckwash-edge-agent";
|
||||
export const DEFAULT_RUNTIME_DIR = `${DEFAULT_INSTALL_DIR}/runtime`;
|
||||
export const DEFAULT_STATE_DATABASE_PATH = `${DEFAULT_RUNTIME_DIR}/gateway-state.sqlite`;
|
||||
export const DEFAULT_STACK_SERVICE_NAME = "truckwash-edge-gateway-stack.service";
|
||||
export const DEFAULT_HEARTBEAT_INTERVAL_SECONDS = 15;
|
||||
export const DEFAULT_INSTALLED_VERSION = "php-agent-v1";
|
||||
const DEFAULT_COMPOSE_SERVICES = ["traefik", "redis", "mysql-debug", "edge-broker", "caddy"];
|
||||
|
||||
function composeArgs(projectName, args) {
|
||||
return ["compose", "-p", projectName, ...args];
|
||||
}
|
||||
|
||||
function usesWindowsPathSyntax(filePath) {
|
||||
return /^[A-Za-z]:($|[\\/])/.test(filePath) || filePath.startsWith("\\\\") || filePath.includes("\\");
|
||||
}
|
||||
|
||||
function pathForInputs(...filePaths) {
|
||||
const hasWindowsPath = filePaths.some((filePath) => usesWindowsPathSyntax(String(filePath || "")));
|
||||
|
||||
return hasWindowsPath ? path.win32 : path;
|
||||
}
|
||||
|
||||
async function resolveRootDir(scriptPath) {
|
||||
const cwd = process.cwd();
|
||||
|
||||
try {
|
||||
await fs.access(path.join(cwd, "docker-compose.yml"));
|
||||
return cwd;
|
||||
} catch {
|
||||
return path.resolve(path.dirname(scriptPath), "..");
|
||||
}
|
||||
}
|
||||
|
||||
function printUsage() {
|
||||
process.stdout.write(`Usage:
|
||||
node scripts/test-gateway.mjs start [--install-token <token>] [--container-name <name>] [--hostname <hostname>]
|
||||
node scripts/test-gateway.mjs stop [--container-name <name>]
|
||||
node scripts/test-gateway.mjs logs [--container-name <name>] [--tail <lines>]
|
||||
node scripts/test-gateway.mjs status [--container-name <name>]
|
||||
|
||||
Options:
|
||||
--install-token <token> Claim a new edge gateway before starting the container.
|
||||
--container-name <name> Docker container name. Default: ${DEFAULT_CONTAINER_NAME}
|
||||
--hostname <hostname> Gateway hostname reported during claim and Docker run.
|
||||
--host-api-url <url> Host-reachable API URL for claim/health checks. Default: ${DEFAULT_HOST_API_URL}
|
||||
--api-url <url> Container-internal API URL. Default: ${DEFAULT_CONTAINER_API_URL}
|
||||
--broker-url <url> Container-internal broker URL. Default: ${DEFAULT_CONTAINER_BROKER_URL}
|
||||
--config-dir <dir> Directory for generated config. Default: backend-php/.tmp/test-gateway
|
||||
--image-tag <tag> Docker image tag. Default: ${DEFAULT_IMAGE_TAG}
|
||||
--heartbeat-seconds <n> Agent heartbeat interval. Default: ${DEFAULT_HEARTBEAT_INTERVAL_SECONDS}
|
||||
--tail <lines> Log lines for the logs action. Default: 200
|
||||
--skip-compose-up Do not start the local Docker Compose stack before start.
|
||||
--skip-build Do not rebuild the test gateway image before start.
|
||||
--copy-config Copy generated config into the container instead of bind mounting it.
|
||||
`);
|
||||
}
|
||||
|
||||
export function resolveComposeProjectName(rootDir, env = process.env) {
|
||||
const explicit = String(env.COMPOSE_PROJECT_NAME || "").trim();
|
||||
if (explicit !== "") {
|
||||
return explicit;
|
||||
}
|
||||
|
||||
return pathForInputs(rootDir).basename(rootDir);
|
||||
}
|
||||
|
||||
export function resolveComposeNetworkName(rootDir, env = process.env) {
|
||||
return `${resolveComposeProjectName(rootDir, env)}_default`;
|
||||
}
|
||||
|
||||
export function resolveConfigDirectory(rootDir, explicitDir = null) {
|
||||
const pathModule = pathForInputs(rootDir, explicitDir);
|
||||
|
||||
if (explicitDir) {
|
||||
return pathModule.resolve(rootDir, explicitDir);
|
||||
}
|
||||
|
||||
return pathModule.join(rootDir, ".tmp", "test-gateway");
|
||||
}
|
||||
|
||||
export function shouldClaimGateway(existingConfig = {}, installToken = "") {
|
||||
if (String(installToken || "").trim() !== "") {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !(existingConfig.gatewayId && existingConfig.agentToken);
|
||||
}
|
||||
|
||||
export function buildGatewayConfig({
|
||||
existingConfig = {},
|
||||
claim = null,
|
||||
apiUrl = DEFAULT_CONTAINER_API_URL,
|
||||
brokerUrl = DEFAULT_CONTAINER_BROKER_URL,
|
||||
hostname = DEFAULT_CONTAINER_NAME,
|
||||
heartbeatIntervalSeconds = DEFAULT_HEARTBEAT_INTERVAL_SECONDS,
|
||||
} = {}) {
|
||||
const installedVersion = String(existingConfig.installedVersion || DEFAULT_INSTALLED_VERSION);
|
||||
const targetVersion = String(existingConfig.targetVersion || installedVersion);
|
||||
|
||||
return {
|
||||
apiUrl,
|
||||
brokerUrl,
|
||||
gatewayId: claim?.gateway?.id ?? existingConfig.gatewayId ?? null,
|
||||
agentToken: claim?.agent_token ?? existingConfig.agentToken ?? null,
|
||||
hostname,
|
||||
releaseChannel: claim?.release_channel ?? existingConfig.releaseChannel ?? "stable",
|
||||
installedVersion,
|
||||
targetVersion,
|
||||
installDir: DEFAULT_INSTALL_DIR,
|
||||
runtimeDir: DEFAULT_RUNTIME_DIR,
|
||||
stateDatabasePath: DEFAULT_STATE_DATABASE_PATH,
|
||||
agentPath: `${DEFAULT_INSTALL_DIR}/agent.php`,
|
||||
lanWorkerPath: `${DEFAULT_INSTALL_DIR}/lan-worker.php`,
|
||||
serviceUnitPath: `${DEFAULT_INSTALL_DIR}/truckwash-edge-agent.service`,
|
||||
stackServiceUnitPath: `${DEFAULT_INSTALL_DIR}/${DEFAULT_STACK_SERVICE_NAME}`,
|
||||
serviceName: String(existingConfig.serviceName || hostname || DEFAULT_CONTAINER_NAME),
|
||||
stackServiceName: String(existingConfig.stackServiceName || DEFAULT_STACK_SERVICE_NAME),
|
||||
composeFileName: String(existingConfig.composeFileName || "docker-compose.gateway.yml"),
|
||||
composeProjectName: String(existingConfig.composeProjectName || "truckwash-edge-gateway"),
|
||||
launcherScriptName: String(existingConfig.launcherScriptName || "gateway-launcher.sh"),
|
||||
workerBaseUrl: String(existingConfig.workerBaseUrl || "http://lan-worker:8090"),
|
||||
updateWindow: String(existingConfig.updateWindow || "02:00-04:00"),
|
||||
runtimeMode: String(existingConfig.runtimeMode || "compose"),
|
||||
restartMode: "spawn",
|
||||
heartbeatIntervalSeconds,
|
||||
commandPollTimeoutSeconds: Number(existingConfig.commandPollTimeoutSeconds || 20),
|
||||
commandPollRetryDelayMs: Number(existingConfig.commandPollRetryDelayMs || 1000),
|
||||
brokerReconnectDelayMs: Number(existingConfig.brokerReconnectDelayMs || 1500),
|
||||
};
|
||||
}
|
||||
|
||||
export function parseArgs(argv = process.argv.slice(2)) {
|
||||
const knownActions = new Set(["start", "stop", "logs", "status"]);
|
||||
const [first = "", ...remaining] = argv;
|
||||
const action = knownActions.has(first) ? first : "start";
|
||||
const rest = knownActions.has(first) ? remaining : argv;
|
||||
const options = {
|
||||
action,
|
||||
containerName: DEFAULT_CONTAINER_NAME,
|
||||
imageTag: DEFAULT_IMAGE_TAG,
|
||||
hostApiUrl: DEFAULT_HOST_API_URL,
|
||||
apiUrl: DEFAULT_CONTAINER_API_URL,
|
||||
brokerUrl: DEFAULT_CONTAINER_BROKER_URL,
|
||||
hostname: DEFAULT_CONTAINER_NAME,
|
||||
configDir: null,
|
||||
installToken: "",
|
||||
heartbeatSeconds: DEFAULT_HEARTBEAT_INTERVAL_SECONDS,
|
||||
tail: "200",
|
||||
skipComposeUp: false,
|
||||
skipBuild: false,
|
||||
copyConfig: false,
|
||||
};
|
||||
|
||||
for (let index = 0; index < rest.length; index += 1) {
|
||||
const arg = rest[index];
|
||||
const next = rest[index + 1];
|
||||
|
||||
switch (arg) {
|
||||
case "--container-name":
|
||||
options.containerName = String(next || "").trim() || DEFAULT_CONTAINER_NAME;
|
||||
options.hostname = options.containerName;
|
||||
index += 1;
|
||||
break;
|
||||
case "--hostname":
|
||||
options.hostname = String(next || "").trim() || options.hostname;
|
||||
index += 1;
|
||||
break;
|
||||
case "--image-tag":
|
||||
options.imageTag = String(next || "").trim() || DEFAULT_IMAGE_TAG;
|
||||
index += 1;
|
||||
break;
|
||||
case "--host-api-url":
|
||||
options.hostApiUrl = String(next || "").trim() || DEFAULT_HOST_API_URL;
|
||||
index += 1;
|
||||
break;
|
||||
case "--api-url":
|
||||
options.apiUrl = String(next || "").trim() || DEFAULT_CONTAINER_API_URL;
|
||||
index += 1;
|
||||
break;
|
||||
case "--broker-url":
|
||||
options.brokerUrl = String(next || "").trim() || DEFAULT_CONTAINER_BROKER_URL;
|
||||
index += 1;
|
||||
break;
|
||||
case "--config-dir":
|
||||
options.configDir = String(next || "").trim() || null;
|
||||
index += 1;
|
||||
break;
|
||||
case "--install-token":
|
||||
options.installToken = String(next || "").trim();
|
||||
index += 1;
|
||||
break;
|
||||
case "--heartbeat-seconds":
|
||||
options.heartbeatSeconds = Number(next || DEFAULT_HEARTBEAT_INTERVAL_SECONDS);
|
||||
index += 1;
|
||||
break;
|
||||
case "--tail":
|
||||
options.tail = String(next || "200").trim() || "200";
|
||||
index += 1;
|
||||
break;
|
||||
case "--skip-compose-up":
|
||||
options.skipComposeUp = true;
|
||||
break;
|
||||
case "--skip-build":
|
||||
options.skipBuild = true;
|
||||
break;
|
||||
case "--copy-config":
|
||||
options.copyConfig = true;
|
||||
break;
|
||||
case "--help":
|
||||
case "-h":
|
||||
options.help = true;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unknown argument: ${arg}`);
|
||||
}
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
async function runCommand(command, args, { cwd, allowFailure = false, stdio = "pipe" } = {}) {
|
||||
if (stdio === "inherit") {
|
||||
await new Promise((resolve, reject) => {
|
||||
const child = spawnCallback(command, args, {
|
||||
cwd,
|
||||
stdio: "inherit",
|
||||
windowsHide: true,
|
||||
});
|
||||
|
||||
child.on("exit", (code) => {
|
||||
if (code === 0 || allowFailure) {
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
reject(new Error(`${command} ${args.join(" ")} failed with exit code ${code}`));
|
||||
});
|
||||
child.on("error", reject);
|
||||
});
|
||||
return { stdout: "", stderr: "" };
|
||||
}
|
||||
|
||||
try {
|
||||
return await execFile(command, args, {
|
||||
cwd,
|
||||
windowsHide: true,
|
||||
encoding: "utf8",
|
||||
});
|
||||
} catch (error) {
|
||||
if (allowFailure) {
|
||||
return {
|
||||
stdout: error.stdout || "",
|
||||
stderr: error.stderr || "",
|
||||
code: error.code || 1,
|
||||
};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureComposeServices(rootDir, skipComposeUp) {
|
||||
if (skipComposeUp) {
|
||||
return;
|
||||
}
|
||||
|
||||
await runCommand("docker", composeArgs(resolveComposeProjectName(rootDir), ["up", "-d", ...DEFAULT_COMPOSE_SERVICES]), {
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
|
||||
function normalizeBaseUrl(url) {
|
||||
return String(url || "").replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
async function waitForApiReady(hostApiUrl, attempts = 60) {
|
||||
const baseUrl = normalizeBaseUrl(hostApiUrl);
|
||||
let lastError = null;
|
||||
|
||||
for (let attempt = 0; attempt < attempts; attempt += 1) {
|
||||
try {
|
||||
const response = await fetch(`${baseUrl}/ping`);
|
||||
if (response.ok) {
|
||||
return;
|
||||
}
|
||||
lastError = new Error(`HTTP ${response.status}`);
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
}
|
||||
|
||||
throw new Error(`API did not become ready at ${baseUrl}/ping: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
|
||||
}
|
||||
|
||||
async function claimGateway(hostApiUrl, installToken, hostname) {
|
||||
const response = await fetch(`${normalizeBaseUrl(hostApiUrl)}/edge-agent/claim`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"content-type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: installToken,
|
||||
hostname,
|
||||
installed_version: DEFAULT_INSTALLED_VERSION,
|
||||
metadata: {
|
||||
source: "docker-test-gateway-script",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
const json = await response.json();
|
||||
if (!response.ok) {
|
||||
throw new Error(json?.data?.message || json?.message || `HTTP ${response.status}`);
|
||||
}
|
||||
|
||||
return json.data ?? json;
|
||||
}
|
||||
|
||||
async function readConfig(configFilePath) {
|
||||
try {
|
||||
const raw = await fs.readFile(configFilePath, "utf8");
|
||||
return JSON.parse(raw);
|
||||
} catch (error) {
|
||||
if (error?.code === "ENOENT") {
|
||||
return {};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function writeConfig(configFilePath, config) {
|
||||
await fs.mkdir(path.dirname(configFilePath), { recursive: true });
|
||||
await fs.writeFile(configFilePath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
||||
}
|
||||
|
||||
function toDockerMountPath(hostPath) {
|
||||
return hostPath.replace(/\\/g, "/");
|
||||
}
|
||||
|
||||
async function ensureImage(rootDir, imageTag, skipBuild) {
|
||||
if (skipBuild) {
|
||||
return;
|
||||
}
|
||||
|
||||
await runCommand(
|
||||
"docker",
|
||||
["build", "-t", imageTag, "-f", "services/edge-agent/Dockerfile.test-gateway", "."],
|
||||
{
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async function removeContainer(containerName) {
|
||||
await runCommand("docker", ["rm", "-f", containerName], { allowFailure: true });
|
||||
}
|
||||
|
||||
async function startContainer({
|
||||
rootDir,
|
||||
imageTag,
|
||||
containerName,
|
||||
hostname,
|
||||
configDir,
|
||||
copyConfig,
|
||||
}) {
|
||||
const networkName = resolveComposeNetworkName(rootDir);
|
||||
const mountedConfigDir = toDockerMountPath(configDir);
|
||||
const containerConfigPath = copyConfig
|
||||
? `/tmp/${DEFAULT_CONFIG_FILE_NAME}`
|
||||
: `/config/${DEFAULT_CONFIG_FILE_NAME}`;
|
||||
const createArgs = [
|
||||
copyConfig ? "create" : "run",
|
||||
...(copyConfig ? [] : ["-d"]),
|
||||
"--name",
|
||||
containerName,
|
||||
"--hostname",
|
||||
hostname,
|
||||
"--restart",
|
||||
"unless-stopped",
|
||||
"--network",
|
||||
networkName,
|
||||
...(copyConfig ? [] : ["-v", `${mountedConfigDir}:/config`]),
|
||||
imageTag,
|
||||
"--config",
|
||||
containerConfigPath,
|
||||
];
|
||||
|
||||
await removeContainer(containerName);
|
||||
await runCommand("docker", createArgs, {
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
if (copyConfig) {
|
||||
await runCommand("docker", [
|
||||
"cp",
|
||||
path.join(configDir, DEFAULT_CONFIG_FILE_NAME),
|
||||
`${containerName}:${containerConfigPath}`,
|
||||
], {
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
|
||||
await runCommand("docker", ["start", containerName], {
|
||||
cwd: rootDir,
|
||||
stdio: "inherit",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function printStatus({ imageTag, configDir, containerName }) {
|
||||
const configFilePath = path.join(configDir, DEFAULT_CONFIG_FILE_NAME);
|
||||
const config = await readConfig(configFilePath);
|
||||
const inspection = await runCommand("docker", ["inspect", containerName], {
|
||||
allowFailure: true,
|
||||
});
|
||||
|
||||
let container = null;
|
||||
if (inspection.stdout && String(inspection.stdout).trim() !== "") {
|
||||
try {
|
||||
const decoded = JSON.parse(inspection.stdout);
|
||||
container = Array.isArray(decoded) ? decoded[0] ?? null : null;
|
||||
} catch {
|
||||
container = null;
|
||||
}
|
||||
}
|
||||
|
||||
process.stdout.write(`${JSON.stringify({
|
||||
containerName,
|
||||
configFilePath,
|
||||
gatewayId: config.gatewayId ?? null,
|
||||
installDir: config.installDir ?? null,
|
||||
runtimeDir: config.runtimeDir ?? null,
|
||||
agentPath: config.agentPath ?? null,
|
||||
serviceUnitPath: config.serviceUnitPath ?? null,
|
||||
stackServiceUnitPath: config.stackServiceUnitPath ?? null,
|
||||
containerStatus: container?.State?.Status ?? "missing",
|
||||
running: Boolean(container?.State?.Running),
|
||||
image: container?.Config?.Image ?? imageTag,
|
||||
}, null, 2)}\n`);
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const options = parseArgs();
|
||||
if (options.help) {
|
||||
printUsage();
|
||||
return;
|
||||
}
|
||||
|
||||
const scriptPath = fileURLToPath(import.meta.url);
|
||||
const rootDir = await resolveRootDir(scriptPath);
|
||||
const configDir = resolveConfigDirectory(rootDir, options.configDir);
|
||||
const configFilePath = path.join(configDir, DEFAULT_CONFIG_FILE_NAME);
|
||||
|
||||
switch (options.action) {
|
||||
case "start": {
|
||||
await ensureComposeServices(rootDir, options.skipComposeUp);
|
||||
await waitForApiReady(options.hostApiUrl);
|
||||
await ensureImage(rootDir, options.imageTag, options.skipBuild);
|
||||
|
||||
const existingConfig = await readConfig(configFilePath);
|
||||
let claim = null;
|
||||
if (shouldClaimGateway(existingConfig, options.installToken)) {
|
||||
if (String(options.installToken || "").trim() === "") {
|
||||
throw new Error(`An install token is required to create the first test gateway config at ${configFilePath}`);
|
||||
}
|
||||
claim = await claimGateway(options.hostApiUrl, options.installToken, options.hostname);
|
||||
}
|
||||
|
||||
const config = buildGatewayConfig({
|
||||
existingConfig,
|
||||
claim,
|
||||
apiUrl: options.apiUrl,
|
||||
brokerUrl: options.brokerUrl,
|
||||
hostname: options.hostname,
|
||||
heartbeatIntervalSeconds: Number.isFinite(options.heartbeatSeconds) && options.heartbeatSeconds > 0
|
||||
? Math.round(options.heartbeatSeconds)
|
||||
: DEFAULT_HEARTBEAT_INTERVAL_SECONDS,
|
||||
});
|
||||
|
||||
await writeConfig(configFilePath, config);
|
||||
await startContainer({
|
||||
rootDir,
|
||||
imageTag: options.imageTag,
|
||||
containerName: options.containerName,
|
||||
hostname: options.hostname,
|
||||
configDir,
|
||||
copyConfig: options.copyConfig,
|
||||
});
|
||||
|
||||
process.stdout.write(`Test gateway container started.
|
||||
Container: ${options.containerName}
|
||||
Config: ${configFilePath}
|
||||
Gateway ID: ${config.gatewayId ?? "unclaimed"}
|
||||
`);
|
||||
return;
|
||||
}
|
||||
case "stop":
|
||||
await removeContainer(options.containerName);
|
||||
process.stdout.write(`Removed container ${options.containerName}\n`);
|
||||
return;
|
||||
case "logs":
|
||||
await runCommand("docker", ["logs", "-f", "--tail", options.tail, options.containerName], {
|
||||
stdio: "inherit",
|
||||
});
|
||||
return;
|
||||
case "status":
|
||||
await printStatus({
|
||||
imageTag: options.imageTag,
|
||||
configDir,
|
||||
containerName: options.containerName,
|
||||
});
|
||||
return;
|
||||
default:
|
||||
throw new Error(`Unsupported action: ${options.action}`);
|
||||
}
|
||||
}
|
||||
|
||||
const currentFilePath = fileURLToPath(import.meta.url);
|
||||
const currentRealPath = await fs.realpath(currentFilePath).catch(() => currentFilePath);
|
||||
const invokedScript = process.argv[1] ? path.resolve(process.argv[1]) : "";
|
||||
const invokedRealPath = invokedScript !== ""
|
||||
? await fs.realpath(invokedScript).catch(() => invokedScript)
|
||||
: "";
|
||||
if (invokedRealPath === currentRealPath) {
|
||||
main().catch((error) => {
|
||||
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
& node (Join-Path $scriptDir "test-gateway.mjs") @args
|
||||
exit $LASTEXITCODE
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
node "$SCRIPT_DIR/test-gateway.mjs" "$@"
|
||||
@@ -0,0 +1,97 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import {
|
||||
DEFAULT_CONTAINER_NAME,
|
||||
DEFAULT_CONTAINER_API_URL,
|
||||
DEFAULT_CONTAINER_BROKER_URL,
|
||||
DEFAULT_HEARTBEAT_INTERVAL_SECONDS,
|
||||
buildGatewayConfig,
|
||||
parseArgs,
|
||||
resolveComposeNetworkName,
|
||||
resolveComposeProjectName,
|
||||
resolveConfigDirectory,
|
||||
shouldClaimGateway,
|
||||
} from "./test-gateway.mjs";
|
||||
|
||||
test("resolveComposeProjectName prefers COMPOSE_PROJECT_NAME", () => {
|
||||
assert.equal(
|
||||
resolveComposeProjectName("C:/Users/test/backend-php", { COMPOSE_PROJECT_NAME: "custom-stack" }),
|
||||
"custom-stack"
|
||||
);
|
||||
});
|
||||
|
||||
test("resolveComposeProjectName falls back to backend directory name", () => {
|
||||
assert.equal(resolveComposeProjectName("C:/Users/test/backend-php", {}), "backend-php");
|
||||
});
|
||||
|
||||
test("resolveComposeNetworkName derives the default compose network", () => {
|
||||
assert.equal(
|
||||
resolveComposeNetworkName("C:/Users/test/backend-php", { COMPOSE_PROJECT_NAME: "custom-stack" }),
|
||||
"custom-stack_default"
|
||||
);
|
||||
});
|
||||
|
||||
test("resolveConfigDirectory uses the default temp folder when none is provided", () => {
|
||||
assert.equal(
|
||||
resolveConfigDirectory("C:/Users/test/backend-php"),
|
||||
"C:\\Users\\test\\backend-php\\.tmp\\test-gateway"
|
||||
);
|
||||
});
|
||||
|
||||
test("shouldClaimGateway requires a token when no credentials are present", () => {
|
||||
assert.equal(shouldClaimGateway({}, ""), true);
|
||||
assert.equal(shouldClaimGateway({ gatewayId: 12, agentToken: "secret" }, ""), false);
|
||||
assert.equal(shouldClaimGateway({ gatewayId: 12, agentToken: "secret" }, "fresh-token"), true);
|
||||
});
|
||||
|
||||
test("parseArgs accepts help without an explicit action", () => {
|
||||
const options = parseArgs(["--help"]);
|
||||
|
||||
assert.equal(options.action, "start");
|
||||
assert.equal(options.help, true);
|
||||
});
|
||||
|
||||
test("parseArgs accepts copy config mode", () => {
|
||||
const options = parseArgs(["start", "--copy-config"]);
|
||||
|
||||
assert.equal(options.copyConfig, true);
|
||||
});
|
||||
|
||||
test("buildGatewayConfig applies defaults for a dockerized gateway", () => {
|
||||
const config = buildGatewayConfig({});
|
||||
|
||||
assert.equal(config.apiUrl, DEFAULT_CONTAINER_API_URL);
|
||||
assert.equal(config.brokerUrl, DEFAULT_CONTAINER_BROKER_URL);
|
||||
assert.equal(config.hostname, DEFAULT_CONTAINER_NAME);
|
||||
assert.equal(config.restartMode, "spawn");
|
||||
assert.equal(config.heartbeatIntervalSeconds, DEFAULT_HEARTBEAT_INTERVAL_SECONDS);
|
||||
assert.equal(config.installDir, "/opt/truckwash-edge-agent");
|
||||
assert.equal(config.agentPath, "/opt/truckwash-edge-agent/agent.php");
|
||||
assert.equal(config.serviceUnitPath, "/opt/truckwash-edge-agent/truckwash-edge-agent.service");
|
||||
assert.equal(config.serviceName, DEFAULT_CONTAINER_NAME);
|
||||
});
|
||||
|
||||
test("buildGatewayConfig folds claim response into the generated config", () => {
|
||||
const config = buildGatewayConfig({
|
||||
existingConfig: {
|
||||
installedVersion: "0.9.0",
|
||||
targetVersion: "0.9.1",
|
||||
},
|
||||
hostname: "gw-docker-1",
|
||||
claim: {
|
||||
gateway: { id: 44 },
|
||||
agent_token: "agent-secret",
|
||||
release_channel: "stable",
|
||||
},
|
||||
heartbeatIntervalSeconds: 7,
|
||||
});
|
||||
|
||||
assert.equal(config.gatewayId, 44);
|
||||
assert.equal(config.agentToken, "agent-secret");
|
||||
assert.equal(config.releaseChannel, "stable");
|
||||
assert.equal(config.hostname, "gw-docker-1");
|
||||
assert.equal(config.installedVersion, "0.9.0");
|
||||
assert.equal(config.targetVersion, "0.9.1");
|
||||
assert.equal(config.heartbeatIntervalSeconds, 7);
|
||||
});
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,42 @@
|
||||
worker_processes auto;
|
||||
pid /run/nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
}
|
||||
|
||||
http {
|
||||
include /etc/nginx/mime.types;
|
||||
default_type application/octet-stream;
|
||||
|
||||
access_log /dev/stdout;
|
||||
error_log /dev/stderr warn;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
client_max_body_size 64m;
|
||||
|
||||
gzip on;
|
||||
gzip_types application/json application/javascript application/xml text/css text/plain;
|
||||
|
||||
server {
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
root /var/www/html;
|
||||
index index.php;
|
||||
|
||||
location / {
|
||||
include fastcgi_params;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
fastcgi_index index.php;
|
||||
fastcgi_read_timeout 60s;
|
||||
fastcgi_param SCRIPT_FILENAME $document_root/index.php;
|
||||
fastcgi_param SCRIPT_NAME /index.php;
|
||||
fastcgi_param X_REQUEST_ID $http_x_request_id;
|
||||
}
|
||||
|
||||
location ~ /\.(?!well-known) {
|
||||
deny all;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
mkdir -p /run/nginx
|
||||
php-fpm -D
|
||||
exec nginx -g "daemon off;"
|
||||
@@ -0,0 +1,11 @@
|
||||
FROM php:8.2-cli
|
||||
|
||||
WORKDIR /opt/truckwash-edge-agent
|
||||
|
||||
RUN set -eux; \
|
||||
php -r 'foreach (["curl", "sqlite3"] as $extension) { if (!extension_loaded($extension)) { fwrite(STDERR, "Missing PHP extension: {$extension}\n"); exit(1); } }'
|
||||
|
||||
COPY services/nginx/app/resources/edge-gateway-agent/ ./
|
||||
|
||||
ENTRYPOINT ["php", "/opt/truckwash-edge-agent/agent.php"]
|
||||
CMD ["--config", "/config/test-gateway.json"]
|
||||
Vendored
+2194
File diff suppressed because it is too large
Load Diff
+8
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"name": "truckwash-edge-agent",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"node-pty": "^1.1.0"
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"name": "truckwash-edge-agent",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"node_modules/node-addon-api": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
|
||||
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/node-pty": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.1.0.tgz",
|
||||
"integrity": "sha512-20JqtutY6JPXTUnL0ij1uad7Qe1baT46lyolh2sSENDd4sTzKZ4nmAFkeAARDKwmlLjPx6XKRlwRUxwjOy+lUg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^7.1.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2017 [Node.js API collaborators](https://github.com/nodejs/node-addon-api#collaborators)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
NOTE: The default branch has been renamed!
|
||||
master is now named main
|
||||
|
||||
If you have a local clone, you can update it by running:
|
||||
|
||||
```shell
|
||||
git branch -m master main
|
||||
git fetch origin
|
||||
git branch -u origin/main main
|
||||
```
|
||||
|
||||
# **node-addon-api module**
|
||||
This module contains **header-only C++ wrapper classes** which simplify
|
||||
the use of the C based [Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html)
|
||||
provided by Node.js when using C++. It provides a C++ object model
|
||||
and exception handling semantics with low overhead.
|
||||
|
||||
There are three options for implementing addons: Node-API, nan, or direct
|
||||
use of internal V8, libuv, and Node.js libraries. Unless there is a need for
|
||||
direct access to functionality that is not exposed by Node-API as outlined
|
||||
in [C/C++ addons](https://nodejs.org/dist/latest/docs/api/addons.html)
|
||||
in Node.js core, use Node-API. Refer to
|
||||
[C/C++ addons with Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html)
|
||||
for more information on Node-API.
|
||||
|
||||
Node-API is an ABI stable C interface provided by Node.js for building native
|
||||
addons. It is independent of the underlying JavaScript runtime (e.g. V8 or ChakraCore)
|
||||
and is maintained as part of Node.js itself. It is intended to insulate
|
||||
native addons from changes in the underlying JavaScript engine and allow
|
||||
modules compiled for one version to run on later versions of Node.js without
|
||||
recompilation.
|
||||
|
||||
The `node-addon-api` module, which is not part of Node.js, preserves the benefits
|
||||
of the Node-API as it consists only of inline code that depends only on the stable API
|
||||
provided by Node-API. As such, modules built against one version of Node.js
|
||||
using node-addon-api should run without having to be rebuilt with newer versions
|
||||
of Node.js.
|
||||
|
||||
It is important to remember that *other* Node.js interfaces such as
|
||||
`libuv` (included in a project via `#include <uv.h>`) are not ABI-stable across
|
||||
Node.js major versions. Thus, an addon must use Node-API and/or `node-addon-api`
|
||||
exclusively and build against a version of Node.js that includes an
|
||||
implementation of Node-API (meaning an active LTS version of Node.js) in
|
||||
order to benefit from ABI stability across Node.js major versions. Node.js
|
||||
provides an [ABI stability guide][] containing a detailed explanation of ABI
|
||||
stability in general, and the Node-API ABI stability guarantee in particular.
|
||||
|
||||
As new APIs are added to Node-API, node-addon-api must be updated to provide
|
||||
wrappers for those new APIs. For this reason, node-addon-api provides
|
||||
methods that allow callers to obtain the underlying Node-API handles so
|
||||
direct calls to Node-API and the use of the objects/methods provided by
|
||||
node-addon-api can be used together. For example, in order to be able
|
||||
to use an API for which the node-addon-api does not yet provide a wrapper.
|
||||
|
||||
APIs exposed by node-addon-api are generally used to create and
|
||||
manipulate JavaScript values. Concepts and operations generally map
|
||||
to ideas specified in the **ECMA262 Language Specification**.
|
||||
|
||||
The [Node-API Resource](https://nodejs.github.io/node-addon-examples/) offers an
|
||||
excellent orientation and tips for developers just getting started with Node-API
|
||||
and node-addon-api.
|
||||
|
||||
- **[Setup](#setup)**
|
||||
- **[API Documentation](#api)**
|
||||
- **[Examples](#examples)**
|
||||
- **[Tests](#tests)**
|
||||
- **[More resource and info about native Addons](#resources)**
|
||||
- **[Badges](#badges)**
|
||||
- **[Code of Conduct](CODE_OF_CONDUCT.md)**
|
||||
- **[Contributors](#contributors)**
|
||||
- **[License](#license)**
|
||||
|
||||
## **Current version: 7.1.1**
|
||||
|
||||
(See [CHANGELOG.md](CHANGELOG.md) for complete Changelog)
|
||||
|
||||
[](https://nodei.co/npm/node-addon-api/) [](https://nodei.co/npm/node-addon-api/)
|
||||
|
||||
<a name="setup"></a>
|
||||
|
||||
node-addon-api is based on [Node-API](https://nodejs.org/api/n-api.html) and supports using different Node-API versions.
|
||||
This allows addons built with it to run with Node.js versions which support the targeted Node-API version.
|
||||
**However** the node-addon-api support model is to support only the active LTS Node.js versions. This means that
|
||||
every year there will be a new major which drops support for the Node.js LTS version which has gone out of service.
|
||||
|
||||
The oldest Node.js version supported by the current version of node-addon-api is Node.js 16.x.
|
||||
|
||||
## Setup
|
||||
- [Installation and usage](doc/setup.md)
|
||||
- [node-gyp](doc/node-gyp.md)
|
||||
- [cmake-js](doc/cmake-js.md)
|
||||
- [Conversion tool](doc/conversion-tool.md)
|
||||
- [Checker tool](doc/checker-tool.md)
|
||||
- [Generator](doc/generator.md)
|
||||
- [Prebuild tools](doc/prebuild_tools.md)
|
||||
|
||||
<a name="api"></a>
|
||||
|
||||
### **API Documentation**
|
||||
|
||||
The following is the documentation for node-addon-api.
|
||||
|
||||
- [Full Class Hierarchy](doc/hierarchy.md)
|
||||
- [Addon Structure](doc/addon.md)
|
||||
- Data Types:
|
||||
- [Env](doc/env.md)
|
||||
- [CallbackInfo](doc/callbackinfo.md)
|
||||
- [Reference](doc/reference.md)
|
||||
- [Value](doc/value.md)
|
||||
- [Name](doc/name.md)
|
||||
- [Symbol](doc/symbol.md)
|
||||
- [String](doc/string.md)
|
||||
- [Number](doc/number.md)
|
||||
- [Date](doc/date.md)
|
||||
- [BigInt](doc/bigint.md)
|
||||
- [Boolean](doc/boolean.md)
|
||||
- [External](doc/external.md)
|
||||
- [Object](doc/object.md)
|
||||
- [Array](doc/array.md)
|
||||
- [ObjectReference](doc/object_reference.md)
|
||||
- [PropertyDescriptor](doc/property_descriptor.md)
|
||||
- [Function](doc/function.md)
|
||||
- [FunctionReference](doc/function_reference.md)
|
||||
- [ObjectWrap](doc/object_wrap.md)
|
||||
- [ClassPropertyDescriptor](doc/class_property_descriptor.md)
|
||||
- [Buffer](doc/buffer.md)
|
||||
- [ArrayBuffer](doc/array_buffer.md)
|
||||
- [TypedArray](doc/typed_array.md)
|
||||
- [TypedArrayOf](doc/typed_array_of.md)
|
||||
- [DataView](doc/dataview.md)
|
||||
- [Error Handling](doc/error_handling.md)
|
||||
- [Error](doc/error.md)
|
||||
- [TypeError](doc/type_error.md)
|
||||
- [RangeError](doc/range_error.md)
|
||||
- [SyntaxError](doc/syntax_error.md)
|
||||
- [Object Lifetime Management](doc/object_lifetime_management.md)
|
||||
- [HandleScope](doc/handle_scope.md)
|
||||
- [EscapableHandleScope](doc/escapable_handle_scope.md)
|
||||
- [Memory Management](doc/memory_management.md)
|
||||
- [Async Operations](doc/async_operations.md)
|
||||
- [AsyncWorker](doc/async_worker.md)
|
||||
- [AsyncContext](doc/async_context.md)
|
||||
- [AsyncWorker Variants](doc/async_worker_variants.md)
|
||||
- [Thread-safe Functions](doc/threadsafe.md)
|
||||
- [ThreadSafeFunction](doc/threadsafe_function.md)
|
||||
- [TypedThreadSafeFunction](doc/typed_threadsafe_function.md)
|
||||
- [Promises](doc/promises.md)
|
||||
- [Version management](doc/version_management.md)
|
||||
|
||||
<a name="examples"></a>
|
||||
|
||||
### **Examples**
|
||||
|
||||
Are you new to **node-addon-api**? Take a look at our **[examples](https://github.com/nodejs/node-addon-examples)**
|
||||
|
||||
- **[Hello World](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/1_hello_world)**
|
||||
- **[Pass arguments to a function](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/2_function_arguments/node-addon-api)**
|
||||
- **[Callbacks](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/3_callbacks/node-addon-api)**
|
||||
- **[Object factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/4_object_factory/node-addon-api)**
|
||||
- **[Function factory](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/5_function_factory/node-addon-api)**
|
||||
- **[Wrapping C++ Object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/6_object_wrap/node-addon-api)**
|
||||
- **[Factory of wrapped object](https://github.com/nodejs/node-addon-examples/tree/main/src/1-getting-started/7_factory_wrap/node-addon-api)**
|
||||
- **[Passing wrapped object around](https://github.com/nodejs/node-addon-examples/tree/main/src/2-js-to-native-conversion/8_passing_wrapped/node-addon-api)**
|
||||
|
||||
<a name="tests"></a>
|
||||
|
||||
### **Tests**
|
||||
|
||||
To run the **node-addon-api** tests do:
|
||||
|
||||
```
|
||||
npm install
|
||||
npm test
|
||||
```
|
||||
|
||||
To avoid testing the deprecated portions of the API run
|
||||
```
|
||||
npm install
|
||||
npm test --disable-deprecated
|
||||
```
|
||||
|
||||
To run the tests targeting a specific version of Node-API run
|
||||
```
|
||||
npm install
|
||||
export NAPI_VERSION=X
|
||||
npm test --NAPI_VERSION=X
|
||||
```
|
||||
|
||||
where X is the version of Node-API you want to target.
|
||||
|
||||
To run a specific unit test, filter conditions are available
|
||||
|
||||
**Example:**
|
||||
compile and run only tests on objectwrap.cc and objectwrap.js
|
||||
```
|
||||
npm run unit --filter=objectwrap
|
||||
```
|
||||
|
||||
Multiple unit tests cane be selected with wildcards
|
||||
|
||||
**Example:**
|
||||
compile and run all test files ending with "reference" -> function_reference.cc, object_reference.cc, reference.cc
|
||||
```
|
||||
npm run unit --filter=*reference
|
||||
```
|
||||
|
||||
Multiple filter conditions can be joined to broaden the test selection
|
||||
|
||||
**Example:**
|
||||
compile and run all tests under folders threadsafe_function and typed_threadsafe_function and also the objectwrap.cc file
|
||||
npm run unit --filter='*function objectwrap'
|
||||
|
||||
### **Debug**
|
||||
|
||||
To run the **node-addon-api** tests with `--debug` option:
|
||||
|
||||
```
|
||||
npm run-script dev
|
||||
```
|
||||
|
||||
If you want a faster build, you might use the following option:
|
||||
|
||||
```
|
||||
npm run-script dev:incremental
|
||||
```
|
||||
|
||||
Take a look and get inspired by our **[test suite](https://github.com/nodejs/node-addon-api/tree/HEAD/test)**
|
||||
|
||||
### **Benchmarks**
|
||||
|
||||
You can run the available benchmarks using the following command:
|
||||
|
||||
```
|
||||
npm run-script benchmark
|
||||
```
|
||||
|
||||
See [benchmark/README.md](benchmark/README.md) for more details about running and adding benchmarks.
|
||||
|
||||
<a name="resources"></a>
|
||||
|
||||
### **More resource and info about native Addons**
|
||||
- **[C++ Addons](https://nodejs.org/dist/latest/docs/api/addons.html)**
|
||||
- **[Node-API](https://nodejs.org/dist/latest/docs/api/n-api.html)**
|
||||
- **[Node-API - Next Generation Node API for Native Modules](https://youtu.be/-Oniup60Afs)**
|
||||
- **[How We Migrated Realm JavaScript From NAN to Node-API](https://developer.mongodb.com/article/realm-javascript-nan-to-n-api)**
|
||||
|
||||
As node-addon-api's core mission is to expose the plain C Node-API as C++
|
||||
wrappers, tools that facilitate n-api/node-addon-api providing more
|
||||
convenient patterns for developing a Node.js add-on with n-api/node-addon-api
|
||||
can be published to NPM as standalone packages. It is also recommended to tag
|
||||
such packages with `node-addon-api` to provide more visibility to the community.
|
||||
|
||||
Quick links to NPM searches: [keywords:node-addon-api](https://www.npmjs.com/search?q=keywords%3Anode-addon-api).
|
||||
|
||||
<a name="other-bindings"></a>
|
||||
|
||||
### **Other bindings**
|
||||
|
||||
- **[napi-rs](https://napi.rs)** - (`Rust`)
|
||||
|
||||
<a name="badges"></a>
|
||||
|
||||
### **Badges**
|
||||
|
||||
The use of badges is recommended to indicate the minimum version of Node-API
|
||||
required for the module. This helps to determine which Node.js major versions are
|
||||
supported. Addon maintainers can consult the [Node-API support matrix][] to determine
|
||||
which Node.js versions provide a given Node-API version. The following badges are
|
||||
available:
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## **Contributing**
|
||||
|
||||
We love contributions from the community to **node-addon-api**!
|
||||
See [CONTRIBUTING.md](CONTRIBUTING.md) for more details on our philosophy around extending this module.
|
||||
|
||||
<a name="contributors"></a>
|
||||
|
||||
## Team members
|
||||
|
||||
### Active
|
||||
| Name | GitHub Link |
|
||||
| ------------------- | ----------------------------------------------------- |
|
||||
| Anna Henningsen | [addaleax](https://github.com/addaleax) |
|
||||
| Chengzhong Wu | [legendecas](https://github.com/legendecas) |
|
||||
| Jack Xia | [JckXia](https://github.com/JckXia) |
|
||||
| Kevin Eady | [KevinEady](https://github.com/KevinEady) |
|
||||
| Michael Dawson | [mhdawson](https://github.com/mhdawson) |
|
||||
| Nicola Del Gobbo | [NickNaso](https://github.com/NickNaso) |
|
||||
| Vladimir Morozov | [vmoroz](https://github.com/vmoroz) |
|
||||
|
||||
### Emeritus
|
||||
| Name | GitHub Link |
|
||||
| ------------------- | ----------------------------------------------------- |
|
||||
| Arunesh Chandra | [aruneshchandra](https://github.com/aruneshchandra) |
|
||||
| Benjamin Byholm | [kkoopa](https://github.com/kkoopa) |
|
||||
| Gabriel Schulhof | [gabrielschulhof](https://github.com/gabrielschulhof) |
|
||||
| Hitesh Kanwathirtha | [digitalinfinity](https://github.com/digitalinfinity) |
|
||||
| Jason Ginchereau | [jasongin](https://github.com/jasongin) |
|
||||
| Jim Schlight | [jschlight](https://github.com/jschlight) |
|
||||
| Sampson Gao | [sampsongao](https://github.com/sampsongao) |
|
||||
| Taylor Woll | [boingoing](https://github.com/boingoing) |
|
||||
|
||||
<a name="license"></a>
|
||||
|
||||
Licensed under [MIT](./LICENSE.md)
|
||||
|
||||
[ABI stability guide]: https://nodejs.org/en/docs/guides/abi-stability/
|
||||
[Node-API support matrix]: https://nodejs.org/dist/latest/docs/api/n-api.html#n_api_n_api_version_matrix
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
'variables': {
|
||||
'NAPI_VERSION%': "<!(node -p \"process.env.NAPI_VERSION || process.versions.napi\")",
|
||||
'disable_deprecated': "<!(node -p \"process.env['npm_config_disable_deprecated']\")"
|
||||
},
|
||||
'conditions': [
|
||||
['NAPI_VERSION!=""', { 'defines': ['NAPI_VERSION=<@(NAPI_VERSION)'] } ],
|
||||
['disable_deprecated=="true"', {
|
||||
'defines': ['NODE_ADDON_API_DISABLE_DEPRECATED']
|
||||
}],
|
||||
['OS=="mac"', {
|
||||
'cflags+': ['-fvisibility=hidden'],
|
||||
'xcode_settings': {
|
||||
'OTHER_CFLAGS': ['-fvisibility=hidden']
|
||||
}
|
||||
}]
|
||||
],
|
||||
'cflags': [ '-Werror', '-Wall', '-Wextra', '-Wpedantic', '-Wunused-parameter' ],
|
||||
'cflags_cc': [ '-Werror', '-Wall', '-Wextra', '-Wpedantic', '-Wunused-parameter' ]
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
{
|
||||
'defines': [ 'NAPI_CPP_EXCEPTIONS' ],
|
||||
'cflags!': [ '-fno-exceptions' ],
|
||||
'cflags_cc!': [ '-fno-exceptions' ],
|
||||
'conditions': [
|
||||
["OS=='win'", {
|
||||
"defines": [
|
||||
"_HAS_EXCEPTIONS=1"
|
||||
],
|
||||
"msvs_settings": {
|
||||
"VCCLCompilerTool": {
|
||||
"ExceptionHandling": 1,
|
||||
'EnablePREfast': 'true',
|
||||
},
|
||||
},
|
||||
}],
|
||||
["OS=='mac'", {
|
||||
'xcode_settings': {
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
|
||||
'CLANG_CXX_LIBRARY': 'libc++',
|
||||
'MACOSX_DEPLOYMENT_TARGET': '10.7',
|
||||
},
|
||||
}],
|
||||
],
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
const path = require('path');
|
||||
|
||||
const includeDir = path.relative('.', __dirname);
|
||||
|
||||
module.exports = {
|
||||
include: `"${__dirname}"`, // deprecated, can be removed as part of 4.0.0
|
||||
include_dir: includeDir,
|
||||
gyp: path.join(includeDir, 'node_api.gyp:nothing'), // deprecated.
|
||||
targets: path.join(includeDir, 'node_addon_api.gyp'),
|
||||
isNodeApiBuiltin: true,
|
||||
needsFlag: false
|
||||
};
|
||||
+186
@@ -0,0 +1,186 @@
|
||||
#ifndef SRC_NAPI_INL_DEPRECATED_H_
|
||||
#define SRC_NAPI_INL_DEPRECATED_H_
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
// PropertyDescriptor class
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
template <typename Getter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
const char* utf8name,
|
||||
Getter getter,
|
||||
napi_property_attributes attributes,
|
||||
void* /*data*/) {
|
||||
using CbData = details::CallbackData<Getter, Napi::Value>;
|
||||
// TODO: Delete when the function is destroyed
|
||||
auto callbackData = new CbData({getter, nullptr});
|
||||
|
||||
return PropertyDescriptor({utf8name,
|
||||
nullptr,
|
||||
nullptr,
|
||||
CbData::Wrapper,
|
||||
nullptr,
|
||||
nullptr,
|
||||
attributes,
|
||||
callbackData});
|
||||
}
|
||||
|
||||
template <typename Getter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
const std::string& utf8name,
|
||||
Getter getter,
|
||||
napi_property_attributes attributes,
|
||||
void* data) {
|
||||
return Accessor(utf8name.c_str(), getter, attributes, data);
|
||||
}
|
||||
|
||||
template <typename Getter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
napi_value name,
|
||||
Getter getter,
|
||||
napi_property_attributes attributes,
|
||||
void* /*data*/) {
|
||||
using CbData = details::CallbackData<Getter, Napi::Value>;
|
||||
// TODO: Delete when the function is destroyed
|
||||
auto callbackData = new CbData({getter, nullptr});
|
||||
|
||||
return PropertyDescriptor({nullptr,
|
||||
name,
|
||||
nullptr,
|
||||
CbData::Wrapper,
|
||||
nullptr,
|
||||
nullptr,
|
||||
attributes,
|
||||
callbackData});
|
||||
}
|
||||
|
||||
template <typename Getter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
Name name, Getter getter, napi_property_attributes attributes, void* data) {
|
||||
napi_value nameValue = name;
|
||||
return PropertyDescriptor::Accessor(nameValue, getter, attributes, data);
|
||||
}
|
||||
|
||||
template <typename Getter, typename Setter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
const char* utf8name,
|
||||
Getter getter,
|
||||
Setter setter,
|
||||
napi_property_attributes attributes,
|
||||
void* /*data*/) {
|
||||
using CbData = details::AccessorCallbackData<Getter, Setter>;
|
||||
// TODO: Delete when the function is destroyed
|
||||
auto callbackData = new CbData({getter, setter, nullptr});
|
||||
|
||||
return PropertyDescriptor({utf8name,
|
||||
nullptr,
|
||||
nullptr,
|
||||
CbData::GetterWrapper,
|
||||
CbData::SetterWrapper,
|
||||
nullptr,
|
||||
attributes,
|
||||
callbackData});
|
||||
}
|
||||
|
||||
template <typename Getter, typename Setter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
const std::string& utf8name,
|
||||
Getter getter,
|
||||
Setter setter,
|
||||
napi_property_attributes attributes,
|
||||
void* data) {
|
||||
return Accessor(utf8name.c_str(), getter, setter, attributes, data);
|
||||
}
|
||||
|
||||
template <typename Getter, typename Setter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
napi_value name,
|
||||
Getter getter,
|
||||
Setter setter,
|
||||
napi_property_attributes attributes,
|
||||
void* /*data*/) {
|
||||
using CbData = details::AccessorCallbackData<Getter, Setter>;
|
||||
// TODO: Delete when the function is destroyed
|
||||
auto callbackData = new CbData({getter, setter, nullptr});
|
||||
|
||||
return PropertyDescriptor({nullptr,
|
||||
name,
|
||||
nullptr,
|
||||
CbData::GetterWrapper,
|
||||
CbData::SetterWrapper,
|
||||
nullptr,
|
||||
attributes,
|
||||
callbackData});
|
||||
}
|
||||
|
||||
template <typename Getter, typename Setter>
|
||||
inline PropertyDescriptor PropertyDescriptor::Accessor(
|
||||
Name name,
|
||||
Getter getter,
|
||||
Setter setter,
|
||||
napi_property_attributes attributes,
|
||||
void* data) {
|
||||
napi_value nameValue = name;
|
||||
return PropertyDescriptor::Accessor(
|
||||
nameValue, getter, setter, attributes, data);
|
||||
}
|
||||
|
||||
template <typename Callable>
|
||||
inline PropertyDescriptor PropertyDescriptor::Function(
|
||||
const char* utf8name,
|
||||
Callable cb,
|
||||
napi_property_attributes attributes,
|
||||
void* /*data*/) {
|
||||
using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr)));
|
||||
using CbData = details::CallbackData<Callable, ReturnType>;
|
||||
// TODO: Delete when the function is destroyed
|
||||
auto callbackData = new CbData({cb, nullptr});
|
||||
|
||||
return PropertyDescriptor({utf8name,
|
||||
nullptr,
|
||||
CbData::Wrapper,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
attributes,
|
||||
callbackData});
|
||||
}
|
||||
|
||||
template <typename Callable>
|
||||
inline PropertyDescriptor PropertyDescriptor::Function(
|
||||
const std::string& utf8name,
|
||||
Callable cb,
|
||||
napi_property_attributes attributes,
|
||||
void* data) {
|
||||
return Function(utf8name.c_str(), cb, attributes, data);
|
||||
}
|
||||
|
||||
template <typename Callable>
|
||||
inline PropertyDescriptor PropertyDescriptor::Function(
|
||||
napi_value name,
|
||||
Callable cb,
|
||||
napi_property_attributes attributes,
|
||||
void* /*data*/) {
|
||||
using ReturnType = decltype(cb(CallbackInfo(nullptr, nullptr)));
|
||||
using CbData = details::CallbackData<Callable, ReturnType>;
|
||||
// TODO: Delete when the function is destroyed
|
||||
auto callbackData = new CbData({cb, nullptr});
|
||||
|
||||
return PropertyDescriptor({nullptr,
|
||||
name,
|
||||
CbData::Wrapper,
|
||||
nullptr,
|
||||
nullptr,
|
||||
nullptr,
|
||||
attributes,
|
||||
callbackData});
|
||||
}
|
||||
|
||||
template <typename Callable>
|
||||
inline PropertyDescriptor PropertyDescriptor::Function(
|
||||
Name name, Callable cb, napi_property_attributes attributes, void* data) {
|
||||
napi_value nameValue = name;
|
||||
return PropertyDescriptor::Function(nameValue, cb, attributes, data);
|
||||
}
|
||||
|
||||
#endif // !SRC_NAPI_INL_DEPRECATED_H_
|
||||
+6607
File diff suppressed because it is too large
Load Diff
+3201
File diff suppressed because it is too large
Load Diff
+32
@@ -0,0 +1,32 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'node_addon_api',
|
||||
'type': 'none',
|
||||
'sources': [ 'napi.h', 'napi-inl.h' ],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [ '.' ],
|
||||
'includes': ['noexcept.gypi'],
|
||||
}
|
||||
},
|
||||
{
|
||||
'target_name': 'node_addon_api_except',
|
||||
'type': 'none',
|
||||
'sources': [ 'napi.h', 'napi-inl.h' ],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [ '.' ],
|
||||
'includes': ['except.gypi'],
|
||||
}
|
||||
},
|
||||
{
|
||||
'target_name': 'node_addon_api_maybe',
|
||||
'type': 'none',
|
||||
'sources': [ 'napi.h', 'napi-inl.h' ],
|
||||
'direct_dependent_settings': {
|
||||
'include_dirs': [ '.' ],
|
||||
'includes': ['noexcept.gypi'],
|
||||
'defines': ['NODE_ADDON_API_ENABLE_MAYBE']
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'nothing',
|
||||
'type': 'static_library',
|
||||
'sources': [ 'nothing.c' ]
|
||||
}
|
||||
]
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
{
|
||||
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
|
||||
'cflags': [ '-fno-exceptions' ],
|
||||
'cflags_cc': [ '-fno-exceptions' ],
|
||||
'conditions': [
|
||||
["OS=='win'", {
|
||||
# _HAS_EXCEPTIONS is already defined and set to 0 in common.gypi
|
||||
#"defines": [
|
||||
# "_HAS_EXCEPTIONS=0"
|
||||
#],
|
||||
"msvs_settings": {
|
||||
"VCCLCompilerTool": {
|
||||
'ExceptionHandling': 0,
|
||||
'EnablePREfast': 'true',
|
||||
},
|
||||
},
|
||||
}],
|
||||
["OS=='mac'", {
|
||||
'xcode_settings': {
|
||||
'CLANG_CXX_LIBRARY': 'libc++',
|
||||
'MACOSX_DEPLOYMENT_TARGET': '10.7',
|
||||
'GCC_ENABLE_CPP_EXCEPTIONS': 'NO',
|
||||
},
|
||||
}],
|
||||
],
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"versions": [
|
||||
{
|
||||
"version": "*",
|
||||
"target": {
|
||||
"node": "active"
|
||||
},
|
||||
"response": {
|
||||
"type": "time-permitting",
|
||||
"paid": false,
|
||||
"contact": {
|
||||
"name": "node-addon-api team",
|
||||
"url": "https://github.com/nodejs/node-addon-api/issues"
|
||||
}
|
||||
},
|
||||
"backing": [ { "project": "https://github.com/nodejs" },
|
||||
{ "foundation": "https://openjsf.org/" }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
+480
@@ -0,0 +1,480 @@
|
||||
{
|
||||
"bugs": {
|
||||
"url": "https://github.com/nodejs/node-addon-api/issues"
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"name": "Abhishek Kumar Singh",
|
||||
"url": "https://github.com/abhi11210646"
|
||||
},
|
||||
{
|
||||
"name": "Alba Mendez",
|
||||
"url": "https://github.com/jmendeth"
|
||||
},
|
||||
{
|
||||
"name": "Alexander Floh",
|
||||
"url": "https://github.com/alexanderfloh"
|
||||
},
|
||||
{
|
||||
"name": "Ammar Faizi",
|
||||
"url": "https://github.com/ammarfaizi2"
|
||||
},
|
||||
{
|
||||
"name": "András Timár, Dr",
|
||||
"url": "https://github.com/timarandras"
|
||||
},
|
||||
{
|
||||
"name": "Andrew Petersen",
|
||||
"url": "https://github.com/kirbysayshi"
|
||||
},
|
||||
{
|
||||
"name": "Anisha Rohra",
|
||||
"url": "https://github.com/anisha-rohra"
|
||||
},
|
||||
{
|
||||
"name": "Anna Henningsen",
|
||||
"url": "https://github.com/addaleax"
|
||||
},
|
||||
{
|
||||
"name": "Arnaud Botella",
|
||||
"url": "https://github.com/BotellaA"
|
||||
},
|
||||
{
|
||||
"name": "Arunesh Chandra",
|
||||
"url": "https://github.com/aruneshchandra"
|
||||
},
|
||||
{
|
||||
"name": "Azlan Mukhtar",
|
||||
"url": "https://github.com/azlan"
|
||||
},
|
||||
{
|
||||
"name": "Ben Berman",
|
||||
"url": "https://github.com/rivertam"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Byholm",
|
||||
"url": "https://github.com/kkoopa"
|
||||
},
|
||||
{
|
||||
"name": "Bill Gallafent",
|
||||
"url": "https://github.com/gallafent"
|
||||
},
|
||||
{
|
||||
"name": "blagoev",
|
||||
"url": "https://github.com/blagoev"
|
||||
},
|
||||
{
|
||||
"name": "Bruce A. MacNaughton",
|
||||
"url": "https://github.com/bmacnaughton"
|
||||
},
|
||||
{
|
||||
"name": "Cory Mickelson",
|
||||
"url": "https://github.com/corymickelson"
|
||||
},
|
||||
{
|
||||
"name": "Daniel Bevenius",
|
||||
"url": "https://github.com/danbev"
|
||||
},
|
||||
{
|
||||
"name": "Dante Calderón",
|
||||
"url": "https://github.com/dantehemerson"
|
||||
},
|
||||
{
|
||||
"name": "Darshan Sen",
|
||||
"url": "https://github.com/RaisinTen"
|
||||
},
|
||||
{
|
||||
"name": "David Halls",
|
||||
"url": "https://github.com/davedoesdev"
|
||||
},
|
||||
{
|
||||
"name": "Deepak Rajamohan",
|
||||
"url": "https://github.com/deepakrkris"
|
||||
},
|
||||
{
|
||||
"name": "Dmitry Ashkadov",
|
||||
"url": "https://github.com/dmitryash"
|
||||
},
|
||||
{
|
||||
"name": "Dongjin Na",
|
||||
"url": "https://github.com/nadongguri"
|
||||
},
|
||||
{
|
||||
"name": "Doni Rubiagatra",
|
||||
"url": "https://github.com/rubiagatra"
|
||||
},
|
||||
{
|
||||
"name": "Eric Bickle",
|
||||
"url": "https://github.com/ebickle"
|
||||
},
|
||||
{
|
||||
"name": "extremeheat",
|
||||
"url": "https://github.com/extremeheat"
|
||||
},
|
||||
{
|
||||
"name": "Feng Yu",
|
||||
"url": "https://github.com/F3n67u"
|
||||
},
|
||||
{
|
||||
"name": "Ferdinand Holzer",
|
||||
"url": "https://github.com/fholzer"
|
||||
},
|
||||
{
|
||||
"name": "Gabriel Schulhof",
|
||||
"url": "https://github.com/gabrielschulhof"
|
||||
},
|
||||
{
|
||||
"name": "Guenter Sandner",
|
||||
"url": "https://github.com/gms1"
|
||||
},
|
||||
{
|
||||
"name": "Gus Caplan",
|
||||
"url": "https://github.com/devsnek"
|
||||
},
|
||||
{
|
||||
"name": "Helio Frota",
|
||||
"url": "https://github.com/helio-frota"
|
||||
},
|
||||
{
|
||||
"name": "Hitesh Kanwathirtha",
|
||||
"url": "https://github.com/digitalinfinity"
|
||||
},
|
||||
{
|
||||
"name": "ikokostya",
|
||||
"url": "https://github.com/ikokostya"
|
||||
},
|
||||
{
|
||||
"name": "Jack Xia",
|
||||
"url": "https://github.com/JckXia"
|
||||
},
|
||||
{
|
||||
"name": "Jake Barnes",
|
||||
"url": "https://github.com/DuBistKomisch"
|
||||
},
|
||||
{
|
||||
"name": "Jake Yoon",
|
||||
"url": "https://github.com/yjaeseok"
|
||||
},
|
||||
{
|
||||
"name": "Jason Ginchereau",
|
||||
"url": "https://github.com/jasongin"
|
||||
},
|
||||
{
|
||||
"name": "Jenny",
|
||||
"url": "https://github.com/egg-bread"
|
||||
},
|
||||
{
|
||||
"name": "Jeroen Janssen",
|
||||
"url": "https://github.com/japj"
|
||||
},
|
||||
{
|
||||
"name": "Jim Schlight",
|
||||
"url": "https://github.com/jschlight"
|
||||
},
|
||||
{
|
||||
"name": "Jinho Bang",
|
||||
"url": "https://github.com/romandev"
|
||||
},
|
||||
{
|
||||
"name": "José Expósito",
|
||||
"url": "https://github.com/JoseExposito"
|
||||
},
|
||||
{
|
||||
"name": "joshgarde",
|
||||
"url": "https://github.com/joshgarde"
|
||||
},
|
||||
{
|
||||
"name": "Julian Mesa",
|
||||
"url": "https://github.com/julianmesa-gitkraken"
|
||||
},
|
||||
{
|
||||
"name": "Kasumi Hanazuki",
|
||||
"url": "https://github.com/hanazuki"
|
||||
},
|
||||
{
|
||||
"name": "Kelvin",
|
||||
"url": "https://github.com/kelvinhammond"
|
||||
},
|
||||
{
|
||||
"name": "Kevin Eady",
|
||||
"url": "https://github.com/KevinEady"
|
||||
},
|
||||
{
|
||||
"name": "Kévin VOYER",
|
||||
"url": "https://github.com/kecsou"
|
||||
},
|
||||
{
|
||||
"name": "kidneysolo",
|
||||
"url": "https://github.com/kidneysolo"
|
||||
},
|
||||
{
|
||||
"name": "Koki Nishihara",
|
||||
"url": "https://github.com/Nishikoh"
|
||||
},
|
||||
{
|
||||
"name": "Konstantin Tarkus",
|
||||
"url": "https://github.com/koistya"
|
||||
},
|
||||
{
|
||||
"name": "Kyle Farnung",
|
||||
"url": "https://github.com/kfarnung"
|
||||
},
|
||||
{
|
||||
"name": "Kyle Kovacs",
|
||||
"url": "https://github.com/nullromo"
|
||||
},
|
||||
{
|
||||
"name": "legendecas",
|
||||
"url": "https://github.com/legendecas"
|
||||
},
|
||||
{
|
||||
"name": "LongYinan",
|
||||
"url": "https://github.com/Brooooooklyn"
|
||||
},
|
||||
{
|
||||
"name": "Lovell Fuller",
|
||||
"url": "https://github.com/lovell"
|
||||
},
|
||||
{
|
||||
"name": "Luciano Martorella",
|
||||
"url": "https://github.com/lmartorella"
|
||||
},
|
||||
{
|
||||
"name": "mastergberry",
|
||||
"url": "https://github.com/mastergberry"
|
||||
},
|
||||
{
|
||||
"name": "Mathias Küsel",
|
||||
"url": "https://github.com/mathiask88"
|
||||
},
|
||||
{
|
||||
"name": "Mathias Stearn",
|
||||
"url": "https://github.com/RedBeard0531"
|
||||
},
|
||||
{
|
||||
"name": "Matteo Collina",
|
||||
"url": "https://github.com/mcollina"
|
||||
},
|
||||
{
|
||||
"name": "Michael Dawson",
|
||||
"url": "https://github.com/mhdawson"
|
||||
},
|
||||
{
|
||||
"name": "Michael Price",
|
||||
"url": "https://github.com/mikepricedev"
|
||||
},
|
||||
{
|
||||
"name": "Michele Campus",
|
||||
"url": "https://github.com/kYroL01"
|
||||
},
|
||||
{
|
||||
"name": "Mikhail Cheshkov",
|
||||
"url": "https://github.com/mcheshkov"
|
||||
},
|
||||
{
|
||||
"name": "nempoBu4",
|
||||
"url": "https://github.com/nempoBu4"
|
||||
},
|
||||
{
|
||||
"name": "Nicola Del Gobbo",
|
||||
"url": "https://github.com/NickNaso"
|
||||
},
|
||||
{
|
||||
"name": "Nick Soggin",
|
||||
"url": "https://github.com/iSkore"
|
||||
},
|
||||
{
|
||||
"name": "Nikolai Vavilov",
|
||||
"url": "https://github.com/seishun"
|
||||
},
|
||||
{
|
||||
"name": "Nurbol Alpysbayev",
|
||||
"url": "https://github.com/anurbol"
|
||||
},
|
||||
{
|
||||
"name": "pacop",
|
||||
"url": "https://github.com/pacop"
|
||||
},
|
||||
{
|
||||
"name": "Peter Šándor",
|
||||
"url": "https://github.com/petersandor"
|
||||
},
|
||||
{
|
||||
"name": "Philipp Renoth",
|
||||
"url": "https://github.com/DaAitch"
|
||||
},
|
||||
{
|
||||
"name": "rgerd",
|
||||
"url": "https://github.com/rgerd"
|
||||
},
|
||||
{
|
||||
"name": "Richard Lau",
|
||||
"url": "https://github.com/richardlau"
|
||||
},
|
||||
{
|
||||
"name": "Rolf Timmermans",
|
||||
"url": "https://github.com/rolftimmermans"
|
||||
},
|
||||
{
|
||||
"name": "Ross Weir",
|
||||
"url": "https://github.com/ross-weir"
|
||||
},
|
||||
{
|
||||
"name": "Ryuichi Okumura",
|
||||
"url": "https://github.com/okuryu"
|
||||
},
|
||||
{
|
||||
"name": "Saint Gabriel",
|
||||
"url": "https://github.com/chineduG"
|
||||
},
|
||||
{
|
||||
"name": "Sampson Gao",
|
||||
"url": "https://github.com/sampsongao"
|
||||
},
|
||||
{
|
||||
"name": "Sam Roberts",
|
||||
"url": "https://github.com/sam-github"
|
||||
},
|
||||
{
|
||||
"name": "strager",
|
||||
"url": "https://github.com/strager"
|
||||
},
|
||||
{
|
||||
"name": "Taylor Woll",
|
||||
"url": "https://github.com/boingoing"
|
||||
},
|
||||
{
|
||||
"name": "Thomas Gentilhomme",
|
||||
"url": "https://github.com/fraxken"
|
||||
},
|
||||
{
|
||||
"name": "Tim Rach",
|
||||
"url": "https://github.com/timrach"
|
||||
},
|
||||
{
|
||||
"name": "Tobias Nießen",
|
||||
"url": "https://github.com/tniessen"
|
||||
},
|
||||
{
|
||||
"name": "todoroff",
|
||||
"url": "https://github.com/todoroff"
|
||||
},
|
||||
{
|
||||
"name": "Toyo Li",
|
||||
"url": "https://github.com/toyobayashi"
|
||||
},
|
||||
{
|
||||
"name": "Tux3",
|
||||
"url": "https://github.com/tux3"
|
||||
},
|
||||
{
|
||||
"name": "Vlad Velmisov",
|
||||
"url": "https://github.com/Velmisov"
|
||||
},
|
||||
{
|
||||
"name": "Vladimir Morozov",
|
||||
"url": "https://github.com/vmoroz"
|
||||
|
||||
},
|
||||
{
|
||||
"name": "WenheLI",
|
||||
"url": "https://github.com/WenheLI"
|
||||
},
|
||||
{
|
||||
"name": "Xuguang Mei",
|
||||
"url": "https://github.com/meixg"
|
||||
},
|
||||
{
|
||||
"name": "Yohei Kishimoto",
|
||||
"url": "https://github.com/morokosi"
|
||||
},
|
||||
{
|
||||
"name": "Yulong Wang",
|
||||
"url": "https://github.com/fs-eire"
|
||||
},
|
||||
{
|
||||
"name": "Ziqiu Zhao",
|
||||
"url": "https://github.com/ZzqiZQute"
|
||||
},
|
||||
{
|
||||
"name": "Feng Yu",
|
||||
"url": "https://github.com/F3n67u"
|
||||
},
|
||||
{
|
||||
"name": "wanlu wang",
|
||||
"url": "https://github.com/wanlu"
|
||||
},
|
||||
{
|
||||
"name": "Caleb Hearon",
|
||||
"url": "https://github.com/chearon"
|
||||
},
|
||||
{
|
||||
"name": "Marx",
|
||||
"url": "https://github.com/MarxJiao"
|
||||
},
|
||||
{
|
||||
"name": "Ömer AKGÜL",
|
||||
"url": "https://github.com/tuhalf"
|
||||
}
|
||||
],
|
||||
"description": "Node.js API (Node-API)",
|
||||
"devDependencies": {
|
||||
"benchmark": "^2.1.4",
|
||||
"bindings": "^1.5.0",
|
||||
"clang-format": "^1.4.0",
|
||||
"eslint": "^7.32.0",
|
||||
"eslint-config-semistandard": "^16.0.0",
|
||||
"eslint-config-standard": "^16.0.3",
|
||||
"eslint-plugin-import": "^2.24.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"fs-extra": "^11.1.1",
|
||||
"path": "^0.12.7",
|
||||
"pre-commit": "^1.2.2",
|
||||
"safe-buffer": "^5.1.1"
|
||||
},
|
||||
"directories": {},
|
||||
"gypfile": false,
|
||||
"homepage": "https://github.com/nodejs/node-addon-api",
|
||||
"keywords": [
|
||||
"n-api",
|
||||
"napi",
|
||||
"addon",
|
||||
"native",
|
||||
"bindings",
|
||||
"c",
|
||||
"c++",
|
||||
"nan",
|
||||
"node-addon-api"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "index.js",
|
||||
"name": "node-addon-api",
|
||||
"readme": "README.md",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git://github.com/nodejs/node-addon-api.git"
|
||||
},
|
||||
"files": [
|
||||
"*.{c,h,gyp,gypi}",
|
||||
"package-support.json",
|
||||
"tools/"
|
||||
],
|
||||
"scripts": {
|
||||
"prebenchmark": "node-gyp rebuild -C benchmark",
|
||||
"benchmark": "node benchmark",
|
||||
"pretest": "node-gyp rebuild -C test",
|
||||
"test": "node test",
|
||||
"test:debug": "node-gyp rebuild -C test --debug && NODE_API_BUILD_CONFIG=Debug node ./test/index.js",
|
||||
"predev": "node-gyp rebuild -C test --debug",
|
||||
"dev": "node test",
|
||||
"predev:incremental": "node-gyp configure build -C test --debug",
|
||||
"dev:incremental": "node test",
|
||||
"doc": "doxygen doc/Doxyfile",
|
||||
"lint": "node tools/eslint-format && node tools/clang-format",
|
||||
"lint:fix": "node tools/clang-format --fix && node tools/eslint-format --fix"
|
||||
},
|
||||
"pre-commit": "lint",
|
||||
"version": "7.1.1",
|
||||
"support": true
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
# Tools
|
||||
|
||||
## clang-format
|
||||
|
||||
The clang-format checking tools is designed to check changed lines of code compared to given git-refs.
|
||||
|
||||
## Migration Script
|
||||
|
||||
The migration tool is designed to reduce repetitive work in the migration process. However, the script is not aiming to convert every thing for you. There are usually some small fixes and major reconstruction required.
|
||||
|
||||
### How To Use
|
||||
|
||||
To run the conversion script, first make sure you have the latest `node-addon-api` in your `node_modules` directory.
|
||||
```
|
||||
npm install node-addon-api
|
||||
```
|
||||
|
||||
Then run the script passing your project directory
|
||||
```
|
||||
node ./node_modules/node-addon-api/tools/conversion.js ./
|
||||
```
|
||||
|
||||
After finish, recompile and debug things that are missed by the script.
|
||||
|
||||
|
||||
### Quick Fixes
|
||||
Here is the list of things that can be fixed easily.
|
||||
1. Change your methods' return value to void if it doesn't return value to JavaScript.
|
||||
2. Use `.` to access attribute or to invoke member function in Napi::Object instead of `->`.
|
||||
3. `Napi::New(env, value);` to `Napi::[Type]::New(env, value);
|
||||
|
||||
|
||||
### Major Reconstructions
|
||||
The implementation of `Napi::ObjectWrap` is significantly different from NAN's. `Napi::ObjectWrap` takes a pointer to the wrapped object and creates a reference to the wrapped object inside ObjectWrap constructor. `Napi::ObjectWrap` also associates wrapped object's instance methods to Javascript module instead of static methods like NAN.
|
||||
|
||||
So if you use Nan::ObjectWrap in your module, you will need to execute the following steps.
|
||||
|
||||
1. Convert your [ClassName]::New function to a constructor function that takes a `Napi::CallbackInfo`. Declare it as
|
||||
```
|
||||
[ClassName](const Napi::CallbackInfo& info);
|
||||
```
|
||||
and define it as
|
||||
```
|
||||
[ClassName]::[ClassName](const Napi::CallbackInfo& info) : Napi::ObjectWrap<[ClassName]>(info){
|
||||
...
|
||||
}
|
||||
```
|
||||
This way, the `Napi::ObjectWrap` constructor will be invoked after the object has been instantiated and `Napi::ObjectWrap` can use the `this` pointer to create a reference to the wrapped object.
|
||||
|
||||
2. Move your original constructor code into the new constructor. Delete your original constructor.
|
||||
3. In your class initialization function, associate native methods in the following way.
|
||||
```
|
||||
Napi::FunctionReference constructor;
|
||||
|
||||
void [ClassName]::Init(Napi::Env env, Napi::Object exports, Napi::Object module) {
|
||||
Napi::HandleScope scope(env);
|
||||
Napi::Function ctor = DefineClass(env, "Canvas", {
|
||||
InstanceMethod<&[ClassName]::Func1>("Func1"),
|
||||
InstanceMethod<&[ClassName]::Func2>("Func2"),
|
||||
InstanceAccessor<&[ClassName]::ValueGetter>("Value"),
|
||||
StaticMethod<&[ClassName]::StaticMethod>("MethodName"),
|
||||
InstanceValue("Value", Napi::[Type]::New(env, value)),
|
||||
});
|
||||
|
||||
constructor = Napi::Persistent(ctor);
|
||||
constructor .SuppressDestruct();
|
||||
exports.Set("[ClassName]", ctor);
|
||||
}
|
||||
```
|
||||
4. In function where you need to Unwrap the ObjectWrap in NAN like `[ClassName]* native = Nan::ObjectWrap::Unwrap<[ClassName]>(info.This());`, use `this` pointer directly as the unwrapped object as each ObjectWrap instance is associated with a unique object instance.
|
||||
|
||||
|
||||
If you still find issues after following this guide, please leave us an issue describing your problem and we will try to resolve it.
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
'use strict';
|
||||
// Descend into a directory structure and, for each file matching *.node, output
|
||||
// based on the imports found in the file whether it's an N-API module or not.
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
// Read the output of the command, break it into lines, and use the reducer to
|
||||
// decide whether the file is an N-API module or not.
|
||||
function checkFile (file, command, argv, reducer) {
|
||||
const child = require('child_process').spawn(command, argv, {
|
||||
stdio: ['inherit', 'pipe', 'inherit']
|
||||
});
|
||||
let leftover = '';
|
||||
let isNapi;
|
||||
child.stdout.on('data', (chunk) => {
|
||||
if (isNapi === undefined) {
|
||||
chunk = (leftover + chunk.toString()).split(/[\r\n]+/);
|
||||
leftover = chunk.pop();
|
||||
isNapi = chunk.reduce(reducer, isNapi);
|
||||
if (isNapi !== undefined) {
|
||||
child.kill();
|
||||
}
|
||||
}
|
||||
});
|
||||
child.on('close', (code, signal) => {
|
||||
if ((code === null && signal !== null) || (code !== 0)) {
|
||||
console.log(
|
||||
command + ' exited with code: ' + code + ' and signal: ' + signal);
|
||||
} else {
|
||||
// Green if it's a N-API module, red otherwise.
|
||||
console.log(
|
||||
'\x1b[' + (isNapi ? '42' : '41') + 'm' +
|
||||
(isNapi ? ' N-API' : 'Not N-API') +
|
||||
'\x1b[0m: ' + file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Use nm -a to list symbols.
|
||||
function checkFileUNIX (file) {
|
||||
checkFile(file, 'nm', ['-a', file], (soFar, line) => {
|
||||
if (soFar === undefined) {
|
||||
line = line.match(/([0-9a-f]*)? ([a-zA-Z]) (.*$)/);
|
||||
if (line[2] === 'U') {
|
||||
if (/^napi/.test(line[3])) {
|
||||
soFar = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return soFar;
|
||||
});
|
||||
}
|
||||
|
||||
// Use dumpbin /imports to list symbols.
|
||||
function checkFileWin32 (file) {
|
||||
checkFile(file, 'dumpbin', ['/imports', file], (soFar, line) => {
|
||||
if (soFar === undefined) {
|
||||
line = line.match(/([0-9a-f]*)? +([a-zA-Z0-9]) (.*$)/);
|
||||
if (line && /^napi/.test(line[line.length - 1])) {
|
||||
soFar = true;
|
||||
}
|
||||
}
|
||||
return soFar;
|
||||
});
|
||||
}
|
||||
|
||||
// Descend into a directory structure and pass each file ending in '.node' to
|
||||
// one of the above checks, depending on the OS.
|
||||
function recurse (top) {
|
||||
fs.readdir(top, (error, items) => {
|
||||
if (error) {
|
||||
throw new Error('error reading directory ' + top + ': ' + error);
|
||||
}
|
||||
items.forEach((item) => {
|
||||
item = path.join(top, item);
|
||||
fs.stat(item, ((item) => (error, stats) => {
|
||||
if (error) {
|
||||
throw new Error('error about ' + item + ': ' + error);
|
||||
}
|
||||
if (stats.isDirectory()) {
|
||||
recurse(item);
|
||||
} else if (/[.]node$/.test(item) &&
|
||||
// Explicitly ignore files called 'nothing.node' because they are
|
||||
// artefacts of node-addon-api having identified a version of
|
||||
// Node.js that ships with a correct implementation of N-API.
|
||||
path.basename(item) !== 'nothing.node') {
|
||||
process.platform === 'win32'
|
||||
? checkFileWin32(item)
|
||||
: checkFileUNIX(item);
|
||||
}
|
||||
})(item));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Start with the directory given on the command line or the current directory
|
||||
// if nothing was given.
|
||||
recurse(process.argv.length > 3 ? process.argv[2] : '.');
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const spawn = require('child_process').spawnSync;
|
||||
const path = require('path');
|
||||
|
||||
const filesToCheck = ['*.h', '*.cc'];
|
||||
const FORMAT_START = process.env.FORMAT_START || 'main';
|
||||
|
||||
function main (args) {
|
||||
let fix = false;
|
||||
while (args.length > 0) {
|
||||
switch (args[0]) {
|
||||
case '-f':
|
||||
case '--fix':
|
||||
fix = true;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
args.shift();
|
||||
}
|
||||
|
||||
const clangFormatPath = path.dirname(require.resolve('clang-format'));
|
||||
const binary = process.platform === 'win32'
|
||||
? 'node_modules\\.bin\\clang-format.cmd'
|
||||
: 'node_modules/.bin/clang-format';
|
||||
const options = ['--binary=' + binary, '--style=file'];
|
||||
if (fix) {
|
||||
options.push(FORMAT_START);
|
||||
} else {
|
||||
options.push('--diff', FORMAT_START);
|
||||
}
|
||||
|
||||
const gitClangFormatPath = path.join(clangFormatPath, 'bin/git-clang-format');
|
||||
const result = spawn(
|
||||
'python',
|
||||
[gitClangFormatPath, ...options, '--', ...filesToCheck],
|
||||
{ encoding: 'utf-8' }
|
||||
);
|
||||
|
||||
if (result.stderr) {
|
||||
console.error('Error running git-clang-format:', result.stderr);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const clangFormatOutput = result.stdout.trim();
|
||||
// Bail fast if in fix mode.
|
||||
if (fix) {
|
||||
console.log(clangFormatOutput);
|
||||
return 0;
|
||||
}
|
||||
// Detect if there is any complains from clang-format
|
||||
if (
|
||||
clangFormatOutput !== '' &&
|
||||
clangFormatOutput !== 'no modified files to format' &&
|
||||
clangFormatOutput !== 'clang-format did not modify any files'
|
||||
) {
|
||||
console.error(clangFormatOutput);
|
||||
const fixCmd = 'npm run lint:fix';
|
||||
console.error(`
|
||||
ERROR: please run "${fixCmd}" to format changes in your commit
|
||||
Note that when running the command locally, please keep your local
|
||||
main branch and working branch up to date with nodejs/node-addon-api
|
||||
to exclude un-related complains.
|
||||
Or you can run "env FORMAT_START=upstream/main ${fixCmd}".`);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
process.exitCode = main(process.argv.slice(2));
|
||||
}
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
#! /usr/bin/env node
|
||||
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
const dir = args[0];
|
||||
if (!dir) {
|
||||
console.log('Usage: node ' + path.basename(__filename) + ' <target-dir>');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const NodeApiVersion = require('../package.json').version;
|
||||
|
||||
const disable = args[1];
|
||||
let ConfigFileOperations;
|
||||
if (disable !== '--disable' && dir !== '--disable') {
|
||||
ConfigFileOperations = {
|
||||
'package.json': [
|
||||
[/([ ]*)"dependencies": {/g, '$1"dependencies": {\n$1 "node-addon-api": "' + NodeApiVersion + '",'],
|
||||
[/[ ]*"nan": *"[^"]+"(,|)[\n\r]/g, '']
|
||||
],
|
||||
'binding.gyp': [
|
||||
[/([ ]*)'include_dirs': \[/g, '$1\'include_dirs\': [\n$1 \'<!(node -p "require(\\\'node-addon-api\\\').include_dir")\','],
|
||||
[/([ ]*)"include_dirs": \[/g, '$1"include_dirs": [\n$1 "<!(node -p \\"require(\'node-addon-api\').include_dir\\")",'],
|
||||
[/[ ]*("|')<!\(node -e ("|'|\\"|\\')require\(("|'|\\"|\\')nan("|'|\\"|\\')\)("|'|\\"|\\')\)("|')(,|)[\r\n]/g, ''],
|
||||
[/([ ]*)("|')target_name("|'): ("|')(.+?)("|'),/g, '$1$2target_name$2: $4$5$6,\n $2cflags!$2: [ $2-fno-exceptions$2 ],\n $2cflags_cc!$2: [ $2-fno-exceptions$2 ],\n $2xcode_settings$2: { $2GCC_ENABLE_CPP_EXCEPTIONS$2: $2YES$2,\n $2CLANG_CXX_LIBRARY$2: $2libc++$2,\n $2MACOSX_DEPLOYMENT_TARGET$2: $210.7$2,\n },\n $2msvs_settings$2: {\n $2VCCLCompilerTool$2: { $2ExceptionHandling$2: 1 },\n },']
|
||||
]
|
||||
};
|
||||
} else {
|
||||
ConfigFileOperations = {
|
||||
'package.json': [
|
||||
[/([ ]*)"dependencies": {/g, '$1"dependencies": {\n$1 "node-addon-api": "' + NodeApiVersion + '",'],
|
||||
[/[ ]*"nan": *"[^"]+"(,|)[\n\r]/g, '']
|
||||
],
|
||||
'binding.gyp': [
|
||||
[/([ ]*)'include_dirs': \[/g, '$1\'include_dirs\': [\n$1 \'<!(node -p "require(\\\'node-addon-api\\\').include_dir")\','],
|
||||
[/([ ]*)"include_dirs": \[/g, '$1"include_dirs": [\n$1 "<!(node -p \'require(\\"node-addon-api\\").include_dir\')",'],
|
||||
[/[ ]*("|')<!\(node -e ("|'|\\"|\\')require\(("|'|\\"|\\')nan("|'|\\"|\\')\)("|'|\\"|\\')\)("|')(,|)[\r\n]/g, ''],
|
||||
[/([ ]*)("|')target_name("|'): ("|')(.+?)("|'),/g, '$1$2target_name$2: $4$5$6,\n $2cflags!$2: [ $2-fno-exceptions$2 ],\n $2cflags_cc!$2: [ $2-fno-exceptions$2 ],\n $2defines$2: [ $2NAPI_DISABLE_CPP_EXCEPTIONS$2 ],\n $2conditions$2: [\n [\'OS=="win"\', { $2defines$2: [ $2_HAS_EXCEPTIONS=1$2 ] }]\n ]']
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
const SourceFileOperations = [
|
||||
[/Nan::SetMethod\(target,[\s]*"(.*)"[\s]*,[\s]*([^)]+)\)/g, 'exports.Set(Napi::String::New(env, "$1"), Napi::Function::New(env, $2))'],
|
||||
|
||||
[/v8::Local<v8::FunctionTemplate>\s+(\w+)\s*=\s*Nan::New<FunctionTemplate>\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'],
|
||||
[/Local<FunctionTemplate>\s+(\w+)\s*=\s*Nan::New<FunctionTemplate>\([\w\d:]+\);\s+(\w+)\.Reset\((\1)\);\s+\1->SetClassName\((Nan::String::New|Nan::New<(v8::)*String>)\("(.+?)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$6", {'],
|
||||
[/Local<FunctionTemplate>\s+(\w+)\s*=\s*Nan::New<FunctionTemplate>\([\w\d:]+\);(?:\w+->Reset\(\1\))?\s+\1->SetClassName\(Nan::String::New\("(\w+)"\)\);/g, 'Napi::Function $1 = DefineClass(env, "$2", {'],
|
||||
[/Nan::New<v8::FunctionTemplate>\(([\w\d:]+)\)->GetFunction\(\)/g, 'Napi::Function::New(env, $1)'],
|
||||
[/Nan::New<FunctionTemplate>\(([\w\d:]+)\)->GetFunction()/g, 'Napi::Function::New(env, $1);'],
|
||||
[/Nan::New<v8::FunctionTemplate>\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'],
|
||||
[/Nan::New<FunctionTemplate>\(([\w\d:]+)\)/g, 'Napi::Function::New(env, $1)'],
|
||||
|
||||
// FunctionTemplate to FunctionReference
|
||||
[/Nan::Persistent<(v8::)*FunctionTemplate>/g, 'Napi::FunctionReference'],
|
||||
[/Nan::Persistent<(v8::)*Function>/g, 'Napi::FunctionReference'],
|
||||
[/v8::Local<v8::FunctionTemplate>/g, 'Napi::FunctionReference'],
|
||||
[/Local<FunctionTemplate>/g, 'Napi::FunctionReference'],
|
||||
[/v8::FunctionTemplate/g, 'Napi::FunctionReference'],
|
||||
[/FunctionTemplate/g, 'Napi::FunctionReference'],
|
||||
|
||||
[/([ ]*)Nan::SetPrototypeMethod\(\w+, "(\w+)", (\w+)\);/g, '$1InstanceMethod("$2", &$3),'],
|
||||
[/([ ]*)(?:\w+\.Reset\(\w+\);\s+)?\(target\)\.Set\("(\w+)",\s*Nan::GetFunction\((\w+)\)\);/gm,
|
||||
'});\n\n' +
|
||||
'$1constructor = Napi::Persistent($3);\n' +
|
||||
'$1constructor.SuppressDestruct();\n' +
|
||||
'$1target.Set("$2", $3);'],
|
||||
|
||||
// TODO: Other attribute combinations
|
||||
[/static_cast<PropertyAttribute>\(ReadOnly\s*\|\s*DontDelete\)/gm,
|
||||
'static_cast<napi_property_attributes>(napi_enumerable | napi_configurable)'],
|
||||
|
||||
[/([\w\d:<>]+?)::Cast\((.+?)\)/g, '$2.As<$1>()'],
|
||||
|
||||
[/\*Nan::Utf8String\(([^)]+)\)/g, '$1->As<Napi::String>().Utf8Value().c_str()'],
|
||||
[/Nan::Utf8String +(\w+)\(([^)]+)\)/g, 'std::string $1 = $2.As<Napi::String>()'],
|
||||
[/Nan::Utf8String/g, 'std::string'],
|
||||
|
||||
[/v8::String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'],
|
||||
[/String::Utf8Value (.+?)\((.+?)\)/g, 'Napi::String $1(env, $2)'],
|
||||
[/\.length\(\)/g, '.Length()'],
|
||||
|
||||
[/Nan::MakeCallback\(([^,]+),[\s\\]+([^,]+),/gm, '$2.MakeCallback($1,'],
|
||||
|
||||
[/class\s+(\w+)\s*:\s*public\s+Nan::ObjectWrap/g, 'class $1 : public Napi::ObjectWrap<$1>'],
|
||||
[/(\w+)\(([^)]*)\)\s*:\s*Nan::ObjectWrap\(\)\s*(,)?/gm, '$1($2) : Napi::ObjectWrap<$1>()$3'],
|
||||
|
||||
// HandleOKCallback to OnOK
|
||||
[/HandleOKCallback/g, 'OnOK'],
|
||||
// HandleErrorCallback to OnError
|
||||
[/HandleErrorCallback/g, 'OnError'],
|
||||
|
||||
// ex. .As<Function>() to .As<Napi::Object>()
|
||||
[/\.As<v8::(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As<Napi::$1>()'],
|
||||
[/\.As<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>\(\)/g, '.As<Napi::$1>()'],
|
||||
|
||||
// ex. Nan::New<Number>(info[0]) to Napi::Number::New(info[0])
|
||||
[/Nan::New<(v8::)*Integer>\((.+?)\)/g, 'Napi::Number::New(env, $2)'],
|
||||
[/Nan::New\(([0-9.]+)\)/g, 'Napi::Number::New(env, $1)'],
|
||||
[/Nan::New<(v8::)*String>\("(.+?)"\)/g, 'Napi::String::New(env, "$2")'],
|
||||
[/Nan::New\("(.+?)"\)/g, 'Napi::String::New(env, "$1")'],
|
||||
[/Nan::New<(v8::)*(.+?)>\(\)/g, 'Napi::$2::New(env)'],
|
||||
[/Nan::New<(.+?)>\(\)/g, 'Napi::$1::New(env)'],
|
||||
[/Nan::New<(v8::)*(.+?)>\(/g, 'Napi::$2::New(env, '],
|
||||
[/Nan::New<(.+?)>\(/g, 'Napi::$1::New(env, '],
|
||||
[/Nan::NewBuffer\(/g, 'Napi::Buffer<char>::New(env, '],
|
||||
// TODO: Properly handle this
|
||||
[/Nan::New\(/g, 'Napi::New(env, '],
|
||||
|
||||
[/\.IsInt32\(\)/g, '.IsNumber()'],
|
||||
[/->IsInt32\(\)/g, '.IsNumber()'],
|
||||
|
||||
[/(.+?)->BooleanValue\(\)/g, '$1.As<Napi::Boolean>().Value()'],
|
||||
[/(.+?)->Int32Value\(\)/g, '$1.As<Napi::Number>().Int32Value()'],
|
||||
[/(.+?)->Uint32Value\(\)/g, '$1.As<Napi::Number>().Uint32Value()'],
|
||||
[/(.+?)->IntegerValue\(\)/g, '$1.As<Napi::Number>().Int64Value()'],
|
||||
[/(.+?)->NumberValue\(\)/g, '$1.As<Napi::Number>().DoubleValue()'],
|
||||
|
||||
// ex. Nan::To<bool>(info[0]) to info[0].Value()
|
||||
[/Nan::To<v8::(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To<Napi::$1>()'],
|
||||
[/Nan::To<(Boolean|String|Number|Object|Array|Symbol|Function)>\((.+?)\)/g, '$2.To<Napi::$1>()'],
|
||||
// ex. Nan::To<bool>(info[0]) to info[0].As<Napi::Boolean>().Value()
|
||||
[/Nan::To<bool>\((.+?)\)/g, '$1.As<Napi::Boolean>().Value()'],
|
||||
// ex. Nan::To<int>(info[0]) to info[0].As<Napi::Number>().Int32Value()
|
||||
[/Nan::To<int>\((.+?)\)/g, '$1.As<Napi::Number>().Int32Value()'],
|
||||
// ex. Nan::To<int32_t>(info[0]) to info[0].As<Napi::Number>().Int32Value()
|
||||
[/Nan::To<int32_t>\((.+?)\)/g, '$1.As<Napi::Number>().Int32Value()'],
|
||||
// ex. Nan::To<uint32_t>(info[0]) to info[0].As<Napi::Number>().Uint32Value()
|
||||
[/Nan::To<uint32_t>\((.+?)\)/g, '$1.As<Napi::Number>().Uint32Value()'],
|
||||
// ex. Nan::To<int64_t>(info[0]) to info[0].As<Napi::Number>().Int64Value()
|
||||
[/Nan::To<int64_t>\((.+?)\)/g, '$1.As<Napi::Number>().Int64Value()'],
|
||||
// ex. Nan::To<float>(info[0]) to info[0].As<Napi::Number>().FloatValue()
|
||||
[/Nan::To<float>\((.+?)\)/g, '$1.As<Napi::Number>().FloatValue()'],
|
||||
// ex. Nan::To<double>(info[0]) to info[0].As<Napi::Number>().DoubleValue()
|
||||
[/Nan::To<double>\((.+?)\)/g, '$1.As<Napi::Number>().DoubleValue()'],
|
||||
|
||||
[/Nan::New\((\w+)\)->HasInstance\((\w+)\)/g, '$2.InstanceOf($1.Value())'],
|
||||
|
||||
[/Nan::Has\(([^,]+),\s*/gm, '($1).Has('],
|
||||
[/\.Has\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Has($1)'],
|
||||
[/\.Has\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Has($1)'],
|
||||
|
||||
[/Nan::Get\(([^,]+),\s*/gm, '($1).Get('],
|
||||
[/\.Get\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\)/gm, '.Get($1)'],
|
||||
[/\.Get\([\s|\\]*Nan::New\(([^)]+)\)\)/gm, '.Get($1)'],
|
||||
|
||||
[/Nan::Set\(([^,]+),\s*/gm, '($1).Set('],
|
||||
[/\.Set\([\s|\\]*Nan::New<(v8::)*String>\(([^)]+)\)\s*,/gm, '.Set($1,'],
|
||||
[/\.Set\([\s|\\]*Nan::New\(([^)]+)\)\s*,/gm, '.Set($1,'],
|
||||
|
||||
// ex. node::Buffer::HasInstance(info[0]) to info[0].IsBuffer()
|
||||
[/node::Buffer::HasInstance\((.+?)\)/g, '$1.IsBuffer()'],
|
||||
// ex. node::Buffer::Length(info[0]) to info[0].Length()
|
||||
[/node::Buffer::Length\((.+?)\)/g, '$1.As<Napi::Buffer<char>>().Length()'],
|
||||
// ex. node::Buffer::Data(info[0]) to info[0].Data()
|
||||
[/node::Buffer::Data\((.+?)\)/g, '$1.As<Napi::Buffer<char>>().Data()'],
|
||||
[/Nan::CopyBuffer\(/g, 'Napi::Buffer::Copy(env, '],
|
||||
|
||||
// Nan::AsyncQueueWorker(worker)
|
||||
[/Nan::AsyncQueueWorker\((.+)\);/g, '$1.Queue();'],
|
||||
[/Nan::(Undefined|Null|True|False)\(\)/g, 'env.$1()'],
|
||||
|
||||
// Nan::ThrowError(error) to Napi::Error::New(env, error).ThrowAsJavaScriptException()
|
||||
[/([ ]*)return Nan::Throw(\w*?)Error\((.+?)\);/g, '$1Napi::$2Error::New(env, $3).ThrowAsJavaScriptException();\n$1return env.Null();'],
|
||||
[/Nan::Throw(\w*?)Error\((.+?)\);\n(\s*)return;/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n$3return env.Null();'],
|
||||
[/Nan::Throw(\w*?)Error\((.+?)\);/g, 'Napi::$1Error::New(env, $2).ThrowAsJavaScriptException();\n'],
|
||||
// Nan::RangeError(error) to Napi::RangeError::New(env, error)
|
||||
[/Nan::(\w*?)Error\((.+)\)/g, 'Napi::$1Error::New(env, $2)'],
|
||||
|
||||
[/Nan::Set\((.+?),\n* *(.+?),\n* *(.+?),\n* *(.+?)\)/g, '$1.Set($2, $3, $4)'],
|
||||
|
||||
[/Nan::(Escapable)?HandleScope\s+(\w+)\s*;/g, 'Napi::$1HandleScope $2(env);'],
|
||||
[/Nan::(Escapable)?HandleScope/g, 'Napi::$1HandleScope'],
|
||||
[/Nan::ForceSet\(([^,]+), ?/g, '$1->DefineProperty('],
|
||||
[/\.ForceSet\(Napi::String::New\(env, "(\w+)"\),\s*?/g, '.DefineProperty("$1", '],
|
||||
// [ /Nan::GetPropertyNames\(([^,]+)\)/, '$1->GetPropertyNames()' ],
|
||||
[/Nan::Equals\(([^,]+),/g, '$1.StrictEquals('],
|
||||
|
||||
[/(.+)->Set\(/g, '$1.Set('],
|
||||
|
||||
[/Nan::Callback/g, 'Napi::FunctionReference'],
|
||||
|
||||
[/Nan::Persistent<Object>/g, 'Napi::ObjectReference'],
|
||||
[/Nan::ADDON_REGISTER_FUNCTION_ARGS_TYPE target/g, 'Napi::Env& env, Napi::Object& target'],
|
||||
|
||||
[/(\w+)\*\s+(\w+)\s*=\s*Nan::ObjectWrap::Unwrap<\w+>\(info\.This\(\)\);/g, '$1* $2 = this;'],
|
||||
[/Nan::ObjectWrap::Unwrap<(\w+)>\((.*)\);/g, '$2.Unwrap<$1>();'],
|
||||
|
||||
[/Nan::NAN_METHOD_RETURN_TYPE/g, 'void'],
|
||||
[/NAN_INLINE/g, 'inline'],
|
||||
|
||||
[/Nan::NAN_METHOD_ARGS_TYPE/g, 'const Napi::CallbackInfo&'],
|
||||
[/NAN_METHOD\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'],
|
||||
[/static\s*NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'],
|
||||
[/NAN_GETTER\(([\w\d:]+?)\)/g, 'Napi::Value $1(const Napi::CallbackInfo& info)'],
|
||||
[/static\s*NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'],
|
||||
[/NAN_SETTER\(([\w\d:]+?)\)/g, 'void $1(const Napi::CallbackInfo& info, const Napi::Value& value)'],
|
||||
[/void Init\((v8::)*Local<(v8::)*Object> exports\)/g, 'Napi::Object Init(Napi::Env env, Napi::Object exports)'],
|
||||
[/NAN_MODULE_INIT\(([\w\d:]+?)\);/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports);'],
|
||||
[/NAN_MODULE_INIT\(([\w\d:]+?)\)/g, 'Napi::Object $1(Napi::Env env, Napi::Object exports)'],
|
||||
|
||||
[/::(Init(?:ialize)?)\(target\)/g, '::$1(env, target, module)'],
|
||||
[/constructor_template/g, 'constructor'],
|
||||
|
||||
[/Nan::FunctionCallbackInfo<(v8::)?Value>[ ]*& [ ]*info\)[ ]*{\n*([ ]*)/gm, 'Napi::CallbackInfo& info) {\n$2Napi::Env env = info.Env();\n$2'],
|
||||
[/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&\s*info\);/g, 'Napi::CallbackInfo& info);'],
|
||||
[/Nan::FunctionCallbackInfo<(v8::)*Value>\s*&/g, 'Napi::CallbackInfo&'],
|
||||
|
||||
[/Buffer::HasInstance\(([^)]+)\)/g, '$1.IsBuffer()'],
|
||||
|
||||
[/info\[(\d+)\]->/g, 'info[$1].'],
|
||||
[/info\[([\w\d]+)\]->/g, 'info[$1].'],
|
||||
[/info\.This\(\)->/g, 'info.This().'],
|
||||
[/->Is(Object|String|Int32|Number)\(\)/g, '.Is$1()'],
|
||||
[/info.GetReturnValue\(\).SetUndefined\(\)/g, 'return env.Undefined()'],
|
||||
[/info\.GetReturnValue\(\)\.Set\(((\n|.)+?)\);/g, 'return $1;'],
|
||||
|
||||
// ex. Local<Value> to Napi::Value
|
||||
[/v8::Local<v8::(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1'],
|
||||
[/Local<(Value|Boolean|String|Number|Object|Array|Symbol|External|Function)>/g, 'Napi::$1'],
|
||||
|
||||
// Declare an env in helper functions that take a Napi::Value
|
||||
[/(\w+)\(Napi::Value (\w+)(,\s*[^()]+)?\)\s*{\n*([ ]*)/gm, '$1(Napi::Value $2$3) {\n$4Napi::Env env = $2.Env();\n$4'],
|
||||
|
||||
// delete #include <node.h> and/or <v8.h>
|
||||
[/#include +(<|")(?:node|nan).h("|>)/g, '#include $1napi.h$2\n#include $1uv.h$2'],
|
||||
// NODE_MODULE to NODE_API_MODULE
|
||||
[/NODE_MODULE/g, 'NODE_API_MODULE'],
|
||||
[/Nan::/g, 'Napi::'],
|
||||
[/nan.h/g, 'napi.h'],
|
||||
|
||||
// delete .FromJust()
|
||||
[/\.FromJust\(\)/g, ''],
|
||||
// delete .ToLocalCheck()
|
||||
[/\.ToLocalChecked\(\)/g, ''],
|
||||
[/^.*->SetInternalFieldCount\(.*$/gm, ''],
|
||||
|
||||
// replace using node; and/or using v8; to using Napi;
|
||||
[/using (node|v8);/g, 'using Napi;'],
|
||||
[/using namespace (node|Nan|v8);/g, 'using namespace Napi;'],
|
||||
// delete using v8::Local;
|
||||
[/using v8::Local;\n/g, ''],
|
||||
// replace using v8::XXX; with using Napi::XXX
|
||||
[/using v8::([A-Za-z]+);/g, 'using Napi::$1;']
|
||||
|
||||
];
|
||||
|
||||
const paths = listFiles(dir);
|
||||
paths.forEach(function (dirEntry) {
|
||||
const filename = dirEntry.split('\\').pop().split('/').pop();
|
||||
|
||||
// Check whether the file is a source file or a config file
|
||||
// then execute function accordingly
|
||||
const sourcePattern = /.+\.h|.+\.cc|.+\.cpp/;
|
||||
if (sourcePattern.test(filename)) {
|
||||
convertFile(dirEntry, SourceFileOperations);
|
||||
} else if (ConfigFileOperations[filename] != null) {
|
||||
convertFile(dirEntry, ConfigFileOperations[filename]);
|
||||
}
|
||||
});
|
||||
|
||||
function listFiles (dir, filelist) {
|
||||
const files = fs.readdirSync(dir);
|
||||
filelist = filelist || [];
|
||||
files.forEach(function (file) {
|
||||
if (file === 'node_modules') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (fs.statSync(path.join(dir, file)).isDirectory()) {
|
||||
filelist = listFiles(path.join(dir, file), filelist);
|
||||
} else {
|
||||
filelist.push(path.join(dir, file));
|
||||
}
|
||||
});
|
||||
return filelist;
|
||||
}
|
||||
|
||||
function convert (content, operations) {
|
||||
for (let i = 0; i < operations.length; i++) {
|
||||
const operation = operations[i];
|
||||
content = content.replace(operation[0], operation[1]);
|
||||
}
|
||||
return content;
|
||||
}
|
||||
|
||||
function convertFile (fileName, operations) {
|
||||
fs.readFile(fileName, 'utf-8', function (err, file) {
|
||||
if (err) throw err;
|
||||
|
||||
file = convert(file, operations);
|
||||
|
||||
fs.writeFile(fileName, file, function (err) {
|
||||
if (err) throw err;
|
||||
});
|
||||
});
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const spawn = require('child_process').spawnSync;
|
||||
|
||||
const filesToCheck = '*.js';
|
||||
const FORMAT_START = process.env.FORMAT_START || 'main';
|
||||
const IS_WIN = process.platform === 'win32';
|
||||
const ESLINT_PATH = IS_WIN ? 'node_modules\\.bin\\eslint.cmd' : 'node_modules/.bin/eslint';
|
||||
|
||||
function main (args) {
|
||||
let fix = false;
|
||||
while (args.length > 0) {
|
||||
switch (args[0]) {
|
||||
case '-f':
|
||||
case '--fix':
|
||||
fix = true;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
args.shift();
|
||||
}
|
||||
|
||||
// Check js files that change on unstaged file
|
||||
const fileUnStaged = spawn(
|
||||
'git',
|
||||
['diff', '--name-only', '--diff-filter=d', FORMAT_START, filesToCheck],
|
||||
{
|
||||
encoding: 'utf-8'
|
||||
}
|
||||
);
|
||||
|
||||
// Check js files that change on staged file
|
||||
const fileStaged = spawn(
|
||||
'git',
|
||||
['diff', '--name-only', '--cached', '--diff-filter=d', FORMAT_START, filesToCheck],
|
||||
{
|
||||
encoding: 'utf-8'
|
||||
}
|
||||
);
|
||||
|
||||
const options = [
|
||||
...fileStaged.stdout.split('\n').filter((f) => f !== ''),
|
||||
...fileUnStaged.stdout.split('\n').filter((f) => f !== '')
|
||||
];
|
||||
|
||||
if (fix) {
|
||||
options.push('--fix');
|
||||
}
|
||||
|
||||
const result = spawn(ESLINT_PATH, [...options], {
|
||||
encoding: 'utf-8'
|
||||
});
|
||||
|
||||
if (result.error && result.error.errno === 'ENOENT') {
|
||||
console.error('Eslint not found! Eslint is supposed to be found at ', ESLINT_PATH);
|
||||
return 2;
|
||||
}
|
||||
|
||||
if (result.status === 1) {
|
||||
console.error('Eslint error:', result.stdout);
|
||||
const fixCmd = 'npm run lint:fix';
|
||||
console.error(`ERROR: please run "${fixCmd}" to format changes in your commit
|
||||
Note that when running the command locally, please keep your local
|
||||
main branch and working branch up to date with nodejs/node-addon-api
|
||||
to exclude un-related complains.
|
||||
Or you can run "env FORMAT_START=upstream/main ${fixCmd}".
|
||||
Also fix JS files by yourself if necessary.`);
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (result.stderr) {
|
||||
console.error('Error running eslint:', result.stderr);
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
process.exitCode = main(process.argv.slice(2));
|
||||
}
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
Copyright (c) 2012-2015, Christopher Jeffrey (https://github.com/chjj/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016, Daniel Imms (http://www.growingwiththeweb.com)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2018 - present Microsoft Corporation
|
||||
|
||||
All rights reserved.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+165
@@ -0,0 +1,165 @@
|
||||
# node-pty
|
||||
|
||||
[](https://dev.azure.com/vscode/node-pty/_build/latest?definitionId=11&branchName=main)
|
||||
|
||||
`forkpty(3)` bindings for node.js. This allows you to fork processes with pseudoterminal file descriptors. It returns a terminal object which allows reads and writes.
|
||||
|
||||
This is useful for:
|
||||
|
||||
- Writing a terminal emulator (eg. via [xterm.js](https://github.com/sourcelair/xterm.js)).
|
||||
- Getting certain programs to *think* you're a terminal, such as when you need a program to send you control sequences.
|
||||
|
||||
`node-pty` supports Linux, macOS and Windows. Windows support is possible by utilizing the [Windows conpty API](https://blogs.msdn.microsoft.com/commandline/2018/08/02/windows-command-line-introducing-the-windows-pseudo-console-conpty/) on Windows 1809+ and the [winpty](https://github.com/rprichard/winpty) library in older version.
|
||||
|
||||
## API
|
||||
|
||||
The full API for node-pty is contained within the [TypeScript declaration file](https://github.com/microsoft/node-pty/blob/main/typings/node-pty.d.ts), use the branch/tag picker in GitHub (`w`) to navigate to the correct version of the API.
|
||||
|
||||
## Example Usage
|
||||
|
||||
```js
|
||||
import * as os from 'node:os';
|
||||
import * as pty from 'node-pty';
|
||||
|
||||
const shell = os.platform() === 'win32' ? 'powershell.exe' : 'bash';
|
||||
|
||||
const ptyProcess = pty.spawn(shell, [], {
|
||||
name: 'xterm-color',
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
cwd: process.env.HOME,
|
||||
env: process.env
|
||||
});
|
||||
|
||||
ptyProcess.onData((data) => {
|
||||
process.stdout.write(data);
|
||||
});
|
||||
|
||||
ptyProcess.write('ls\r');
|
||||
ptyProcess.resize(100, 40);
|
||||
ptyProcess.write('ls\r');
|
||||
```
|
||||
|
||||
## Real-world Uses
|
||||
|
||||
`node-pty` powers many different terminal emulators, including:
|
||||
|
||||
- [Microsoft Visual Studio Code](https://code.visualstudio.com)
|
||||
- [Hyper](https://hyper.is/)
|
||||
- [Upterm](https://github.com/railsware/upterm)
|
||||
- [Script Runner](https://github.com/ioquatix/script-runner) for Atom.
|
||||
- [Theia](https://github.com/theia-ide/theia)
|
||||
- [FreeMAN](https://github.com/matthew-matvei/freeman) file manager
|
||||
- [terminus](https://atom.io/packages/terminus) - An Atom plugin for providing terminals inside your Atom workspace.
|
||||
- [x-terminal](https://atom.io/packages/x-terminal) - Also an Atom plugin that provides terminals inside your Atom workspace.
|
||||
- [Termination](https://atom.io/packages/termination) - Also an Atom plugin that provides terminals inside your Atom workspace.
|
||||
- [atom-xterm](https://atom.io/packages/atom-xterm) - Also an Atom plugin that provides terminals inside your Atom workspace.
|
||||
- [electerm](https://github.com/electerm/electerm) Terminal/SSH/SFTP client(Linux, macOS, Windows).
|
||||
- [Extraterm](http://extraterm.org/)
|
||||
- [Wetty](https://github.com/krishnasrinivas/wetty) Browser based Terminal over HTTP and HTTPS
|
||||
- [nomad](https://github.com/lukebarnard1/nomad-term)
|
||||
- [DockerStacks](https://github.com/sfx101/docker-stacks) Local LAMP/LEMP stack using Docker
|
||||
- [TeleType](https://github.com/akshaykmr/TeleType): cli tool that allows you to share your terminal online conveniently. Show off mad cli-fu, help a colleague, teach, or troubleshoot.
|
||||
- [mesos-term](https://github.com/criteo/mesos-term): A web terminal for Apache Mesos. It allows to execute commands within containers.
|
||||
- [Commas](https://github.com/CyanSalt/commas): A hackable terminal and command runner.
|
||||
- [ENiGMA½ BBS Software](https://github.com/NuSkooler/enigma-bbs): A modern BBS software with a nostalgic flair!
|
||||
- [Tinkerun](https://github.com/tinkerun/tinkerun): A new way of running Tinker.
|
||||
- [Tess](https://tessapp.dev): Hackable, simple and rapid terminal for the new era of technology 👍
|
||||
- [NxShell](https://nxshell.github.io/): An easy to use new terminal for Windows/Linux/MacOS platform.
|
||||
- [OpenSumi](https://github.com/opensumi/core): A framework helps you quickly build Cloud or Desktop IDE products.
|
||||
- [Enjoy Git](https://github.com/huangcs427/enjoy-git-release): A modern Git client featuring an intuitive user interface, built with Electron, Vue 3, and TypeScript.
|
||||
|
||||
Do you use node-pty in your application as well? Please open a [Pull Request](https://github.com/Tyriar/node-pty/pulls) to include it here. We would love to have it in our list.
|
||||
|
||||
## Building
|
||||
|
||||
```bash
|
||||
# Install dependencies and build C++
|
||||
npm install
|
||||
# Compile TypeScript -> JavaScript
|
||||
npm run build
|
||||
```
|
||||
|
||||
## Dependencies
|
||||
|
||||
Node.JS 16 or Electron 19 is required to use `node-pty`. What version of node is supported is currently mostly bound to [whatever version Visual Studio Code is using](https://github.com/microsoft/node-pty/issues/557#issuecomment-1332193541).
|
||||
|
||||
### Linux (apt)
|
||||
|
||||
```sh
|
||||
sudo apt install -y make python build-essential
|
||||
```
|
||||
|
||||
### macOS
|
||||
|
||||
Xcode is needed to compile the sources, this can be installed from the App Store.
|
||||
|
||||
### Windows
|
||||
|
||||
`npm install` requires some tools to be present in the system like Python and C++ compiler. Windows users can easily install them by running the following command in PowerShell as administrator. For more information see https://github.com/felixrieseberg/windows-build-tools:
|
||||
|
||||
```sh
|
||||
npm install --global --production windows-build-tools
|
||||
```
|
||||
|
||||
The following are also needed:
|
||||
|
||||
- [Windows SDK](https://developer.microsoft.com/en-us/windows/downloads/windows-10-sdk) - only the "Desktop C++ Apps" components are needed to be installed
|
||||
- Spectre-mitigated libraries - In order to avoid the build error "MSB8040: Spectre-mitigated libraries are required for this project", open the Visual Studio Installer, press the Modify button, navigate to the "Individual components" tab, search "Spectre", and install an option like "MSVC v143 - VS 2022 C++ x64/x86 Spectre-mitigated libs (Latest)" (the exact option to install will depend on your version of Visual Studio as well as your operating system architecture)
|
||||
|
||||
## Debugging
|
||||
|
||||
[The wiki](https://github.com/Microsoft/node-pty/wiki/Debugging) contains instructions for debugging node-pty.
|
||||
|
||||
## Security
|
||||
|
||||
All processes launched from node-pty will launch at the same permission level of the parent process. Take care particularly when using node-pty inside a server that's accessible on the internet. We recommend launching the pty inside a container to protect your host machine.
|
||||
|
||||
## Thread Safety
|
||||
|
||||
Note that node-pty is not thread safe so running it across multiple worker threads in node.js could cause issues.
|
||||
|
||||
## Flow Control
|
||||
|
||||
Automatic flow control can be enabled by either providing `handleFlowControl = true` in the constructor options or setting it later on:
|
||||
|
||||
```js
|
||||
const PAUSE = '\x13'; // XOFF
|
||||
const RESUME = '\x11'; // XON
|
||||
|
||||
const ptyProcess = pty.spawn(shell, [], {handleFlowControl: true});
|
||||
|
||||
// flow control in action
|
||||
ptyProcess.write(PAUSE); // pty will block and pause the child program
|
||||
...
|
||||
ptyProcess.write(RESUME); // pty will enter flow mode and resume the child program
|
||||
|
||||
// temporarily disable/re-enable flow control
|
||||
ptyProcess.handleFlowControl = false;
|
||||
...
|
||||
ptyProcess.handleFlowControl = true;
|
||||
```
|
||||
|
||||
By default `PAUSE` and `RESUME` are XON/XOFF control codes (as shown above). To avoid conflicts in environments that use these control codes for different purposes the messages can be customized as `flowControlPause: string` and `flowControlResume: string` in the constructor options. `PAUSE` and `RESUME` are not passed to the underlying pseudoterminal if flow control is enabled.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Powershell gives error 8009001d
|
||||
|
||||
> Internal Windows PowerShell error. Loading managed Windows PowerShell failed with error 8009001d.
|
||||
|
||||
This happens when PowerShell is launched with no `SystemRoot` environment variable present.
|
||||
|
||||
### ConnectNamedPipe failed: Windows error 232
|
||||
|
||||
This error can occur due to anti-virus software intercepting winpty from creating a pty. To workaround this you can exclude this file from your anti-virus scanning `node-pty\build\Release\winpty-agent.exe`
|
||||
|
||||
## pty.js
|
||||
|
||||
This project is forked from [chjj/pty.js](https://github.com/chjj/pty.js) with the primary goals being to provide better support for later Node.js versions and Windows.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) 2012-2015, Christopher Jeffrey (MIT License).<br>
|
||||
Copyright (c) 2016, Daniel Imms (MIT License).<br>
|
||||
Copyright (c) 2018, Microsoft Corporation (MIT License).
|
||||
+111
@@ -0,0 +1,111 @@
|
||||
{
|
||||
'target_defaults': {
|
||||
'dependencies': [
|
||||
"<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
|
||||
],
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'msvs_configuration_attributes': {
|
||||
'SpectreMitigation': 'Spectre'
|
||||
},
|
||||
'msvs_settings': {
|
||||
'VCCLCompilerTool': {
|
||||
'AdditionalOptions': [
|
||||
'/guard:cf',
|
||||
'/sdl',
|
||||
'/W3',
|
||||
'/w34244',
|
||||
'/w34267',
|
||||
'/ZH:SHA_256'
|
||||
]
|
||||
},
|
||||
'VCLinkerTool': {
|
||||
'AdditionalOptions': [
|
||||
'/DYNAMICBASE',
|
||||
'/guard:cf'
|
||||
]
|
||||
}
|
||||
},
|
||||
}],
|
||||
],
|
||||
},
|
||||
'conditions': [
|
||||
['OS=="win"', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'conpty',
|
||||
'sources' : [
|
||||
'src/win/conpty.cc',
|
||||
'src/win/path_util.cc'
|
||||
],
|
||||
'libraries': [
|
||||
'-lshlwapi'
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'conpty_console_list',
|
||||
'sources' : [
|
||||
'src/win/conpty_console_list.cc'
|
||||
],
|
||||
},
|
||||
{
|
||||
'target_name': 'pty',
|
||||
'include_dirs' : [
|
||||
'<!(node -p "require(\'node-addon-api\').include_dir")',
|
||||
'deps/winpty/src/include',
|
||||
],
|
||||
# Disabled due to winpty
|
||||
'msvs_disabled_warnings': [ 4506, 4530 ],
|
||||
'dependencies' : [
|
||||
'deps/winpty/src/winpty.gyp:winpty-agent',
|
||||
'deps/winpty/src/winpty.gyp:winpty',
|
||||
],
|
||||
'sources' : [
|
||||
'src/win/winpty.cc',
|
||||
'src/win/path_util.cc'
|
||||
],
|
||||
'libraries': [
|
||||
'-lshlwapi'
|
||||
],
|
||||
}
|
||||
]
|
||||
}, { # OS!="win"
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'pty',
|
||||
'sources': [
|
||||
'src/unix/pty.cc',
|
||||
],
|
||||
'libraries': [
|
||||
'-lutil'
|
||||
],
|
||||
'cflags': ['-Wall', '-O2', '-D_FORTIFY_SOURCE=2'],
|
||||
'conditions': [
|
||||
# http://www.gnu.org/software/gnulib/manual/html_node/forkpty.html
|
||||
# One some systems (at least including Cygwin, Interix,
|
||||
# OSF/1 4 and 5, and Mac OS X) linking with -lutil is not required.
|
||||
['OS=="mac" or OS=="solaris"', {
|
||||
'libraries!': [
|
||||
'-lutil'
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
]
|
||||
}],
|
||||
['OS=="mac"', {
|
||||
'targets': [
|
||||
{
|
||||
'target_name': 'spawn-helper',
|
||||
'type': 'executable',
|
||||
'sources': [
|
||||
'src/unix/spawn-helper.cc',
|
||||
],
|
||||
"xcode_settings": {
|
||||
"MACOSX_DEPLOYMENT_TARGET":"10.7"
|
||||
}
|
||||
},
|
||||
]
|
||||
}]
|
||||
]
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
# Have editor config ignore this directory
|
||||
root = true
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# Build configure for https://www.tea-ci.org (fork of Drone CI with Msys2 support)
|
||||
build:
|
||||
image: teaci/msys$$arch
|
||||
pull: true
|
||||
shell: msys$$arch
|
||||
commands:
|
||||
- pacman -S --needed --noconfirm --noprogressbar mingw-w64-cross-gcc mingw-w64-cross-crt-git
|
||||
- ./configure
|
||||
- make
|
||||
- make tests
|
||||
- build/trivial_test.exe
|
||||
- mintty --log - --exec build/winpty.exe cmd /c ver | grep Windows
|
||||
|
||||
matrix:
|
||||
arch:
|
||||
- 64
|
||||
- 32
|
||||
@@ -0,0 +1,19 @@
|
||||
* text=auto
|
||||
*.bat text eol=crlf
|
||||
*.c text
|
||||
*.cc text
|
||||
*.gyp text
|
||||
*.gypi text
|
||||
*.h text
|
||||
*.ps1 text eol=crlf
|
||||
*.rst text
|
||||
*.sh text
|
||||
*.txt text
|
||||
.gitignore text
|
||||
.gitattributes text
|
||||
Makefile text
|
||||
configure text
|
||||
|
||||
*.sh eol=lf
|
||||
configure eol=lf
|
||||
VERSION.txt eol=lf
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2011-2016 Ryan Prichard
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to
|
||||
deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
IN THE SOFTWARE.
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
# Copyright (c) 2011-2015 Ryan Prichard
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
# Use make -n to see the actual command-lines make would run.
|
||||
|
||||
# The default "make install" prefix is /usr/local. Pass PREFIX=<path> on the
|
||||
# command-line to override the default.
|
||||
|
||||
.SECONDEXPANSION :
|
||||
|
||||
.PHONY : default
|
||||
default : all
|
||||
|
||||
PREFIX := /usr/local
|
||||
UNIX_ADAPTER_EXE := winpty.exe
|
||||
MINGW_ENABLE_CXX11_FLAG := -std=c++11
|
||||
USE_PCH := 1
|
||||
|
||||
COMMON_CXXFLAGS :=
|
||||
UNIX_CXXFLAGS :=
|
||||
MINGW_CXXFLAGS :=
|
||||
MINGW_LDFLAGS :=
|
||||
UNIX_LDFLAGS :=
|
||||
|
||||
# Include config.mk but complain if it hasn't been created yet.
|
||||
ifeq "$(wildcard config.mk)" ""
|
||||
$(error config.mk does not exist. Please run ./configure)
|
||||
endif
|
||||
include config.mk
|
||||
|
||||
COMMON_CXXFLAGS += \
|
||||
-MMD -Wall \
|
||||
-DUNICODE \
|
||||
-D_UNICODE \
|
||||
-D_WIN32_WINNT=0x0501 \
|
||||
-Ibuild/gen
|
||||
|
||||
UNIX_CXXFLAGS += \
|
||||
$(COMMON_CXXFLAGS)
|
||||
|
||||
MINGW_CXXFLAGS += \
|
||||
$(COMMON_CXXFLAGS) \
|
||||
-O2 \
|
||||
$(MINGW_ENABLE_CXX11_FLAG)
|
||||
|
||||
MINGW_LDFLAGS += -static -static-libgcc -static-libstdc++
|
||||
UNIX_LDFLAGS += $(UNIX_LDFLAGS_STATIC)
|
||||
|
||||
ifeq "$(USE_PCH)" "1"
|
||||
MINGW_CXXFLAGS += -include build/mingw/PrecompiledHeader.h
|
||||
PCH_DEP := build/mingw/PrecompiledHeader.h.gch
|
||||
else
|
||||
PCH_DEP :=
|
||||
endif
|
||||
|
||||
build/gen/GenVersion.h : VERSION.txt $(COMMIT_HASH_DEP) | $$(@D)/.mkdir
|
||||
$(info Updating build/gen/GenVersion.h)
|
||||
@echo "const char GenVersion_Version[] = \"$(shell cat VERSION.txt | tr -d '\r\n')\";" > build/gen/GenVersion.h
|
||||
@echo "const char GenVersion_Commit[] = \"$(COMMIT_HASH)\";" >> build/gen/GenVersion.h
|
||||
|
||||
build/mingw/PrecompiledHeader.h : src/shared/PrecompiledHeader.h | $$(@D)/.mkdir
|
||||
$(info Copying $< to $@)
|
||||
@cp $< $@
|
||||
|
||||
build/mingw/PrecompiledHeader.h.gch : build/mingw/PrecompiledHeader.h | $$(@D)/.mkdir
|
||||
$(info Compiling $<)
|
||||
@$(MINGW_CXX) $(MINGW_CXXFLAGS) -c -o $@ $<
|
||||
|
||||
-include build/mingw/PrecompiledHeader.h.d
|
||||
|
||||
define def_unix_target
|
||||
build/$1/%.o : src/%.cc | $$$$(@D)/.mkdir
|
||||
$$(info Compiling $$<)
|
||||
@$$(UNIX_CXX) $$(UNIX_CXXFLAGS) $2 -I src/include -c -o $$@ $$<
|
||||
endef
|
||||
|
||||
define def_mingw_target
|
||||
build/$1/%.o : src/%.cc $$(PCH_DEP) | $$$$(@D)/.mkdir
|
||||
$$(info Compiling $$<)
|
||||
@$$(MINGW_CXX) $$(MINGW_CXXFLAGS) $2 -I src/include -c -o $$@ $$<
|
||||
endef
|
||||
|
||||
include src/subdir.mk
|
||||
|
||||
.PHONY : all
|
||||
all : $(ALL_TARGETS)
|
||||
|
||||
.PHONY : tests
|
||||
tests : $(TEST_PROGRAMS)
|
||||
|
||||
.PHONY : install-bin
|
||||
install-bin : all
|
||||
mkdir -p $(PREFIX)/bin
|
||||
install -m 755 -p -s build/$(UNIX_ADAPTER_EXE) $(PREFIX)/bin
|
||||
install -m 755 -p -s build/winpty.dll $(PREFIX)/bin
|
||||
install -m 755 -p -s build/winpty-agent.exe $(PREFIX)/bin
|
||||
|
||||
.PHONY : install-debugserver
|
||||
install-debugserver : all
|
||||
mkdir -p $(PREFIX)/bin
|
||||
install -m 755 -p -s build/winpty-debugserver.exe $(PREFIX)/bin
|
||||
|
||||
.PHONY : install-lib
|
||||
install-lib : all
|
||||
mkdir -p $(PREFIX)/lib
|
||||
install -m 644 -p build/winpty.lib $(PREFIX)/lib
|
||||
|
||||
.PHONY : install-doc
|
||||
install-doc :
|
||||
mkdir -p $(PREFIX)/share/doc/winpty
|
||||
install -m 644 -p LICENSE $(PREFIX)/share/doc/winpty
|
||||
install -m 644 -p README.md $(PREFIX)/share/doc/winpty
|
||||
install -m 644 -p RELEASES.md $(PREFIX)/share/doc/winpty
|
||||
|
||||
.PHONY : install-include
|
||||
install-include :
|
||||
mkdir -p $(PREFIX)/include/winpty
|
||||
install -m 644 -p src/include/winpty.h $(PREFIX)/include/winpty
|
||||
install -m 644 -p src/include/winpty_constants.h $(PREFIX)/include/winpty
|
||||
|
||||
.PHONY : install
|
||||
install : \
|
||||
install-bin \
|
||||
install-debugserver \
|
||||
install-lib \
|
||||
install-doc \
|
||||
install-include
|
||||
|
||||
.PHONY : clean
|
||||
clean :
|
||||
rm -fr build
|
||||
|
||||
.PHONY : clean-msvc
|
||||
clean-msvc :
|
||||
rm -fr src/Default src/Release src/.vs src/gen
|
||||
rm -f src/*.vcxproj src/*.vcxproj.filters src/*.sln src/*.sdf
|
||||
|
||||
.PHONY : distclean
|
||||
distclean : clean
|
||||
rm -f config.mk
|
||||
|
||||
.PRECIOUS : %.mkdir
|
||||
%.mkdir :
|
||||
$(info Creating directory $(dir $@))
|
||||
@mkdir -p $(dir $@)
|
||||
@touch $@
|
||||
|
||||
src/%.h :
|
||||
@echo "Missing header file $@ (stale dependency file?)"
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
# winpty
|
||||
|
||||
[](https://tea-ci.org/rprichard/winpty)
|
||||
|
||||
winpty is a Windows software package providing an interface similar to a Unix
|
||||
pty-master for communicating with Windows console programs. The package
|
||||
consists of a library (libwinpty) and a tool for Cygwin and MSYS for running
|
||||
Windows console programs in a Cygwin/MSYS pty.
|
||||
|
||||
The software works by starting the `winpty-agent.exe` process with a new,
|
||||
hidden console window, which bridges between the console API and terminal
|
||||
input/output escape codes. It polls the hidden console's screen buffer for
|
||||
changes and generates a corresponding stream of output.
|
||||
|
||||
The Unix adapter allows running Windows console programs (e.g. CMD, PowerShell,
|
||||
IronPython, etc.) under `mintty` or Cygwin's `sshd` with
|
||||
properly-functioning input (e.g. arrow and function keys) and output (e.g. line
|
||||
buffering). The library could be also useful for writing a non-Cygwin SSH
|
||||
server.
|
||||
|
||||
## Supported Windows versions
|
||||
|
||||
winpty runs on Windows XP through Windows 10, including server versions. It
|
||||
can be compiled into either 32-bit or 64-bit binaries.
|
||||
|
||||
## Cygwin/MSYS adapter (`winpty.exe`)
|
||||
|
||||
### Prerequisites
|
||||
|
||||
You need the following to build winpty:
|
||||
|
||||
* A Cygwin or MSYS installation
|
||||
* GNU make
|
||||
* A MinGW g++ toolchain capable of compiling C++11 code to build `winpty.dll`
|
||||
and `winpty-agent.exe`
|
||||
* A g++ toolchain targeting Cygwin or MSYS to build `winpty.exe`
|
||||
|
||||
Winpty requires two g++ toolchains as it is split into two parts. The
|
||||
`winpty.dll` and `winpty-agent.exe` binaries interface with the native
|
||||
Windows command prompt window so they are compiled with the native MinGW
|
||||
toolchain. The `winpty.exe` binary interfaces with the MSYS/Cygwin terminal so
|
||||
it is compiled with the MSYS/Cygwin toolchain.
|
||||
|
||||
MinGW appears to be split into two distributions -- MinGW (creates 32-bit
|
||||
binaries) and MinGW-w64 (creates both 32-bit and 64-bit binaries). Either
|
||||
one is generally acceptable.
|
||||
|
||||
#### Cygwin packages
|
||||
|
||||
The default g++ compiler for Cygwin targets Cygwin itself, but Cygwin also
|
||||
packages MinGW-w64 compilers. As of this writing, the necessary packages are:
|
||||
|
||||
* Either `mingw64-i686-gcc-g++` or `mingw64-x86_64-gcc-g++`. Select the
|
||||
appropriate compiler for your CPU architecture.
|
||||
* `gcc-g++`
|
||||
* `make`
|
||||
|
||||
As of this writing (2016-01-23), only the MinGW-w64 compiler is acceptable.
|
||||
The MinGW compiler (e.g. from the `mingw-gcc-g++` package) is no longer
|
||||
maintained and is too buggy.
|
||||
|
||||
#### MSYS packages
|
||||
|
||||
For the original MSYS, use the `mingw-get` tool (MinGW Installation Manager),
|
||||
and select at least these components:
|
||||
|
||||
* `mingw-developer-toolkit`
|
||||
* `mingw32-base`
|
||||
* `mingw32-gcc-g++`
|
||||
* `msys-base`
|
||||
* `msys-system-builder`
|
||||
|
||||
When running `./configure`, make sure that `mingw32-g++` is in your
|
||||
`PATH`. It will be in the `C:\MinGW\bin` directory.
|
||||
|
||||
#### MSYS2 packages
|
||||
|
||||
For MSYS2, use `pacman` and install at least these packages:
|
||||
|
||||
* `msys/gcc`
|
||||
* `mingw32/mingw-w64-i686-gcc` or `mingw64/mingw-w64-x86_64-gcc`. Select
|
||||
the appropriate compiler for your CPU architecture.
|
||||
* `make`
|
||||
|
||||
MSYS2 provides three start menu shortcuts for starting MSYS2:
|
||||
|
||||
* MinGW-w64 Win32 Shell
|
||||
* MinGW-w64 Win64 Shell
|
||||
* MSYS2 Shell
|
||||
|
||||
To build winpty, use the MinGW-w64 {Win32,Win64} shortcut of the architecture
|
||||
matching MSYS2. These shortcuts will put the g++ compiler from the
|
||||
`{mingw32,mingw64}/mingw-w64-{i686,x86_64}-gcc` packages into the `PATH`.
|
||||
|
||||
Alternatively, instead of installing `mingw32/mingw-w64-i686-gcc` or
|
||||
`mingw64/mingw-w64-x86_64-gcc`, install the `mingw-w64-cross-gcc` and
|
||||
`mingw-w64-cross-crt-git` packages. These packages install cross-compilers
|
||||
into `/opt/bin`, and then any of the three shortcuts will work.
|
||||
|
||||
### Building the Unix adapter
|
||||
|
||||
In the project directory, run `./configure`, then `make`, then `make install`.
|
||||
By default, winpty is installed into `/usr/local`. Pass `PREFIX=<path>` to
|
||||
`make install` to override this default.
|
||||
|
||||
### Using the Unix adapter
|
||||
|
||||
To run a Windows console program in `mintty` or Cygwin `sshd`, prepend
|
||||
`winpty` to the command-line:
|
||||
|
||||
$ winpty powershell
|
||||
Windows PowerShell
|
||||
Copyright (C) 2009 Microsoft Corporation. All rights reserved.
|
||||
|
||||
PS C:\rprichard\proj\winpty> 10 + 20
|
||||
30
|
||||
PS C:\rprichard\proj\winpty> exit
|
||||
|
||||
## Embedding winpty / MSVC compilation
|
||||
|
||||
See `src/include/winpty.h` for the prototypes of functions exported by
|
||||
`winpty.dll`.
|
||||
|
||||
Only the `winpty.exe` binary uses Cygwin; all the other binaries work without
|
||||
it and can be compiled with either MinGW or MSVC. To compile using MSVC,
|
||||
download gyp and run `gyp -I configurations.gypi` in the `src` subdirectory.
|
||||
This will generate a `winpty.sln` and associated project files. See the
|
||||
`src/winpty.gyp` and `src/configurations.gypi` files for notes on dealing with
|
||||
MSVC versions and different architectures.
|
||||
|
||||
Compiling winpty with MSVC currently requires MSVC 2013 or newer.
|
||||
|
||||
## Debugging winpty
|
||||
|
||||
winpty comes with a tool for collecting timestamped debugging output. To use
|
||||
it:
|
||||
|
||||
1. Run `winpty-debugserver.exe` on the same computer as winpty.
|
||||
2. Set the `WINPTY_DEBUG` environment variable to `trace` for the
|
||||
`winpty.exe` process and/or the process using `libwinpty.dll`.
|
||||
|
||||
winpty also recognizes a `WINPTY_SHOW_CONSOLE` environment variable. Set it
|
||||
to 1 to prevent winpty from hiding the console window.
|
||||
|
||||
## Copyright
|
||||
|
||||
This project is distributed under the MIT license (see the `LICENSE` file in
|
||||
the project root).
|
||||
|
||||
By submitting a pull request for this project, you agree to license your
|
||||
contribution under the MIT license to this project.
|
||||
+280
@@ -0,0 +1,280 @@
|
||||
# Next Version
|
||||
|
||||
Input handling changes:
|
||||
|
||||
* Improve Ctrl-C handling with programs that use unprocessed input. (e.g.
|
||||
Ctrl-C now cancels input with PowerShell on Windows 10.)
|
||||
[#116](https://github.com/rprichard/winpty/issues/116)
|
||||
* Fix a theoretical issue with input event ordering.
|
||||
[#117](https://github.com/rprichard/winpty/issues/117)
|
||||
* Ctrl/Shift+{Arrow,Home,End} keys now work with IntelliJ.
|
||||
[#118](https://github.com/rprichard/winpty/issues/118)
|
||||
|
||||
# Version 0.4.3 (2017-05-17)
|
||||
|
||||
Input handling changes:
|
||||
|
||||
* winpty sets `ENHANCED_KEY` for arrow and navigation keys. This fixes an
|
||||
issue with the Ruby REPL.
|
||||
[#99](https://github.com/rprichard/winpty/issues/99)
|
||||
* AltGr keys are handled better now.
|
||||
[#109](https://github.com/rprichard/winpty/issues/109)
|
||||
* In `ENABLE_VIRTUAL_TERMINAL_INPUT` mode, when typing Home/End with a
|
||||
modifier (e.g. Ctrl), winpty now generates an H/F escape sequence like
|
||||
`^[[1;5F` rather than a 1/4 escape like `^[[4;5~`.
|
||||
[#114](https://github.com/rprichard/winpty/issues/114)
|
||||
|
||||
Resizing and scraping fixes:
|
||||
|
||||
* winpty now synthesizes a `WINDOW_BUFFER_SIZE_EVENT` event after resizing
|
||||
the console to better propagate window size changes to console programs.
|
||||
In particular, this affects WSL and Cygwin.
|
||||
[#110](https://github.com/rprichard/winpty/issues/110)
|
||||
* Better handling of resizing for certain full-screen programs, like
|
||||
WSL less.
|
||||
[#112](https://github.com/rprichard/winpty/issues/112)
|
||||
* Hide the cursor if it's currently outside the console window. This change
|
||||
fixes an issue with Far Manager.
|
||||
[#113](https://github.com/rprichard/winpty/issues/113)
|
||||
* winpty now avoids using console fonts smaller than 5px high to improve
|
||||
half-vs-full-width character handling. See
|
||||
https://github.com/Microsoft/vscode/issues/19665.
|
||||
[b4db322010](https://github.com/rprichard/winpty/commit/b4db322010d2d897e6c496fefc4f0ecc9b84c2f3)
|
||||
|
||||
Cygwin/MSYS adapter fix:
|
||||
|
||||
* The way the `winpty` Cygwin/MSYS2 adapter searches for the program to
|
||||
launch changed. It now resolves symlinks and searches the PATH explicitly.
|
||||
[#81](https://github.com/rprichard/winpty/issues/81)
|
||||
[#98](https://github.com/rprichard/winpty/issues/98)
|
||||
|
||||
This release does not include binaries for the old MSYS1 project anymore.
|
||||
MSYS2 will continue to be supported. See
|
||||
https://github.com/rprichard/winpty/issues/97.
|
||||
|
||||
# Version 0.4.2 (2017-01-18)
|
||||
|
||||
This release improves WSL support (i.e. Bash-on-Windows):
|
||||
|
||||
* winpty generates more correct input escape sequences for WSL programs that
|
||||
enable an alternate input mode using DECCKM. This bug affected arrow keys
|
||||
and Home/End in WSL programs such as `vim`, `mc`, and `less`.
|
||||
[#90](https://github.com/rprichard/winpty/issues/90)
|
||||
* winpty now recognizes the `COMMON_LVB_REVERSE_VIDEO` and
|
||||
`COMMON_LVB_UNDERSCORE` text attributes. The Windows console uses these
|
||||
attributes to implement the SGR.4(Underline) and SGR.7(Negative) modes in
|
||||
its VT handling. This change affects WSL pager status bars, man pages, etc.
|
||||
|
||||
The build system no longer has a "version suffix" mechanism, so passing
|
||||
`VERSION_SUFFIX=<suffix>` to make or `-D VERSION_SUFFIX=<suffix>` to gyp now
|
||||
has no effect. AFAIK, the mechanism was never used publicly.
|
||||
[67a34b6c03](https://github.com/rprichard/winpty/commit/67a34b6c03557a5c2e0a2bdd502c2210921d8f3e)
|
||||
|
||||
# Version 0.4.1 (2017-01-03)
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* This version fixes a bug where the `winpty-agent.exe` process could read
|
||||
past the end of a buffer.
|
||||
[#94](https://github.com/rprichard/winpty/issues/94)
|
||||
|
||||
# Version 0.4.0 (2016-06-28)
|
||||
|
||||
The winpty library has a new API that should be easier for embedding.
|
||||
[880c00c69e](https://github.com/rprichard/winpty/commit/880c00c69eeca73643ddb576f02c5badbec81f56)
|
||||
|
||||
User-visible changes:
|
||||
|
||||
* winpty now automatically puts the terminal into mouse mode when it detects
|
||||
that the console has left QuickEdit mode. The `--mouse` option still forces
|
||||
the terminal into mouse mode. In principle, an option could be added to
|
||||
suppress terminal mode, but hopefully it won't be necessary. There is a
|
||||
script in the `misc` subdirectory, `misc/ConinMode.ps1`, that can change
|
||||
the QuickEdit mode from the command-line.
|
||||
* winpty now passes keyboard escapes to `bash.exe` in the Windows Subsystem
|
||||
for Linux.
|
||||
[#82](https://github.com/rprichard/winpty/issues/82)
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* By default, `winpty.dll` avoids calling `SetProcessWindowStation` within
|
||||
the calling process.
|
||||
[#58](https://github.com/rprichard/winpty/issues/58)
|
||||
* Fixed an uninitialized memory bug that could have crashed winpty.
|
||||
[#80](https://github.com/rprichard/winpty/issues/80)
|
||||
* winpty now works better with very large and very small terminal windows.
|
||||
It resizes the console font according to the number of columns.
|
||||
[#61](https://github.com/rprichard/winpty/issues/61)
|
||||
* winpty no longer uses Mark to freeze the console on Windows 10. The Mark
|
||||
command could interfere with the cursor position, corrupting the data in
|
||||
the screen buffer.
|
||||
[#79](https://github.com/rprichard/winpty/issues/79)
|
||||
|
||||
# Version 0.3.0 (2016-05-20)
|
||||
|
||||
User-visible changes:
|
||||
|
||||
* The UNIX adapter is renamed from `console.exe` to `winpty.exe` to be
|
||||
consistent with MSYS2. The name `winpty.exe` is less likely to conflict
|
||||
with another program and is easier to search for online (e.g. for someone
|
||||
unfamiliar with winpty).
|
||||
* The UNIX adapter now clears the `TERM` variable.
|
||||
[#43](https://github.com/rprichard/winpty/issues/43)
|
||||
* An escape character appearing in a console screen buffer cell is converted
|
||||
to a '?'.
|
||||
[#47](https://github.com/rprichard/winpty/issues/47)
|
||||
|
||||
Bug fixes:
|
||||
|
||||
* A major bug affecting XP users was fixed.
|
||||
[#67](https://github.com/rprichard/winpty/issues/67)
|
||||
* Fixed an incompatibility with ConEmu where winpty hung if ConEmu's
|
||||
"Process 'start'" feature was enabled.
|
||||
[#70](https://github.com/rprichard/winpty/issues/70)
|
||||
* Fixed a bug where `cmd.exe` sometimes printed the message,
|
||||
`Not enough storage is available to process this command.`.
|
||||
[#74](https://github.com/rprichard/winpty/issues/74)
|
||||
|
||||
Many changes internally:
|
||||
|
||||
* The codebase is switched from C++03 to C++11 and uses exceptions internally.
|
||||
No exceptions are thrown across the C APIs defined in `winpty.h`.
|
||||
* This version drops support for the original MinGW compiler packaged with
|
||||
Cygwin (`i686-pc-mingw32-g++`). The MinGW-w64 compiler is still supported,
|
||||
as is the MinGW distributed at mingw.org. Compiling with MSVC now requires
|
||||
MSVC 2013 or newer. Windows XP is still supported.
|
||||
[ec3eae8df5](https://github.com/rprichard/winpty/commit/ec3eae8df5bbbb36d7628d168b0815638d122f37)
|
||||
* Pipe security is improved. winpty works harder to produce unique pipe names
|
||||
and includes a random component in the name. winpty secures pipes with a
|
||||
DACL that prevents arbitrary users from connecting to its pipes. winpty now
|
||||
passes `PIPE_REJECT_REMOTE_CLIENTS` on Vista and up, and it verifies that
|
||||
the pipe client PID is correct, again on Vista and up. When connecting to a
|
||||
named pipe, winpty uses the `SECURITY_IDENTIFICATION` flag to restrict
|
||||
impersonation. Previous versions *should* still be secure.
|
||||
* `winpty-debugserver.exe` now has an `--everyone` flag that allows capturing
|
||||
debug output from other users.
|
||||
* The code now compiles cleanly with MSVC's "Security Development Lifecycle"
|
||||
(`/SDL`) checks enabled.
|
||||
|
||||
# Version 0.2.2 (2016-02-25)
|
||||
|
||||
Minor bug fixes and enhancements:
|
||||
|
||||
* Fix a bug that generated spurious mouse input records when an incomplete
|
||||
mouse escape sequence was seen.
|
||||
* Fix a buffer overflow bug in `winpty-debugserver.exe` affecting messages of
|
||||
exactly 4096 bytes.
|
||||
* For MSVC builds, add a `src/configurations.gypi` file that can be included
|
||||
on the gyp command-line to enable 32-bit and 64-bit builds.
|
||||
* `winpty-agent --show-input` mode: Flush stdout after each line.
|
||||
* Makefile builds: generate a `build/winpty.lib` import library to accompany
|
||||
`build/winpty.dll`.
|
||||
|
||||
# Version 0.2.1 (2015-12-19)
|
||||
|
||||
* The main project source was moved into a `src` directory for better code
|
||||
organization and to fix
|
||||
[#51](https://github.com/rprichard/winpty/issues/51).
|
||||
* winpty recognizes many more escape sequences, including:
|
||||
* putty/rxvt's F1-F4 keys
|
||||
[#40](https://github.com/rprichard/winpty/issues/40)
|
||||
* the Linux virtual console's F1-F5 keys
|
||||
* the "application numpad" keys (e.g. enabled with DECPAM)
|
||||
* Fixed handling of Shift-Alt-O and Alt-[.
|
||||
* Added support for mouse input. The UNIX adapter has a `--mouse` argument
|
||||
that puts the terminal into mouse mode, but the agent recognizes mouse
|
||||
input even without the argument. The agent recognizes double-clicks using
|
||||
Windows' double-click interval setting (i.e. GetDoubleClickTime).
|
||||
[#57](https://github.com/rprichard/winpty/issues/57)
|
||||
|
||||
Changes to debugging interfaces:
|
||||
|
||||
* The `WINPTY_DEBUG` variable is now a comma-separated list. The old
|
||||
behavior (i.e. tracing) is enabled with `WINPTY_DEBUG=trace`.
|
||||
* The UNIX adapter program now has a `--showkey` argument that dumps input
|
||||
bytes.
|
||||
* The `winpty-agent.exe` program has a `--show-input` argument that dumps
|
||||
`INPUT_RECORD` records. (It omits mouse events unless `--with-mouse` is
|
||||
also specified.) The agent also responds to `WINPTY_DEBUG=trace,input`,
|
||||
which logs input bytes and synthesized console events, and it responds to
|
||||
`WINPTY_DEBUG=trace,dump_input_map`, which dumps the internal table of
|
||||
escape sequences.
|
||||
|
||||
# Version 0.2.0 (2015-11-13)
|
||||
|
||||
No changes to the API, but many small changes to the implementation. The big
|
||||
changes include:
|
||||
|
||||
* Support for 64-bit Cygwin and MSYS2
|
||||
* Support for Windows 10
|
||||
* Better Unicode support (especially East Asian languages)
|
||||
|
||||
Details:
|
||||
|
||||
* The `configure` script recognizes 64-bit Cygwin and MSYS2 environments and
|
||||
selects the appropriate compiler.
|
||||
* winpty works much better with the upgraded console in Windows 10. The
|
||||
`conhost.exe` hang can still occur, but only with certain programs, and
|
||||
is much less likely to occur. With the new console, use Mark instead of
|
||||
SelectAll, for better performance.
|
||||
[#31](https://github.com/rprichard/winpty/issues/31)
|
||||
[#30](https://github.com/rprichard/winpty/issues/30)
|
||||
[#53](https://github.com/rprichard/winpty/issues/53)
|
||||
* The UNIX adapter now calls `setlocale(LC_ALL, "")` to set the locale.
|
||||
* Improved Unicode support. When a console is started with an East Asian code
|
||||
page, winpty now chooses an East Asian font rather than Consolas / Lucida
|
||||
Console. Selecting the right font helps synchronize character widths
|
||||
between the console and terminal. (It's not perfect, though.)
|
||||
[#41](https://github.com/rprichard/winpty/issues/41)
|
||||
* winpty now more-or-less works with programs that change the screen buffer
|
||||
or resize the original screen buffer. If the screen buffer height changes,
|
||||
winpty switches to a "direct mode", where it makes no effort to track
|
||||
scrolling. In direct mode, it merely syncs snapshots of the console to the
|
||||
terminal. Caveats:
|
||||
* Changing the screen buffer (i.e. `SetConsoleActiveScreenBuffer`)
|
||||
breaks winpty on Windows 7. This problem can eventually be mitigated,
|
||||
but never completely fixed, due to Windows 7 bugginess.
|
||||
* Resizing the original screen buffer can hang `conhost.exe` on Windows 10.
|
||||
Enabling the legacy console is a workaround.
|
||||
* If a program changes the screen buffer and then exits, relying on the OS
|
||||
to restore the original screen buffer, that restoration probably will not
|
||||
happen with winpty. winpty's behavior can probably be improved here.
|
||||
* Improved color handling:
|
||||
* DkGray-on-Black text was previously hiddenly completely. Now it is
|
||||
output as DkGray, with a fallback to LtGray on terminals that don't
|
||||
recognize the intense colors.
|
||||
[#39](https://github.com/rprichard/winpty/issues/39).
|
||||
* The console is always initialized to LtGray-on-Black, regardless of the
|
||||
user setting, which matches the console color heuristic, which translates
|
||||
LtGray-on-Black to "reset SGR parameters."
|
||||
* Shift-Tab is recognized correctly now.
|
||||
[#19](https://github.com/rprichard/winpty/issues/19)
|
||||
* Add a `--version` argument to `winpty-agent.exe` and the UNIX adapter. The
|
||||
argument reports the nominal version (i.e. the `VERSION.txt`) file, with a
|
||||
"VERSION_SUFFIX" appended (defaulted to `-dev`), and a git commit hash, if
|
||||
the `git` command successfully reports a hash during the build. The `git`
|
||||
command is invoked by either `make` or `gyp`.
|
||||
* The agent now combines `ReadConsoleOutputW` calls when it polls the console
|
||||
buffer for changes, which may slightly reduce its CPU overhead.
|
||||
[#44](https://github.com/rprichard/winpty/issues/44).
|
||||
* A `gyp` file is added to help compile with MSVC.
|
||||
* The code can now be compiled as C++11 code, though it isn't by default.
|
||||
[bde8922e08](https://github.com/rprichard/winpty/commit/bde8922e08c3638e01ecc7b581b676c314163e3c)
|
||||
* If winpty can't create a new window station, it charges ahead rather than
|
||||
aborting. This situation might happen if winpty were started from an SSH
|
||||
session.
|
||||
* Debugging improvements:
|
||||
* `WINPTYDBG` is renamed to `WINPTY_DEBUG`, and a new `WINPTY_SHOW_CONSOLE`
|
||||
variable keeps the underlying console visible.
|
||||
* A `winpty-debugserver.exe` program is built and shipped by default. It
|
||||
collects the trace output enabled with `WINPTY_DEBUG`.
|
||||
* The `Makefile` build of winpty now compiles `winpty-agent.exe` and
|
||||
`winpty.dll` with -O2.
|
||||
|
||||
# Version 0.1.1 (2012-07-28)
|
||||
|
||||
Minor bugfix release.
|
||||
|
||||
# Version 0.1 (2012-04-17)
|
||||
|
||||
Initial release.
|
||||
+1
@@ -0,0 +1 @@
|
||||
0.4.4-dev
|
||||
+167
@@ -0,0 +1,167 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Copyright (c) 2011-2015 Ryan Prichard
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
#
|
||||
# findTool(desc, commandList)
|
||||
#
|
||||
# Searches commandLine for the first command in the PATH and returns it.
|
||||
# Prints an error and aborts the script if no match is found.
|
||||
#
|
||||
FINDTOOL_OUT=""
|
||||
function findTool {
|
||||
DESC=$1
|
||||
OPTIONS=$2
|
||||
for CMD in ${OPTIONS}; do
|
||||
if (which $CMD &>/dev/null) then
|
||||
echo "Found $DESC: $CMD"
|
||||
FINDTOOL_OUT="$CMD"
|
||||
return
|
||||
fi
|
||||
done
|
||||
echo "Error: could not find $DESC. One of these should be in your PATH:"
|
||||
for CMD in ${OPTIONS}; do
|
||||
echo " * $CMD"
|
||||
done
|
||||
exit 1
|
||||
}
|
||||
|
||||
IS_CYGWIN=0
|
||||
IS_MSYS1=0
|
||||
IS_MSYS2=0
|
||||
|
||||
# Link parts of the Cygwin binary statically to aid in redistribution? The
|
||||
# binary still links dynamically against the main DLL. The MinGW binaries are
|
||||
# also statically linked and therefore depend only on Windows DLLs. I started
|
||||
# linking the Cygwin/MSYS binary statically, because G++ 4.7 changed the
|
||||
# Windows C++ ABI.
|
||||
UNIX_LDFLAGS_STATIC='-static -static-libgcc -static-libstdc++'
|
||||
|
||||
# Detect the environment -- Cygwin or MSYS.
|
||||
case $(uname -s) in
|
||||
CYGWIN*)
|
||||
echo 'uname -s identifies a Cygwin environment.'
|
||||
IS_CYGWIN=1
|
||||
case $(uname -m) in
|
||||
i686)
|
||||
echo 'uname -m identifies an i686 environment.'
|
||||
UNIX_CXX=i686-pc-cygwin-g++
|
||||
MINGW_CXX=i686-w64-mingw32-g++
|
||||
;;
|
||||
x86_64)
|
||||
echo 'uname -m identifies an x86_64 environment.'
|
||||
UNIX_CXX=x86_64-pc-cygwin-g++
|
||||
MINGW_CXX=x86_64-w64-mingw32-g++
|
||||
;;
|
||||
*)
|
||||
echo 'Error: uname -m did not match either i686 or x86_64.'
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
MSYS*|MINGW*)
|
||||
# MSYS2 notes:
|
||||
# - MSYS2 offers two shortcuts to open an environment:
|
||||
# - MinGW-w64 Win32 Shell. This env reports a `uname -s` of
|
||||
# MINGW32_NT-6.1 on 32-bit Win7. The MinGW-w64 compiler
|
||||
# (i686-w64-mingw32-g++.exe) is in the PATH.
|
||||
# - MSYS2 Shell. `uname -s` instead reports MSYS_NT-6.1.
|
||||
# The i686-w64-mingw32-g++ compiler is not in the PATH.
|
||||
# - MSYS2 appears to use MinGW-w64, not the older mingw.org.
|
||||
# MSYS notes:
|
||||
# - `uname -s` is always MINGW32_NT-6.1 on Win7.
|
||||
echo 'uname -s identifies an MSYS/MSYS2 environment.'
|
||||
case $(uname -m) in
|
||||
i686)
|
||||
echo 'uname -m identifies an i686 environment.'
|
||||
UNIX_CXX=i686-pc-msys-g++
|
||||
if echo "$(uname -r)" | grep '^1[.]' > /dev/null; then
|
||||
# The MSYS-targeting compiler for the original 32-bit-only
|
||||
# MSYS does not recognize the -static-libstdc++ flag, and
|
||||
# it does not work with -static, because it tries to link
|
||||
# statically with the core MSYS library and fails.
|
||||
#
|
||||
# Distinguish between the two using the major version
|
||||
# number of `uname -r`:
|
||||
#
|
||||
# MSYS uname -r: 1.0.18(0.48/3/2)
|
||||
# MSYS2 uname -r: 2.0.0(0.284/5/3)
|
||||
#
|
||||
# This is suboptimal because MSYS2 is not actually the
|
||||
# second version of MSYS--it's a brand-new fork of Cygwin.
|
||||
#
|
||||
IS_MSYS1=1
|
||||
UNIX_LDFLAGS_STATIC=
|
||||
MINGW_CXX=mingw32-g++
|
||||
else
|
||||
IS_MSYS2=1
|
||||
MINGW_CXX=i686-w64-mingw32-g++.exe
|
||||
fi
|
||||
;;
|
||||
x86_64)
|
||||
echo 'uname -m identifies an x86_64 environment.'
|
||||
IS_MSYS2=1
|
||||
UNIX_CXX=x86_64-pc-msys-g++
|
||||
MINGW_CXX=x86_64-w64-mingw32-g++
|
||||
;;
|
||||
*)
|
||||
echo 'Error: uname -m did not match either i686 or x86_64.'
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
echo 'Error: uname -s did not match either CYGWIN* or MINGW*.'
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
# Search the PATH and pick the first match.
|
||||
findTool "Cygwin/MSYS G++ compiler" "$UNIX_CXX"
|
||||
UNIX_CXX=$FINDTOOL_OUT
|
||||
findTool "MinGW G++ compiler" "$MINGW_CXX"
|
||||
MINGW_CXX=$FINDTOOL_OUT
|
||||
|
||||
# Write config files.
|
||||
echo Writing config.mk
|
||||
echo UNIX_CXX=$UNIX_CXX > config.mk
|
||||
echo UNIX_LDFLAGS_STATIC=$UNIX_LDFLAGS_STATIC >> config.mk
|
||||
echo MINGW_CXX=$MINGW_CXX >> config.mk
|
||||
|
||||
if test $IS_MSYS1 = 1; then
|
||||
echo UNIX_CXXFLAGS += -DWINPTY_TARGET_MSYS1 >> config.mk
|
||||
# The MSYS1 MinGW compiler has a bug that prevents inclusion of algorithm
|
||||
# and math.h in normal C++11 mode. The workaround is to enable the gnu++11
|
||||
# mode instead. The bug was fixed on 2015-07-31, but as of 2016-02-26, the
|
||||
# fix apparently hasn't been released. See
|
||||
# http://ehc.ac/p/mingw/bugs/2250/.
|
||||
echo MINGW_ENABLE_CXX11_FLAG := -std=gnu++11 >> config.mk
|
||||
fi
|
||||
|
||||
if test -d .git -a -f .git/HEAD -a -f .git/index && git rev-parse HEAD >&/dev/null; then
|
||||
echo "Commit info: git"
|
||||
echo 'COMMIT_HASH = $(shell git rev-parse HEAD)' >> config.mk
|
||||
echo 'COMMIT_HASH_DEP := config.mk .git/HEAD .git/index' >> config.mk
|
||||
else
|
||||
echo "Commit info: none"
|
||||
echo 'COMMIT_HASH := none' >> config.mk
|
||||
echo 'COMMIT_HASH_DEP := config.mk' >> config.mk
|
||||
fi
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
#include <windows.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "TestUtil.cc"
|
||||
|
||||
void dumpInfoToTrace() {
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
assert(GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info));
|
||||
trace("win=(%d,%d,%d,%d)",
|
||||
(int)info.srWindow.Left,
|
||||
(int)info.srWindow.Top,
|
||||
(int)info.srWindow.Right,
|
||||
(int)info.srWindow.Bottom);
|
||||
trace("buf=(%d,%d)",
|
||||
(int)info.dwSize.X,
|
||||
(int)info.dwSize.Y);
|
||||
trace("cur=(%d,%d)",
|
||||
(int)info.dwCursorPosition.X,
|
||||
(int)info.dwCursorPosition.Y);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc == 1) {
|
||||
startChildProcess(L"CHILD");
|
||||
return 0;
|
||||
}
|
||||
|
||||
setWindowPos(0, 0, 1, 1);
|
||||
|
||||
if (false) {
|
||||
// Reducing the buffer height can move the window up.
|
||||
setBufferSize(80, 25);
|
||||
setWindowPos(0, 20, 80, 5);
|
||||
Sleep(2000);
|
||||
setBufferSize(80, 10);
|
||||
}
|
||||
|
||||
if (false) {
|
||||
// Reducing the buffer height moves the window up and the buffer
|
||||
// contents up too.
|
||||
setBufferSize(80, 25);
|
||||
setWindowPos(0, 20, 80, 5);
|
||||
setCursorPos(0, 20);
|
||||
printf("TEST1\nTEST2\nTEST3\nTEST4\n");
|
||||
fflush(stdout);
|
||||
Sleep(2000);
|
||||
setBufferSize(80, 10);
|
||||
}
|
||||
|
||||
if (false) {
|
||||
// Reducing the buffer width can move the window left.
|
||||
setBufferSize(80, 25);
|
||||
setWindowPos(40, 0, 40, 25);
|
||||
Sleep(2000);
|
||||
setBufferSize(60, 25);
|
||||
}
|
||||
|
||||
if (false) {
|
||||
// Sometimes the buffer contents are shifted up; sometimes they're
|
||||
// shifted down. It seems to depend on the cursor position?
|
||||
|
||||
// setBufferSize(80, 25);
|
||||
// setWindowPos(0, 20, 80, 5);
|
||||
// setCursorPos(0, 20);
|
||||
// printf("TESTa\nTESTb\nTESTc\nTESTd\nTESTe");
|
||||
// fflush(stdout);
|
||||
// setCursorPos(0, 0);
|
||||
// printf("TEST1\nTEST2\nTEST3\nTEST4\nTEST5");
|
||||
// fflush(stdout);
|
||||
// setCursorPos(0, 24);
|
||||
// Sleep(5000);
|
||||
// setBufferSize(80, 24);
|
||||
|
||||
setBufferSize(80, 20);
|
||||
setWindowPos(0, 10, 80, 10);
|
||||
setCursorPos(0, 18);
|
||||
|
||||
printf("TEST1\nTEST2");
|
||||
fflush(stdout);
|
||||
setCursorPos(0, 18);
|
||||
|
||||
Sleep(2000);
|
||||
setBufferSize(80, 18);
|
||||
}
|
||||
|
||||
dumpInfoToTrace();
|
||||
Sleep(30000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
// A test program for CreateConsoleScreenBuffer / SetConsoleActiveScreenBuffer
|
||||
//
|
||||
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <conio.h>
|
||||
#include <io.h>
|
||||
#include <cassert>
|
||||
|
||||
#include "TestUtil.cc"
|
||||
|
||||
int main()
|
||||
{
|
||||
HANDLE origBuffer = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
HANDLE childBuffer = CreateConsoleScreenBuffer(
|
||||
GENERIC_READ | GENERIC_WRITE,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE,
|
||||
NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
|
||||
|
||||
SetConsoleActiveScreenBuffer(childBuffer);
|
||||
|
||||
while (true) {
|
||||
char buf[1024];
|
||||
CONSOLE_SCREEN_BUFFER_INFO info;
|
||||
|
||||
assert(GetConsoleScreenBufferInfo(origBuffer, &info));
|
||||
trace("child.size=(%d,%d)", (int)info.dwSize.X, (int)info.dwSize.Y);
|
||||
trace("child.cursor=(%d,%d)", (int)info.dwCursorPosition.X, (int)info.dwCursorPosition.Y);
|
||||
trace("child.window=(%d,%d,%d,%d)",
|
||||
(int)info.srWindow.Left, (int)info.srWindow.Top,
|
||||
(int)info.srWindow.Right, (int)info.srWindow.Bottom);
|
||||
trace("child.maxSize=(%d,%d)", (int)info.dwMaximumWindowSize.X, (int)info.dwMaximumWindowSize.Y);
|
||||
|
||||
int ch = getch();
|
||||
sprintf(buf, "%02x\n", ch);
|
||||
DWORD actual = 0;
|
||||
WriteFile(childBuffer, buf, strlen(buf), &actual, NULL);
|
||||
if (ch == 0x1b/*ESC*/ || ch == 0x03/*CTRL-C*/)
|
||||
break;
|
||||
|
||||
if (ch == 'b') {
|
||||
setBufferSize(origBuffer, 40, 25);
|
||||
} else if (ch == 'w') {
|
||||
setWindowPos(origBuffer, 1, 1, 38, 23);
|
||||
} else if (ch == 'c') {
|
||||
setCursorPos(origBuffer, 10, 10);
|
||||
}
|
||||
}
|
||||
|
||||
SetConsoleActiveScreenBuffer(origBuffer);
|
||||
|
||||
return 0;
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Demonstrates that console clearing sets each cell's character to SP, not
|
||||
* NUL, and it sets the attribute of each cell to the current text attribute.
|
||||
*
|
||||
* This confirms the MSDN instruction in the "Clearing the Screen" article.
|
||||
* https://msdn.microsoft.com/en-us/library/windows/desktop/ms682022(v=vs.85).aspx
|
||||
* It advises using GetConsoleScreenBufferInfo to get the current text
|
||||
* attribute, then FillConsoleOutputCharacter and FillConsoleOutputAttribute to
|
||||
* write to the console buffer.
|
||||
*/
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
|
||||
#include "TestUtil.cc"
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc == 1) {
|
||||
startChildProcess(L"CHILD");
|
||||
return 0;
|
||||
}
|
||||
|
||||
const HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
|
||||
SetConsoleTextAttribute(conout, 0x24);
|
||||
system("cls");
|
||||
|
||||
setWindowPos(0, 0, 1, 1);
|
||||
setBufferSize(80, 25);
|
||||
setWindowPos(0, 0, 80, 25);
|
||||
|
||||
CHAR_INFO buf;
|
||||
COORD bufSize = { 1, 1 };
|
||||
COORD bufCoord = { 0, 0 };
|
||||
SMALL_RECT rect = { 5, 5, 5, 5 };
|
||||
BOOL ret;
|
||||
DWORD actual;
|
||||
COORD writeCoord = { 5, 5 };
|
||||
|
||||
// After cls, each cell's character is a space, and its attributes are the
|
||||
// default text attributes.
|
||||
ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
|
||||
assert(ret && buf.Char.UnicodeChar == L' ' && buf.Attributes == 0x24);
|
||||
|
||||
// Nevertheless, it is possible to change a cell to NUL.
|
||||
ret = FillConsoleOutputCharacterW(conout, L'\0', 1, writeCoord, &actual);
|
||||
assert(ret && actual == 1);
|
||||
ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
|
||||
assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0x24);
|
||||
|
||||
// As well as a 0 attribute. (As one would expect, the cell is
|
||||
// black-on-black.)
|
||||
ret = FillConsoleOutputAttribute(conout, 0, 1, writeCoord, &actual);
|
||||
assert(ret && actual == 1);
|
||||
ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
|
||||
assert(ret && buf.Char.UnicodeChar == L'\0' && buf.Attributes == 0);
|
||||
ret = FillConsoleOutputCharacterW(conout, L'X', 1, writeCoord, &actual);
|
||||
assert(ret && actual == 1);
|
||||
ret = ReadConsoleOutputW(conout, &buf, bufSize, bufCoord, &rect);
|
||||
assert(ret && buf.Char.UnicodeChar == L'X' && buf.Attributes == 0);
|
||||
|
||||
// The 'X' is invisible.
|
||||
countDown(3);
|
||||
|
||||
ret = FillConsoleOutputAttribute(conout, 0x42, 1, writeCoord, &actual);
|
||||
assert(ret && actual == 1);
|
||||
|
||||
countDown(5);
|
||||
}
|
||||
+117
@@ -0,0 +1,117 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static HANDLE getConin() {
|
||||
HANDLE conin = GetStdHandle(STD_INPUT_HANDLE);
|
||||
if (conin == INVALID_HANDLE_VALUE) {
|
||||
fprintf(stderr, "error: cannot get stdin\n");
|
||||
exit(1);
|
||||
}
|
||||
return conin;
|
||||
}
|
||||
|
||||
static DWORD getConsoleMode() {
|
||||
DWORD mode = 0;
|
||||
if (!GetConsoleMode(getConin(), &mode)) {
|
||||
fprintf(stderr, "error: GetConsoleMode failed (is stdin a console?)\n");
|
||||
exit(1);
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void setConsoleMode(DWORD mode) {
|
||||
if (!SetConsoleMode(getConin(), mode)) {
|
||||
fprintf(stderr, "error: SetConsoleMode failed (is stdin a console?)\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static long parseInt(const std::string &s) {
|
||||
errno = 0;
|
||||
char *endptr = nullptr;
|
||||
long result = strtol(s.c_str(), &endptr, 0);
|
||||
if (errno != 0 || !endptr || *endptr != '\0') {
|
||||
fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str());
|
||||
exit(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
printf("Usage: ConinMode [verb] [options]\n");
|
||||
printf("Verbs:\n");
|
||||
printf(" [info] Dumps info about mode flags.\n");
|
||||
printf(" get Prints the mode DWORD.\n");
|
||||
printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n");
|
||||
printf(" set VALUE MASK\n");
|
||||
printf(" Same as `set VALUE`, but only alters the bits in MASK.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
DWORD value;
|
||||
} kInputFlags[] = {
|
||||
"ENABLE_PROCESSED_INPUT", ENABLE_PROCESSED_INPUT, // 0x0001
|
||||
"ENABLE_LINE_INPUT", ENABLE_LINE_INPUT, // 0x0002
|
||||
"ENABLE_ECHO_INPUT", ENABLE_ECHO_INPUT, // 0x0004
|
||||
"ENABLE_WINDOW_INPUT", ENABLE_WINDOW_INPUT, // 0x0008
|
||||
"ENABLE_MOUSE_INPUT", ENABLE_MOUSE_INPUT, // 0x0010
|
||||
"ENABLE_INSERT_MODE", ENABLE_INSERT_MODE, // 0x0020
|
||||
"ENABLE_QUICK_EDIT_MODE", ENABLE_QUICK_EDIT_MODE, // 0x0040
|
||||
"ENABLE_EXTENDED_FLAGS", ENABLE_EXTENDED_FLAGS, // 0x0080
|
||||
"ENABLE_VIRTUAL_TERMINAL_INPUT", 0x0200/*ENABLE_VIRTUAL_TERMINAL_INPUT*/, // 0x0200
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
std::vector<std::string> args;
|
||||
for (size_t i = 1; i < argc; ++i) {
|
||||
args.push_back(argv[i]);
|
||||
}
|
||||
|
||||
if (args.empty() || args.size() == 1 && args[0] == "info") {
|
||||
DWORD mode = getConsoleMode();
|
||||
printf("mode: 0x%lx\n", mode);
|
||||
for (const auto &flag : kInputFlags) {
|
||||
printf("%-29s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off");
|
||||
mode &= ~flag.value;
|
||||
}
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
if (mode & (1u << i)) {
|
||||
printf("Unrecognized flag: %04x\n", (1u << i));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto verb = args[0];
|
||||
|
||||
if (verb == "set") {
|
||||
if (args.size() == 2) {
|
||||
const DWORD newMode = parseInt(args[1]);
|
||||
setConsoleMode(newMode);
|
||||
} else if (args.size() == 3) {
|
||||
const DWORD mode = parseInt(args[1]);
|
||||
const DWORD mask = parseInt(args[2]);
|
||||
const int newMode = (getConsoleMode() & ~mask) | (mode & mask);
|
||||
setConsoleMode(newMode);
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
} else if (verb == "get") {
|
||||
if (args.size() != 1) {
|
||||
usage();
|
||||
}
|
||||
printf("0x%lx\n", getConsoleMode());
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
#
|
||||
# PowerShell script for controlling the console QuickEdit and InsertMode flags.
|
||||
#
|
||||
# Turn QuickEdit off to interact with mouse-driven console programs.
|
||||
#
|
||||
# Usage:
|
||||
#
|
||||
# powershell .\ConinMode.ps1 [Options]
|
||||
#
|
||||
# Options:
|
||||
# -QuickEdit [on/off]
|
||||
# -InsertMode [on/off]
|
||||
# -Mode [integer]
|
||||
#
|
||||
|
||||
param (
|
||||
[ValidateSet("on", "off")][string] $QuickEdit,
|
||||
[ValidateSet("on", "off")][string] $InsertMode,
|
||||
[int] $Mode
|
||||
)
|
||||
|
||||
$signature = @'
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern IntPtr GetStdHandle(int nStdHandle);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint GetConsoleMode(
|
||||
IntPtr hConsoleHandle,
|
||||
out uint lpMode);
|
||||
|
||||
[DllImport("kernel32.dll", SetLastError = true)]
|
||||
public static extern uint SetConsoleMode(
|
||||
IntPtr hConsoleHandle,
|
||||
uint dwMode);
|
||||
|
||||
public const int STD_INPUT_HANDLE = -10;
|
||||
public const int ENABLE_INSERT_MODE = 0x0020;
|
||||
public const int ENABLE_QUICK_EDIT_MODE = 0x0040;
|
||||
public const int ENABLE_EXTENDED_FLAGS = 0x0080;
|
||||
'@
|
||||
|
||||
$WinAPI = Add-Type -MemberDefinition $signature `
|
||||
-Name WinAPI -Namespace ConinModeScript `
|
||||
-PassThru
|
||||
|
||||
function GetConIn {
|
||||
$ret = $WinAPI::GetStdHandle($WinAPI::STD_INPUT_HANDLE)
|
||||
if ($ret -eq -1) {
|
||||
throw "error: cannot get stdin"
|
||||
}
|
||||
return $ret
|
||||
}
|
||||
|
||||
function GetConsoleMode {
|
||||
$conin = GetConIn
|
||||
$mode = 0
|
||||
$ret = $WinAPI::GetConsoleMode($conin, [ref]$mode)
|
||||
if ($ret -eq 0) {
|
||||
throw "GetConsoleMode failed (is stdin a console?)"
|
||||
}
|
||||
return $mode
|
||||
}
|
||||
|
||||
function SetConsoleMode($mode) {
|
||||
$conin = GetConIn
|
||||
$ret = $WinAPI::SetConsoleMode($conin, $mode)
|
||||
if ($ret -eq 0) {
|
||||
throw "SetConsoleMode failed (is stdin a console?)"
|
||||
}
|
||||
}
|
||||
|
||||
$oldMode = GetConsoleMode
|
||||
$newMode = $oldMode
|
||||
$doingSomething = $false
|
||||
|
||||
if ($PSBoundParameters.ContainsKey("Mode")) {
|
||||
$newMode = $Mode
|
||||
$doingSomething = $true
|
||||
}
|
||||
|
||||
if ($QuickEdit + $InsertMode -ne "") {
|
||||
if (!($newMode -band $WinAPI::ENABLE_EXTENDED_FLAGS)) {
|
||||
# We can't enable an extended flag without overwriting the existing
|
||||
# QuickEdit/InsertMode flags. AFAICT, there is no way to query their
|
||||
# existing values, so at least we can choose sensible defaults.
|
||||
$newMode = $newMode -bor $WinAPI::ENABLE_EXTENDED_FLAGS
|
||||
$newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE
|
||||
$newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE
|
||||
$doingSomething = $true
|
||||
}
|
||||
}
|
||||
|
||||
if ($QuickEdit -eq "on") {
|
||||
$newMode = $newMode -bor $WinAPI::ENABLE_QUICK_EDIT_MODE
|
||||
$doingSomething = $true
|
||||
} elseif ($QuickEdit -eq "off") {
|
||||
$newMode = $newMode -band (-bnot $WinAPI::ENABLE_QUICK_EDIT_MODE)
|
||||
$doingSomething = $true
|
||||
}
|
||||
|
||||
if ($InsertMode -eq "on") {
|
||||
$newMode = $newMode -bor $WinAPI::ENABLE_INSERT_MODE
|
||||
$doingSomething = $true
|
||||
} elseif ($InsertMode -eq "off") {
|
||||
$newMode = $newMode -band (-bnot $WinAPI::ENABLE_INSERT_MODE)
|
||||
$doingSomething = $true
|
||||
}
|
||||
|
||||
if ($doingSomething) {
|
||||
echo "old mode: $oldMode"
|
||||
SetConsoleMode $newMode
|
||||
$newMode = GetConsoleMode
|
||||
echo "new mode: $newMode"
|
||||
} else {
|
||||
echo "mode: $oldMode"
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
static HANDLE getConout() {
|
||||
HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (conout == INVALID_HANDLE_VALUE) {
|
||||
fprintf(stderr, "error: cannot get stdout\n");
|
||||
exit(1);
|
||||
}
|
||||
return conout;
|
||||
}
|
||||
|
||||
static DWORD getConsoleMode() {
|
||||
DWORD mode = 0;
|
||||
if (!GetConsoleMode(getConout(), &mode)) {
|
||||
fprintf(stderr, "error: GetConsoleMode failed (is stdout a console?)\n");
|
||||
exit(1);
|
||||
}
|
||||
return mode;
|
||||
}
|
||||
|
||||
static void setConsoleMode(DWORD mode) {
|
||||
if (!SetConsoleMode(getConout(), mode)) {
|
||||
fprintf(stderr, "error: SetConsoleMode failed (is stdout a console?)\n");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static long parseInt(const std::string &s) {
|
||||
errno = 0;
|
||||
char *endptr = nullptr;
|
||||
long result = strtol(s.c_str(), &endptr, 0);
|
||||
if (errno != 0 || !endptr || *endptr != '\0') {
|
||||
fprintf(stderr, "error: could not parse integral argument '%s'\n", s.c_str());
|
||||
exit(1);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void usage() {
|
||||
printf("Usage: ConoutMode [verb] [options]\n");
|
||||
printf("Verbs:\n");
|
||||
printf(" [info] Dumps info about mode flags.\n");
|
||||
printf(" get Prints the mode DWORD.\n");
|
||||
printf(" set VALUE Sets the mode to VALUE, which can be decimal, hex, or octal.\n");
|
||||
printf(" set VALUE MASK\n");
|
||||
printf(" Same as `set VALUE`, but only alters the bits in MASK.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
struct {
|
||||
const char *name;
|
||||
DWORD value;
|
||||
} kOutputFlags[] = {
|
||||
"ENABLE_PROCESSED_OUTPUT", ENABLE_PROCESSED_OUTPUT, // 0x0001
|
||||
"ENABLE_WRAP_AT_EOL_OUTPUT", ENABLE_WRAP_AT_EOL_OUTPUT, // 0x0002
|
||||
"ENABLE_VIRTUAL_TERMINAL_PROCESSING", 0x0004/*ENABLE_VIRTUAL_TERMINAL_PROCESSING*/, // 0x0004
|
||||
"DISABLE_NEWLINE_AUTO_RETURN", 0x0008/*DISABLE_NEWLINE_AUTO_RETURN*/, // 0x0008
|
||||
"ENABLE_LVB_GRID_WORLDWIDE", 0x0010/*ENABLE_LVB_GRID_WORLDWIDE*/, //0x0010
|
||||
};
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
std::vector<std::string> args;
|
||||
for (size_t i = 1; i < argc; ++i) {
|
||||
args.push_back(argv[i]);
|
||||
}
|
||||
|
||||
if (args.empty() || args.size() == 1 && args[0] == "info") {
|
||||
DWORD mode = getConsoleMode();
|
||||
printf("mode: 0x%lx\n", mode);
|
||||
for (const auto &flag : kOutputFlags) {
|
||||
printf("%-34s 0x%04lx %s\n", flag.name, flag.value, flag.value & mode ? "ON" : "off");
|
||||
mode &= ~flag.value;
|
||||
}
|
||||
for (int i = 0; i < 32; ++i) {
|
||||
if (mode & (1u << i)) {
|
||||
printf("Unrecognized flag: %04x\n", (1u << i));
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto verb = args[0];
|
||||
|
||||
if (verb == "set") {
|
||||
if (args.size() == 2) {
|
||||
const DWORD newMode = parseInt(args[1]);
|
||||
setConsoleMode(newMode);
|
||||
} else if (args.size() == 3) {
|
||||
const DWORD mode = parseInt(args[1]);
|
||||
const DWORD mask = parseInt(args[2]);
|
||||
const int newMode = (getConsoleMode() & ~mask) | (mode & mask);
|
||||
setConsoleMode(newMode);
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
} else if (verb == "get") {
|
||||
if (args.size() != 1) {
|
||||
usage();
|
||||
}
|
||||
printf("0x%lx\n", getConsoleMode());
|
||||
} else {
|
||||
usage();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#!python
|
||||
# Run with native CPython. Needs pywin32 extensions.
|
||||
|
||||
# Copyright (c) 2011-2012 Ryan Prichard
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import winerror
|
||||
import win32pipe
|
||||
import win32file
|
||||
import win32api
|
||||
import sys
|
||||
import pywintypes
|
||||
import time
|
||||
|
||||
if len(sys.argv) != 2:
|
||||
print("Usage: %s message" % sys.argv[0])
|
||||
sys.exit(1)
|
||||
|
||||
message = "[%05.3f %s]: %s" % (time.time() % 100000, sys.argv[0], sys.argv[1])
|
||||
|
||||
win32pipe.CallNamedPipe(
|
||||
"\\\\.\\pipe\\DebugServer",
|
||||
message.encode(),
|
||||
16,
|
||||
win32pipe.NMPWAIT_WAIT_FOREVER)
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#!python
|
||||
#
|
||||
# Run with native CPython. Needs pywin32 extensions.
|
||||
|
||||
# Copyright (c) 2011-2012 Ryan Prichard
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
import win32pipe
|
||||
import win32api
|
||||
import win32file
|
||||
import time
|
||||
import threading
|
||||
import sys
|
||||
|
||||
# A message may not be larger than this size.
|
||||
MSG_SIZE=4096
|
||||
|
||||
serverPipe = win32pipe.CreateNamedPipe(
|
||||
"\\\\.\\pipe\\DebugServer",
|
||||
win32pipe.PIPE_ACCESS_DUPLEX,
|
||||
win32pipe.PIPE_TYPE_MESSAGE | win32pipe.PIPE_READMODE_MESSAGE,
|
||||
win32pipe.PIPE_UNLIMITED_INSTANCES,
|
||||
MSG_SIZE,
|
||||
MSG_SIZE,
|
||||
10 * 1000,
|
||||
None)
|
||||
while True:
|
||||
win32pipe.ConnectNamedPipe(serverPipe, None)
|
||||
(ret, data) = win32file.ReadFile(serverPipe, MSG_SIZE)
|
||||
print(data.decode())
|
||||
sys.stdout.flush()
|
||||
|
||||
# The client uses CallNamedPipe to send its message. CallNamedPipe waits
|
||||
# for a reply message. If I send a reply, however, using WriteFile, then
|
||||
# sometimes WriteFile fails with:
|
||||
# pywintypes.error: (232, 'WriteFile', 'The pipe is being closed.')
|
||||
# I can't figure out how to write a strictly correct pipe server, but if
|
||||
# I comment out the WriteFile line, then everything seems to work. I
|
||||
# think the DisconnectNamedPipe call aborts the client's CallNamedPipe
|
||||
# call normally.
|
||||
|
||||
try:
|
||||
win32file.WriteFile(serverPipe, b'OK')
|
||||
except:
|
||||
pass
|
||||
win32pipe.DisconnectNamedPipe(serverPipe)
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
#!/usr/bin/env python
|
||||
import sys
|
||||
|
||||
for i in range(1, int(sys.argv[1]) + 1):
|
||||
print i, "X" * 78
|
||||
Generated
Vendored
+46
@@ -0,0 +1,46 @@
|
||||
Note regarding ENABLE_EXTENDED_FLAGS (2016-05-30)
|
||||
|
||||
There is a complicated interaction between the ENABLE_EXTENDED_FLAGS flag
|
||||
and the ENABLE_QUICK_EDIT_MODE and ENABLE_INSERT_MODE flags (presumably for
|
||||
backwards compatibility?). I studied the behavior on Windows 7 and Windows
|
||||
10, with both the old and new consoles, and I didn't see any differences
|
||||
between versions. Here's what I seemed to observe:
|
||||
|
||||
- The console has three flags internally:
|
||||
- QuickEdit
|
||||
- InsertMode
|
||||
- ExtendedFlags
|
||||
|
||||
- SetConsoleMode psuedocode:
|
||||
void SetConsoleMode(..., DWORD mode) {
|
||||
ExtendedFlags = (mode & (ENABLE_EXTENDED_FLAGS
|
||||
| ENABLE_QUICK_EDIT_MODE
|
||||
| ENABLE_INSERT_MODE )) != 0;
|
||||
if (ExtendedFlags) {
|
||||
QuickEdit = (mode & ENABLE_QUICK_EDIT_MODE) != 0;
|
||||
InsertMode = (mode & ENABLE_INSERT_MODE) != 0;
|
||||
}
|
||||
}
|
||||
|
||||
- Setting QuickEdit or InsertMode from the properties dialog GUI does not
|
||||
affect the ExtendedFlags setting -- it simply toggles the one flag.
|
||||
|
||||
- GetConsoleMode psuedocode:
|
||||
GetConsoleMode(..., DWORD *result) {
|
||||
if (ExtendedFlags) {
|
||||
*result |= ENABLE_EXTENDED_FLAGS;
|
||||
if (QuickEdit) { *result |= ENABLE_QUICK_EDIT_MODE; }
|
||||
if (InsertMode) { *result |= ENABLE_INSERT_MODE; }
|
||||
}
|
||||
}
|
||||
|
||||
Effectively, the ExtendedFlags flags controls whether the other two flags
|
||||
are visible/controlled by the user application. If they aren't visible,
|
||||
though, there is no way for the user application to make them visible,
|
||||
except by overwriting their values! Calling SetConsoleMode with just
|
||||
ENABLE_EXTENDED_FLAGS would clear the extended flags we want to read.
|
||||
|
||||
Consequently, if a program temporarily alters the QuickEdit flag (e.g. to
|
||||
enable mouse input), it cannot restore the original values of the QuickEdit
|
||||
and InsertMode flags, UNLESS every other console program cooperates by
|
||||
keeping the ExtendedFlags flag set.
|
||||
Generated
Vendored
+528
@@ -0,0 +1,528 @@
|
||||
==================================
|
||||
Code Page 437, Consolas font
|
||||
==================================
|
||||
|
||||
Options: -face "Consolas" -family 0x36
|
||||
Chars: A2 A3 2014 3044 30FC 4000
|
||||
|
||||
FontSurvey "-face \"Consolas\" -family 0x36"
|
||||
|
||||
Windows 7
|
||||
---------
|
||||
|
||||
Size 1: 1,3 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 1,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 2,5 BAD (HHHHHH)
|
||||
Size 6: 3,6 BAD (HHHHHH)
|
||||
Size 7: 3,6 BAD (HHHHHH)
|
||||
Size 8: 4,8 BAD (HHHHHH)
|
||||
Size 9: 4,9 BAD (HHHHHH)
|
||||
Size 10: 5,10 BAD (HHHHHH)
|
||||
Size 11: 5,11 BAD (HHHHHH)
|
||||
Size 12: 6,12 BAD (HHHHHH)
|
||||
Size 13: 6,13 BAD (HHHHHH)
|
||||
Size 14: 7,14 BAD (HHHHHH)
|
||||
Size 15: 7,15 BAD (HHHHHH)
|
||||
Size 16: 8,16 BAD (HHHHHH)
|
||||
Size 17: 8,17 BAD (HHHHHH)
|
||||
Size 18: 8,18 BAD (HHHHHH)
|
||||
Size 19: 9,19 BAD (HHHHHH)
|
||||
Size 20: 9,20 BAD (HHHHHH)
|
||||
Size 21: 10,22 BAD (HHHHHH)
|
||||
Size 22: 10,22 BAD (HHHHHH)
|
||||
Size 23: 11,23 BAD (HHHHHH)
|
||||
Size 24: 11,24 BAD (HHHHHH)
|
||||
Size 25: 12,25 BAD (HHHHHH)
|
||||
Size 26: 12,26 BAD (HHHHHH)
|
||||
Size 27: 13,27 BAD (HHHHHH)
|
||||
Size 28: 13,28 BAD (HHHHHH)
|
||||
Size 29: 14,29 BAD (HHHHHH)
|
||||
Size 30: 14,30 BAD (HHHHHH)
|
||||
Size 31: 15,31 BAD (HHHHHH)
|
||||
Size 32: 15,32 BAD (HHHHHH)
|
||||
Size 33: 15,33 BAD (HHHHHH)
|
||||
Size 34: 16,34 BAD (HHHHHH)
|
||||
Size 35: 16,36 BAD (HHHHHH)
|
||||
Size 36: 17,36 BAD (HHHHHH)
|
||||
Size 37: 17,37 BAD (HHHHHH)
|
||||
Size 38: 18,38 BAD (HHHHHH)
|
||||
Size 39: 18,39 BAD (HHHHHH)
|
||||
Size 40: 19,40 BAD (HHHHHH)
|
||||
Size 41: 19,41 BAD (HHHHHH)
|
||||
Size 42: 20,42 BAD (HHHHHH)
|
||||
Size 43: 20,43 BAD (HHHHHH)
|
||||
Size 44: 21,44 BAD (HHHHHH)
|
||||
Size 45: 21,45 BAD (HHHHHH)
|
||||
Size 46: 22,46 BAD (HHHHHH)
|
||||
Size 47: 22,47 BAD (HHHHHH)
|
||||
Size 48: 23,48 BAD (HHHHHH)
|
||||
Size 49: 23,49 BAD (HHHHHH)
|
||||
Size 50: 23,50 BAD (HHHHHH)
|
||||
Size 51: 24,51 BAD (HHHHHH)
|
||||
Size 52: 24,52 BAD (HHHHHH)
|
||||
Size 53: 25,53 BAD (HHHHHH)
|
||||
Size 54: 25,54 BAD (HHHHHH)
|
||||
Size 55: 26,55 BAD (HHHHHH)
|
||||
Size 56: 26,56 BAD (HHHHHH)
|
||||
Size 57: 27,57 BAD (HHHHHH)
|
||||
Size 58: 27,58 BAD (HHHHHH)
|
||||
Size 59: 28,59 BAD (HHHHHH)
|
||||
Size 60: 28,60 BAD (HHHHHH)
|
||||
Size 61: 29,61 BAD (HHHHHH)
|
||||
Size 62: 29,62 BAD (HHHHHH)
|
||||
Size 63: 30,64 BAD (HHHHHH)
|
||||
Size 64: 30,64 BAD (HHHHHH)
|
||||
Size 65: 31,65 BAD (HHHHHH)
|
||||
Size 66: 31,66 BAD (HHHHHH)
|
||||
Size 67: 31,67 BAD (HHHHHH)
|
||||
Size 68: 32,68 BAD (HHHHHH)
|
||||
Size 69: 32,69 BAD (HHHHHH)
|
||||
Size 70: 33,70 BAD (HHHHHH)
|
||||
Size 71: 33,71 BAD (HHHHHH)
|
||||
Size 72: 34,72 BAD (HHHHHH)
|
||||
Size 73: 34,73 BAD (HHHHHH)
|
||||
Size 74: 35,74 BAD (HHHHHH)
|
||||
Size 75: 35,75 BAD (HHHHHH)
|
||||
Size 76: 36,76 BAD (HHHHHH)
|
||||
Size 77: 36,77 BAD (HHHHHH)
|
||||
Size 78: 37,78 BAD (HHHHHH)
|
||||
Size 79: 37,79 BAD (HHHHHH)
|
||||
Size 80: 38,80 BAD (HHHHHH)
|
||||
Size 81: 38,81 BAD (HHHHHH)
|
||||
Size 82: 39,82 BAD (HHHHHH)
|
||||
Size 83: 39,83 BAD (HHHHHH)
|
||||
Size 84: 39,84 BAD (HHHHHH)
|
||||
Size 85: 40,85 BAD (HHHHHH)
|
||||
Size 86: 40,86 BAD (HHHHHH)
|
||||
Size 87: 41,87 BAD (HHHHHH)
|
||||
Size 88: 41,88 BAD (HHHHHH)
|
||||
Size 89: 42,89 BAD (HHHHHH)
|
||||
Size 90: 42,90 BAD (HHHHHH)
|
||||
Size 91: 43,91 BAD (HHHHHH)
|
||||
Size 92: 43,92 BAD (HHHHHH)
|
||||
Size 93: 44,93 BAD (HHHHHH)
|
||||
Size 94: 44,94 BAD (HHHHHH)
|
||||
Size 95: 45,95 BAD (HHHHHH)
|
||||
Size 96: 45,96 BAD (HHHHHH)
|
||||
Size 97: 46,97 BAD (HHHHHH)
|
||||
Size 98: 46,98 BAD (HHHHHH)
|
||||
Size 99: 46,99 BAD (HHHHHH)
|
||||
Size 100: 47,100 BAD (HHHHHH)
|
||||
|
||||
Windows 8
|
||||
---------
|
||||
|
||||
Size 1: 1,3 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 1,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 2,5 BAD (HHHHHH)
|
||||
Size 6: 3,6 BAD (HHHHHH)
|
||||
Size 7: 3,6 BAD (HHHHHH)
|
||||
Size 8: 4,8 BAD (HHHHHH)
|
||||
Size 9: 4,9 BAD (HHHHHH)
|
||||
Size 10: 5,10 BAD (HHHHHH)
|
||||
Size 11: 5,11 BAD (HHHHHH)
|
||||
Size 12: 6,12 BAD (HHHHHH)
|
||||
Size 13: 6,13 BAD (HHHHHH)
|
||||
Size 14: 7,14 BAD (HHHHHH)
|
||||
Size 15: 7,15 BAD (HHHHHH)
|
||||
Size 16: 8,16 BAD (HHHHHH)
|
||||
Size 17: 8,17 BAD (HHHHHH)
|
||||
Size 18: 8,18 BAD (HHHHHH)
|
||||
Size 19: 9,19 BAD (HHHHHH)
|
||||
Size 20: 9,20 BAD (HHHHHH)
|
||||
Size 21: 10,22 BAD (HHHHHH)
|
||||
Size 22: 10,22 BAD (HHHHHH)
|
||||
Size 23: 11,23 BAD (HHHHHH)
|
||||
Size 24: 11,24 BAD (HHHHHH)
|
||||
Size 25: 12,25 BAD (HHHHHH)
|
||||
Size 26: 12,26 BAD (HHHHHH)
|
||||
Size 27: 13,27 BAD (HHHHHH)
|
||||
Size 28: 13,28 BAD (HHHHHH)
|
||||
Size 29: 14,29 BAD (HHHHHH)
|
||||
Size 30: 14,30 BAD (HHHHHH)
|
||||
Size 31: 15,31 BAD (HHHHHH)
|
||||
Size 32: 15,32 BAD (HHHHHH)
|
||||
Size 33: 15,33 BAD (HHHHHH)
|
||||
Size 34: 16,34 BAD (HHHHHH)
|
||||
Size 35: 16,36 BAD (HHHHHH)
|
||||
Size 36: 17,36 BAD (HHHHHH)
|
||||
Size 37: 17,37 BAD (HHHHHH)
|
||||
Size 38: 18,38 BAD (HHHHHH)
|
||||
Size 39: 18,39 BAD (HHHHHH)
|
||||
Size 40: 19,40 BAD (HHHHHH)
|
||||
Size 41: 19,41 BAD (HHHHHH)
|
||||
Size 42: 20,42 BAD (HHHHHH)
|
||||
Size 43: 20,43 BAD (HHHHHH)
|
||||
Size 44: 21,44 BAD (HHHHHH)
|
||||
Size 45: 21,45 BAD (HHHHHH)
|
||||
Size 46: 22,46 BAD (HHHHHH)
|
||||
Size 47: 22,47 BAD (HHHHHH)
|
||||
Size 48: 23,48 BAD (HHHHHH)
|
||||
Size 49: 23,49 BAD (HHHHHH)
|
||||
Size 50: 23,50 BAD (HHHHHH)
|
||||
Size 51: 24,51 BAD (HHHHHH)
|
||||
Size 52: 24,52 BAD (HHHHHH)
|
||||
Size 53: 25,53 BAD (HHHHHH)
|
||||
Size 54: 25,54 BAD (HHHHHH)
|
||||
Size 55: 26,55 BAD (HHHHHH)
|
||||
Size 56: 26,56 BAD (HHHHHH)
|
||||
Size 57: 27,57 BAD (HHHHHH)
|
||||
Size 58: 27,58 BAD (HHHHHH)
|
||||
Size 59: 28,59 BAD (HHHHHH)
|
||||
Size 60: 28,60 BAD (HHHHHH)
|
||||
Size 61: 29,61 BAD (HHHHHH)
|
||||
Size 62: 29,62 BAD (HHHHHH)
|
||||
Size 63: 30,64 BAD (HHHHHH)
|
||||
Size 64: 30,64 BAD (HHHHHH)
|
||||
Size 65: 31,65 BAD (HHHHHH)
|
||||
Size 66: 31,66 BAD (HHHHHH)
|
||||
Size 67: 31,67 BAD (HHHHHH)
|
||||
Size 68: 32,68 BAD (HHHHHH)
|
||||
Size 69: 32,69 BAD (HHHHHH)
|
||||
Size 70: 33,70 BAD (HHHHHH)
|
||||
Size 71: 33,71 BAD (HHHHHH)
|
||||
Size 72: 34,72 BAD (HHHHHH)
|
||||
Size 73: 34,73 BAD (HHHHHH)
|
||||
Size 74: 35,74 BAD (HHHHHH)
|
||||
Size 75: 35,75 BAD (HHHHHH)
|
||||
Size 76: 36,76 BAD (HHHHHH)
|
||||
Size 77: 36,77 BAD (HHHHHH)
|
||||
Size 78: 37,78 BAD (HHHHHH)
|
||||
Size 79: 37,79 BAD (HHHHHH)
|
||||
Size 80: 38,80 BAD (HHHHHH)
|
||||
Size 81: 38,81 BAD (HHHHHH)
|
||||
Size 82: 39,82 BAD (HHHHHH)
|
||||
Size 83: 39,83 BAD (HHHHHH)
|
||||
Size 84: 39,84 BAD (HHHHHH)
|
||||
Size 85: 40,85 BAD (HHHHHH)
|
||||
Size 86: 40,86 BAD (HHHHHH)
|
||||
Size 87: 41,87 BAD (HHHHHH)
|
||||
Size 88: 41,88 BAD (HHHHHH)
|
||||
Size 89: 42,89 BAD (HHHHHH)
|
||||
Size 90: 42,90 BAD (HHHHHH)
|
||||
Size 91: 43,91 BAD (HHHHHH)
|
||||
Size 92: 43,92 BAD (HHHHHH)
|
||||
Size 93: 44,93 BAD (HHHHHH)
|
||||
Size 94: 44,94 BAD (HHHHHH)
|
||||
Size 95: 45,95 BAD (HHHHHH)
|
||||
Size 96: 45,96 BAD (HHHHHH)
|
||||
Size 97: 46,97 BAD (HHHHHH)
|
||||
Size 98: 46,98 BAD (HHHHHH)
|
||||
Size 99: 46,99 BAD (HHHHHH)
|
||||
Size 100: 47,100 BAD (HHHHHH)
|
||||
|
||||
Windows 8.1
|
||||
-----------
|
||||
|
||||
Size 1: 1,3 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 1,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 2,5 BAD (HHHHHH)
|
||||
Size 6: 3,6 BAD (HHHHHH)
|
||||
Size 7: 3,6 BAD (HHHHHH)
|
||||
Size 8: 4,8 BAD (HHHHHH)
|
||||
Size 9: 4,9 BAD (HHHHHH)
|
||||
Size 10: 5,10 BAD (HHHHHH)
|
||||
Size 11: 5,11 BAD (HHHHHH)
|
||||
Size 12: 6,12 BAD (HHHHHH)
|
||||
Size 13: 6,13 BAD (HHHHHH)
|
||||
Size 14: 7,14 BAD (HHHHHH)
|
||||
Size 15: 7,15 BAD (HHHHHH)
|
||||
Size 16: 8,16 BAD (HHHHHH)
|
||||
Size 17: 8,17 BAD (HHHHHH)
|
||||
Size 18: 8,18 BAD (HHHHHH)
|
||||
Size 19: 9,19 BAD (HHHHHH)
|
||||
Size 20: 9,20 BAD (HHHHHH)
|
||||
Size 21: 10,22 BAD (HHHHHH)
|
||||
Size 22: 10,22 BAD (HHHHHH)
|
||||
Size 23: 11,23 BAD (HHHHHH)
|
||||
Size 24: 11,24 BAD (HHHHHH)
|
||||
Size 25: 12,25 BAD (HHHHHH)
|
||||
Size 26: 12,26 BAD (HHHHHH)
|
||||
Size 27: 13,27 BAD (HHHHHH)
|
||||
Size 28: 13,28 BAD (HHHHHH)
|
||||
Size 29: 14,29 BAD (HHHHHH)
|
||||
Size 30: 14,30 BAD (HHHHHH)
|
||||
Size 31: 15,31 BAD (HHHHHH)
|
||||
Size 32: 15,32 BAD (HHHHHH)
|
||||
Size 33: 15,33 BAD (HHHHHH)
|
||||
Size 34: 16,34 BAD (HHHHHH)
|
||||
Size 35: 16,36 BAD (HHHHHH)
|
||||
Size 36: 17,36 BAD (HHHHHH)
|
||||
Size 37: 17,37 BAD (HHHHHH)
|
||||
Size 38: 18,38 BAD (HHHHHH)
|
||||
Size 39: 18,39 BAD (HHHHHH)
|
||||
Size 40: 19,40 BAD (HHHHHH)
|
||||
Size 41: 19,41 BAD (HHHHHH)
|
||||
Size 42: 20,42 BAD (HHHHHH)
|
||||
Size 43: 20,43 BAD (HHHHHH)
|
||||
Size 44: 21,44 BAD (HHHHHH)
|
||||
Size 45: 21,45 BAD (HHHHHH)
|
||||
Size 46: 22,46 BAD (HHHHHH)
|
||||
Size 47: 22,47 BAD (HHHHHH)
|
||||
Size 48: 23,48 BAD (HHHHHH)
|
||||
Size 49: 23,49 BAD (HHHHHH)
|
||||
Size 50: 23,50 BAD (HHHHHH)
|
||||
Size 51: 24,51 BAD (HHHHHH)
|
||||
Size 52: 24,52 BAD (HHHHHH)
|
||||
Size 53: 25,53 BAD (HHHHHH)
|
||||
Size 54: 25,54 BAD (HHHHHH)
|
||||
Size 55: 26,55 BAD (HHHHHH)
|
||||
Size 56: 26,56 BAD (HHHHHH)
|
||||
Size 57: 27,57 BAD (HHHHHH)
|
||||
Size 58: 27,58 BAD (HHHHHH)
|
||||
Size 59: 28,59 BAD (HHHHHH)
|
||||
Size 60: 28,60 BAD (HHHHHH)
|
||||
Size 61: 29,61 BAD (HHHHHH)
|
||||
Size 62: 29,62 BAD (HHHHHH)
|
||||
Size 63: 30,64 BAD (HHHHHH)
|
||||
Size 64: 30,64 BAD (HHHHHH)
|
||||
Size 65: 31,65 BAD (HHHHHH)
|
||||
Size 66: 31,66 BAD (HHHHHH)
|
||||
Size 67: 31,67 BAD (HHHHHH)
|
||||
Size 68: 32,68 BAD (HHHHHH)
|
||||
Size 69: 32,69 BAD (HHHHHH)
|
||||
Size 70: 33,70 BAD (HHHHHH)
|
||||
Size 71: 33,71 BAD (HHHHHH)
|
||||
Size 72: 34,72 BAD (HHHHHH)
|
||||
Size 73: 34,73 BAD (HHHHHH)
|
||||
Size 74: 35,74 BAD (HHHHHH)
|
||||
Size 75: 35,75 BAD (HHHHHH)
|
||||
Size 76: 36,76 BAD (HHHHHH)
|
||||
Size 77: 36,77 BAD (HHHHHH)
|
||||
Size 78: 37,78 BAD (HHHHHH)
|
||||
Size 79: 37,79 BAD (HHHHHH)
|
||||
Size 80: 38,80 BAD (HHHHHH)
|
||||
Size 81: 38,81 BAD (HHHHHH)
|
||||
Size 82: 39,82 BAD (HHHHHH)
|
||||
Size 83: 39,83 BAD (HHHHHH)
|
||||
Size 84: 39,84 BAD (HHHHHH)
|
||||
Size 85: 40,85 BAD (HHHHHH)
|
||||
Size 86: 40,86 BAD (HHHHHH)
|
||||
Size 87: 41,87 BAD (HHHHHH)
|
||||
Size 88: 41,88 BAD (HHHHHH)
|
||||
Size 89: 42,89 BAD (HHHHHH)
|
||||
Size 90: 42,90 BAD (HHHHHH)
|
||||
Size 91: 43,91 BAD (HHHHHH)
|
||||
Size 92: 43,92 BAD (HHHHHH)
|
||||
Size 93: 44,93 BAD (HHHHHH)
|
||||
Size 94: 44,94 BAD (HHHHHH)
|
||||
Size 95: 45,95 BAD (HHHHHH)
|
||||
Size 96: 45,96 BAD (HHHHHH)
|
||||
Size 97: 46,97 BAD (HHHHHH)
|
||||
Size 98: 46,98 BAD (HHHHHH)
|
||||
Size 99: 46,99 BAD (HHHHHH)
|
||||
Size 100: 47,100 BAD (HHHHHH)
|
||||
|
||||
Windows 10 14342 Old Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,3 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 1,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 2,5 BAD (HHHHHH)
|
||||
Size 6: 3,6 BAD (HHHHHH)
|
||||
Size 7: 3,6 BAD (HHHHHH)
|
||||
Size 8: 4,8 BAD (HHHHHH)
|
||||
Size 9: 4,9 BAD (HHHHHH)
|
||||
Size 10: 5,10 BAD (HHHHHH)
|
||||
Size 11: 5,11 BAD (HHHHHH)
|
||||
Size 12: 6,12 BAD (HHHHHH)
|
||||
Size 13: 6,13 BAD (HHHHHH)
|
||||
Size 14: 7,14 BAD (HHHHHH)
|
||||
Size 15: 7,15 BAD (HHHHHH)
|
||||
Size 16: 8,16 BAD (HHHHHH)
|
||||
Size 17: 8,17 BAD (HHHHHH)
|
||||
Size 18: 8,18 BAD (HHHHHH)
|
||||
Size 19: 9,19 BAD (HHHHHH)
|
||||
Size 20: 9,20 BAD (HHHHHH)
|
||||
Size 21: 10,22 BAD (HHHHHH)
|
||||
Size 22: 10,22 BAD (HHHHHH)
|
||||
Size 23: 11,23 BAD (HHHHHH)
|
||||
Size 24: 11,24 BAD (HHHHHH)
|
||||
Size 25: 12,25 BAD (HHHHHH)
|
||||
Size 26: 12,26 BAD (HHHHHH)
|
||||
Size 27: 13,27 BAD (HHHHHH)
|
||||
Size 28: 13,28 BAD (HHHHHH)
|
||||
Size 29: 14,29 BAD (HHHHHH)
|
||||
Size 30: 14,30 BAD (HHHHHH)
|
||||
Size 31: 15,31 BAD (HHHHHH)
|
||||
Size 32: 15,32 BAD (HHHHHH)
|
||||
Size 33: 15,33 BAD (HHHHHH)
|
||||
Size 34: 16,34 BAD (HHHHHH)
|
||||
Size 35: 16,36 BAD (HHHHHH)
|
||||
Size 36: 17,36 BAD (HHHHHH)
|
||||
Size 37: 17,37 BAD (HHHHHH)
|
||||
Size 38: 18,38 BAD (HHHHHH)
|
||||
Size 39: 18,39 BAD (HHHHHH)
|
||||
Size 40: 19,40 BAD (HHHHHH)
|
||||
Size 41: 19,41 BAD (HHHHHH)
|
||||
Size 42: 20,42 BAD (HHHHHH)
|
||||
Size 43: 20,43 BAD (HHHHHH)
|
||||
Size 44: 21,44 BAD (HHHHHH)
|
||||
Size 45: 21,45 BAD (HHHHHH)
|
||||
Size 46: 22,46 BAD (HHHHHH)
|
||||
Size 47: 22,47 BAD (HHHHHH)
|
||||
Size 48: 23,48 BAD (HHHHHH)
|
||||
Size 49: 23,49 BAD (HHHHHH)
|
||||
Size 50: 23,50 BAD (HHHHHH)
|
||||
Size 51: 24,51 BAD (HHHHHH)
|
||||
Size 52: 24,52 BAD (HHHHHH)
|
||||
Size 53: 25,53 BAD (HHHHHH)
|
||||
Size 54: 25,54 BAD (HHHHHH)
|
||||
Size 55: 26,55 BAD (HHHHHH)
|
||||
Size 56: 26,56 BAD (HHHHHH)
|
||||
Size 57: 27,57 BAD (HHHHHH)
|
||||
Size 58: 27,58 BAD (HHHHHH)
|
||||
Size 59: 28,59 BAD (HHHHHH)
|
||||
Size 60: 28,60 BAD (HHHHHH)
|
||||
Size 61: 29,61 BAD (HHHHHH)
|
||||
Size 62: 29,62 BAD (HHHHHH)
|
||||
Size 63: 30,64 BAD (HHHHHH)
|
||||
Size 64: 30,64 BAD (HHHHHH)
|
||||
Size 65: 31,65 BAD (HHHHHH)
|
||||
Size 66: 31,66 BAD (HHHHHH)
|
||||
Size 67: 31,67 BAD (HHHHHH)
|
||||
Size 68: 32,68 BAD (HHHHHH)
|
||||
Size 69: 32,69 BAD (HHHHHH)
|
||||
Size 70: 33,70 BAD (HHHHHH)
|
||||
Size 71: 33,71 BAD (HHHHHH)
|
||||
Size 72: 34,72 BAD (HHHHHH)
|
||||
Size 73: 34,73 BAD (HHHHHH)
|
||||
Size 74: 35,74 BAD (HHHHHH)
|
||||
Size 75: 35,75 BAD (HHHHHH)
|
||||
Size 76: 36,76 BAD (HHHHHH)
|
||||
Size 77: 36,77 BAD (HHHHHH)
|
||||
Size 78: 37,78 BAD (HHHHHH)
|
||||
Size 79: 37,79 BAD (HHHHHH)
|
||||
Size 80: 38,80 BAD (HHHHHH)
|
||||
Size 81: 38,81 BAD (HHHHHH)
|
||||
Size 82: 39,82 BAD (HHHHHH)
|
||||
Size 83: 39,83 BAD (HHHHHH)
|
||||
Size 84: 39,84 BAD (HHHHHH)
|
||||
Size 85: 40,85 BAD (HHHHHH)
|
||||
Size 86: 40,86 BAD (HHHHHH)
|
||||
Size 87: 41,87 BAD (HHHHHH)
|
||||
Size 88: 41,88 BAD (HHHHHH)
|
||||
Size 89: 42,89 BAD (HHHHHH)
|
||||
Size 90: 42,90 BAD (HHHHHH)
|
||||
Size 91: 43,91 BAD (HHHHHH)
|
||||
Size 92: 43,92 BAD (HHHHHH)
|
||||
Size 93: 44,93 BAD (HHHHHH)
|
||||
Size 94: 44,94 BAD (HHHHHH)
|
||||
Size 95: 45,95 BAD (HHHHHH)
|
||||
Size 96: 45,96 BAD (HHHHHH)
|
||||
Size 97: 46,97 BAD (HHHHHH)
|
||||
Size 98: 46,98 BAD (HHHHHH)
|
||||
Size 99: 46,99 BAD (HHHHHH)
|
||||
Size 100: 47,100 BAD (HHHHHH)
|
||||
|
||||
Windows 10 14342 New Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,1 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 1,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 2,5 BAD (HHHHHH)
|
||||
Size 6: 3,6 BAD (HHHHHH)
|
||||
Size 7: 3,7 BAD (HHHHHH)
|
||||
Size 8: 4,8 BAD (HHHHHH)
|
||||
Size 9: 4,9 BAD (HHHHHH)
|
||||
Size 10: 5,10 BAD (HHHHHH)
|
||||
Size 11: 5,11 BAD (HHHHHH)
|
||||
Size 12: 6,12 BAD (HHHHHH)
|
||||
Size 13: 6,13 BAD (HHHHHH)
|
||||
Size 14: 7,14 BAD (HHHHHH)
|
||||
Size 15: 7,15 BAD (HHHHHH)
|
||||
Size 16: 8,16 BAD (HHHHHH)
|
||||
Size 17: 8,17 BAD (HHHHHH)
|
||||
Size 18: 8,18 BAD (HHHHHH)
|
||||
Size 19: 9,19 BAD (HHHHHH)
|
||||
Size 20: 9,20 BAD (HHHHHH)
|
||||
Size 21: 10,21 BAD (HHHHHH)
|
||||
Size 22: 10,22 BAD (HHHHHH)
|
||||
Size 23: 11,23 BAD (HHHHHH)
|
||||
Size 24: 11,24 BAD (HHHHHH)
|
||||
Size 25: 12,25 BAD (HHHHHH)
|
||||
Size 26: 12,26 BAD (HHHHHH)
|
||||
Size 27: 13,27 BAD (HHHHHH)
|
||||
Size 28: 13,28 BAD (HHHHHH)
|
||||
Size 29: 14,29 BAD (HHHHHH)
|
||||
Size 30: 14,30 BAD (HHHHHH)
|
||||
Size 31: 15,31 BAD (HHHHHH)
|
||||
Size 32: 15,32 BAD (HHHHHH)
|
||||
Size 33: 15,33 BAD (HHHHHH)
|
||||
Size 34: 16,34 BAD (HHHHHH)
|
||||
Size 35: 16,35 BAD (HHHHHH)
|
||||
Size 36: 17,36 BAD (HHHHHH)
|
||||
Size 37: 17,37 BAD (HHHHHH)
|
||||
Size 38: 18,38 BAD (HHHHHH)
|
||||
Size 39: 18,39 BAD (HHHHHH)
|
||||
Size 40: 19,40 BAD (HHHHHH)
|
||||
Size 41: 19,41 BAD (HHHHHH)
|
||||
Size 42: 20,42 BAD (HHHHHH)
|
||||
Size 43: 20,43 BAD (HHHHHH)
|
||||
Size 44: 21,44 BAD (HHHHHH)
|
||||
Size 45: 21,45 BAD (HHHHHH)
|
||||
Size 46: 22,46 BAD (HHHHHH)
|
||||
Size 47: 22,47 BAD (HHHHHH)
|
||||
Size 48: 23,48 BAD (HHHHHH)
|
||||
Size 49: 23,49 BAD (HHHHHH)
|
||||
Size 50: 23,50 BAD (HHHHHH)
|
||||
Size 51: 24,51 BAD (HHHHHH)
|
||||
Size 52: 24,52 BAD (HHHHHH)
|
||||
Size 53: 25,53 BAD (HHHHHH)
|
||||
Size 54: 25,54 BAD (HHHHHH)
|
||||
Size 55: 26,55 BAD (HHHHHH)
|
||||
Size 56: 26,56 BAD (HHHHHH)
|
||||
Size 57: 27,57 BAD (HHHHHH)
|
||||
Size 58: 27,58 BAD (HHHHHH)
|
||||
Size 59: 28,59 BAD (HHHHHH)
|
||||
Size 60: 28,60 BAD (HHHHHH)
|
||||
Size 61: 29,61 BAD (HHHHHH)
|
||||
Size 62: 29,62 BAD (HHHHHH)
|
||||
Size 63: 30,63 BAD (HHHHHH)
|
||||
Size 64: 30,64 BAD (HHHHHH)
|
||||
Size 65: 31,65 BAD (HHHHHH)
|
||||
Size 66: 31,66 BAD (HHHHHH)
|
||||
Size 67: 31,67 BAD (HHHHHH)
|
||||
Size 68: 32,68 BAD (HHHHHH)
|
||||
Size 69: 32,69 BAD (HHHHHH)
|
||||
Size 70: 33,70 BAD (HHHHHH)
|
||||
Size 71: 33,71 BAD (HHHHHH)
|
||||
Size 72: 34,72 BAD (HHHHHH)
|
||||
Size 73: 34,73 BAD (HHHHHH)
|
||||
Size 74: 35,74 BAD (HHHHHH)
|
||||
Size 75: 35,75 BAD (HHHHHH)
|
||||
Size 76: 36,76 BAD (HHHHHH)
|
||||
Size 77: 36,77 BAD (HHHHHH)
|
||||
Size 78: 37,78 BAD (HHHHHH)
|
||||
Size 79: 37,79 BAD (HHHHHH)
|
||||
Size 80: 38,80 BAD (HHHHHH)
|
||||
Size 81: 38,81 BAD (HHHHHH)
|
||||
Size 82: 39,82 BAD (HHHHHH)
|
||||
Size 83: 39,83 BAD (HHHHHH)
|
||||
Size 84: 39,84 BAD (HHHHHH)
|
||||
Size 85: 40,85 BAD (HHHHHH)
|
||||
Size 86: 40,86 BAD (HHHHHH)
|
||||
Size 87: 41,87 BAD (HHHHHH)
|
||||
Size 88: 41,88 BAD (HHHHHH)
|
||||
Size 89: 42,89 BAD (HHHHHH)
|
||||
Size 90: 42,90 BAD (HHHHHH)
|
||||
Size 91: 43,91 BAD (HHHHHH)
|
||||
Size 92: 43,92 BAD (HHHHHH)
|
||||
Size 93: 44,93 BAD (HHHHHH)
|
||||
Size 94: 44,94 BAD (HHHHHH)
|
||||
Size 95: 45,95 BAD (HHHHHH)
|
||||
Size 96: 45,96 BAD (HHHHHH)
|
||||
Size 97: 46,97 BAD (HHHHHH)
|
||||
Size 98: 46,98 BAD (HHHHHH)
|
||||
Size 99: 46,99 BAD (HHHHHH)
|
||||
Size 100: 47,100 BAD (HHHHHH)
|
||||
Generated
Vendored
+633
@@ -0,0 +1,633 @@
|
||||
==================================
|
||||
Code Page 437, Lucida Console font
|
||||
==================================
|
||||
|
||||
Options: -face "Lucida Console" -family 0x36
|
||||
Chars: A2 A3 2014 3044 30FC 4000
|
||||
|
||||
FontSurvey "-face \"Lucida Console\" -family 0x36"
|
||||
|
||||
Vista
|
||||
-----
|
||||
|
||||
Size 1: 1,2 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 2,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 3,5 BAD (HHHHHH)
|
||||
Size 6: 4,6 BAD (HHHHHH)
|
||||
Size 7: 4,7 BAD (HHHHHH)
|
||||
Size 8: 5,8 BAD (HHHHHH)
|
||||
Size 9: 5,9 BAD (HHHHHH)
|
||||
Size 10: 6,10 BAD (HHHHHH)
|
||||
Size 11: 7,11 BAD (HHHHHH)
|
||||
Size 12: 7,12 BAD (HHHHHH)
|
||||
Size 13: 8,13 BAD (HHHHHH)
|
||||
Size 14: 8,14 BAD (HHHHHH)
|
||||
Size 15: 9,15 BAD (HHHHHH)
|
||||
Size 16: 10,16 BAD (HHHHHH)
|
||||
Size 17: 10,17 BAD (HHHHHH)
|
||||
Size 18: 11,18 BAD (HHHHHH)
|
||||
Size 19: 11,19 BAD (HHHHHH)
|
||||
Size 20: 12,20 BAD (HHHHHH)
|
||||
Size 21: 13,21 BAD (HHHHHH)
|
||||
Size 22: 13,22 BAD (HHHHHH)
|
||||
Size 23: 14,23 BAD (HHHHHH)
|
||||
Size 24: 14,24 BAD (HHHHHH)
|
||||
Size 25: 15,25 BAD (HHHHHH)
|
||||
Size 26: 16,26 BAD (HHHHHH)
|
||||
Size 27: 16,27 BAD (HHHHHH)
|
||||
Size 28: 17,28 BAD (HHHHHH)
|
||||
Size 29: 17,29 BAD (HHHHHH)
|
||||
Size 30: 18,30 BAD (HHHHHH)
|
||||
Size 31: 19,31 BAD (HHHHHH)
|
||||
Size 32: 19,32 BAD (HHHHHH)
|
||||
Size 33: 20,33 BAD (HHHHHH)
|
||||
Size 34: 20,34 BAD (HHHHHH)
|
||||
Size 35: 21,35 BAD (HHHHHH)
|
||||
Size 36: 22,36 BAD (HHHHHH)
|
||||
Size 37: 22,37 BAD (HHHHHH)
|
||||
Size 38: 23,38 BAD (HHHHHH)
|
||||
Size 39: 23,39 BAD (HHHHHH)
|
||||
Size 40: 24,40 BAD (HHHHHH)
|
||||
Size 41: 25,41 BAD (HHHHHH)
|
||||
Size 42: 25,42 BAD (HHHHHH)
|
||||
Size 43: 26,43 BAD (HHHHHH)
|
||||
Size 44: 27,44 BAD (HHHHHH)
|
||||
Size 45: 27,45 BAD (HHHHHH)
|
||||
Size 46: 28,46 BAD (HHHHHH)
|
||||
Size 47: 28,47 BAD (HHHHHH)
|
||||
Size 48: 29,48 BAD (HHHHHH)
|
||||
Size 49: 30,49 BAD (HHHHHH)
|
||||
Size 50: 30,50 BAD (HHHHHH)
|
||||
Size 51: 31,51 BAD (HHHHHH)
|
||||
Size 52: 31,52 BAD (HHHHHH)
|
||||
Size 53: 32,53 BAD (HHHHHH)
|
||||
Size 54: 33,54 BAD (HHHHHH)
|
||||
Size 55: 33,55 BAD (HHHHHH)
|
||||
Size 56: 34,56 BAD (HHHHHH)
|
||||
Size 57: 34,57 BAD (HHHHHH)
|
||||
Size 58: 35,58 BAD (HHHHHH)
|
||||
Size 59: 36,59 BAD (HHHHHH)
|
||||
Size 60: 36,60 BAD (HHHHHH)
|
||||
Size 61: 37,61 BAD (HHHHHH)
|
||||
Size 62: 37,62 BAD (HHHHHH)
|
||||
Size 63: 38,63 BAD (HHHHHH)
|
||||
Size 64: 39,65 BAD (HHHHHH)
|
||||
Size 65: 39,65 BAD (HHHHHH)
|
||||
Size 66: 40,66 BAD (HHHHHH)
|
||||
Size 67: 40,67 BAD (HHHHHH)
|
||||
Size 68: 41,68 BAD (HHHHHH)
|
||||
Size 69: 42,69 BAD (HHHHHH)
|
||||
Size 70: 42,70 BAD (HHHHHH)
|
||||
Size 71: 43,71 BAD (HHHHHH)
|
||||
Size 72: 43,72 BAD (HHHHHH)
|
||||
Size 73: 44,73 BAD (HHHHHH)
|
||||
Size 74: 45,74 BAD (HHHHHH)
|
||||
Size 75: 45,75 BAD (HHHHHH)
|
||||
Size 76: 46,76 BAD (HHHHHH)
|
||||
Size 77: 46,77 BAD (HHHHHH)
|
||||
Size 78: 47,78 BAD (HHHHHH)
|
||||
Size 79: 48,79 BAD (HHHHHH)
|
||||
Size 80: 48,80 BAD (HHHHHH)
|
||||
Size 81: 49,81 BAD (HHHHHH)
|
||||
Size 82: 49,82 BAD (HHHHHH)
|
||||
Size 83: 50,83 BAD (HHHHHH)
|
||||
Size 84: 51,84 BAD (HHHHHH)
|
||||
Size 85: 51,85 BAD (HHHHHH)
|
||||
Size 86: 52,86 BAD (HHHHHH)
|
||||
Size 87: 52,87 BAD (HHHHHH)
|
||||
Size 88: 53,88 BAD (HHHHHH)
|
||||
Size 89: 54,89 BAD (HHHHHH)
|
||||
Size 90: 54,90 BAD (HHHHHH)
|
||||
Size 91: 55,91 BAD (HHHHHH)
|
||||
Size 92: 55,92 BAD (HHHHHH)
|
||||
Size 93: 56,93 BAD (HHHHHH)
|
||||
Size 94: 57,94 BAD (HHHHHH)
|
||||
Size 95: 57,95 BAD (HHHHHH)
|
||||
Size 96: 58,96 BAD (HHHHHH)
|
||||
Size 97: 58,97 BAD (HHHHHH)
|
||||
Size 98: 59,98 BAD (HHHHHH)
|
||||
Size 99: 60,99 BAD (HHHHHH)
|
||||
Size 100: 60,100 BAD (HHHHHH)
|
||||
|
||||
|
||||
Windows 7
|
||||
---------
|
||||
|
||||
Size 1: 1,2 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 2,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 3,5 BAD (HHHHHH)
|
||||
Size 6: 4,6 BAD (HHHHHH)
|
||||
Size 7: 4,7 BAD (HHHHHH)
|
||||
Size 8: 5,8 BAD (HHHHHH)
|
||||
Size 9: 5,9 BAD (HHHHHH)
|
||||
Size 10: 6,10 BAD (HHHHHH)
|
||||
Size 11: 7,11 BAD (HHHHHH)
|
||||
Size 12: 7,12 BAD (HHHHHH)
|
||||
Size 13: 8,13 BAD (HHHHHH)
|
||||
Size 14: 8,14 BAD (HHHHHH)
|
||||
Size 15: 9,15 BAD (HHHHHH)
|
||||
Size 16: 10,16 BAD (HHHHHH)
|
||||
Size 17: 10,17 BAD (HHHHHH)
|
||||
Size 18: 11,18 BAD (HHHHHH)
|
||||
Size 19: 11,19 BAD (HHHHHH)
|
||||
Size 20: 12,20 BAD (HHHHHH)
|
||||
Size 21: 13,21 BAD (HHHHHH)
|
||||
Size 22: 13,22 BAD (HHHHHH)
|
||||
Size 23: 14,23 BAD (HHHHHH)
|
||||
Size 24: 14,24 BAD (HHHHHH)
|
||||
Size 25: 15,25 BAD (HHHHHH)
|
||||
Size 26: 16,26 BAD (HHHHHH)
|
||||
Size 27: 16,27 BAD (HHHHHH)
|
||||
Size 28: 17,28 BAD (HHHHHH)
|
||||
Size 29: 17,29 BAD (HHHHHH)
|
||||
Size 30: 18,30 BAD (HHHHHH)
|
||||
Size 31: 19,31 BAD (HHHHHH)
|
||||
Size 32: 19,32 BAD (HHHHHH)
|
||||
Size 33: 20,33 BAD (HHHHHH)
|
||||
Size 34: 20,34 BAD (HHHHHH)
|
||||
Size 35: 21,35 BAD (HHHHHH)
|
||||
Size 36: 22,36 BAD (HHHHHH)
|
||||
Size 37: 22,37 BAD (HHHHHH)
|
||||
Size 38: 23,38 BAD (HHHHHH)
|
||||
Size 39: 23,39 BAD (HHHHHH)
|
||||
Size 40: 24,40 BAD (HHHHHH)
|
||||
Size 41: 25,41 BAD (HHHHHH)
|
||||
Size 42: 25,42 BAD (HHHHHH)
|
||||
Size 43: 26,43 BAD (HHHHHH)
|
||||
Size 44: 27,44 BAD (HHHHHH)
|
||||
Size 45: 27,45 BAD (HHHHHH)
|
||||
Size 46: 28,46 BAD (HHHHHH)
|
||||
Size 47: 28,47 BAD (HHHHHH)
|
||||
Size 48: 29,48 BAD (HHHHHH)
|
||||
Size 49: 30,49 BAD (HHHHHH)
|
||||
Size 50: 30,50 BAD (HHHHHH)
|
||||
Size 51: 31,51 BAD (HHHHHH)
|
||||
Size 52: 31,52 BAD (HHHHHH)
|
||||
Size 53: 32,53 BAD (HHHHHH)
|
||||
Size 54: 33,54 BAD (HHHHHH)
|
||||
Size 55: 33,55 BAD (HHHHHH)
|
||||
Size 56: 34,56 BAD (HHHHHH)
|
||||
Size 57: 34,57 BAD (HHHHHH)
|
||||
Size 58: 35,58 BAD (HHHHHH)
|
||||
Size 59: 36,59 BAD (HHHHHH)
|
||||
Size 60: 36,60 BAD (HHHHHH)
|
||||
Size 61: 37,61 BAD (HHHHHH)
|
||||
Size 62: 37,62 BAD (HHHHHH)
|
||||
Size 63: 38,63 BAD (HHHHHH)
|
||||
Size 64: 39,65 BAD (HHHHHH)
|
||||
Size 65: 39,65 BAD (HHHHHH)
|
||||
Size 66: 40,66 BAD (HHHHHH)
|
||||
Size 67: 40,67 BAD (HHHHHH)
|
||||
Size 68: 41,68 BAD (HHHHHH)
|
||||
Size 69: 42,69 BAD (HHHHHH)
|
||||
Size 70: 42,70 BAD (HHHHHH)
|
||||
Size 71: 43,71 BAD (HHHHHH)
|
||||
Size 72: 43,72 BAD (HHHHHH)
|
||||
Size 73: 44,73 BAD (HHHHHH)
|
||||
Size 74: 45,74 BAD (HHHHHH)
|
||||
Size 75: 45,75 BAD (HHHHHH)
|
||||
Size 76: 46,76 BAD (HHHHHH)
|
||||
Size 77: 46,77 BAD (HHHHHH)
|
||||
Size 78: 47,78 BAD (HHHHHH)
|
||||
Size 79: 48,79 BAD (HHHHHH)
|
||||
Size 80: 48,80 BAD (HHHHHH)
|
||||
Size 81: 49,81 BAD (HHHHHH)
|
||||
Size 82: 49,82 BAD (HHHHHH)
|
||||
Size 83: 50,83 BAD (HHHHHH)
|
||||
Size 84: 51,84 BAD (HHHHHH)
|
||||
Size 85: 51,85 BAD (HHHHHH)
|
||||
Size 86: 52,86 BAD (HHHHHH)
|
||||
Size 87: 52,87 BAD (HHHHHH)
|
||||
Size 88: 53,88 BAD (HHHHHH)
|
||||
Size 89: 54,89 BAD (HHHHHH)
|
||||
Size 90: 54,90 BAD (HHHHHH)
|
||||
Size 91: 55,91 BAD (HHHHHH)
|
||||
Size 92: 55,92 BAD (HHHHHH)
|
||||
Size 93: 56,93 BAD (HHHHHH)
|
||||
Size 94: 57,94 BAD (HHHHHH)
|
||||
Size 95: 57,95 BAD (HHHHHH)
|
||||
Size 96: 58,96 BAD (HHHHHH)
|
||||
Size 97: 58,97 BAD (HHHHHH)
|
||||
Size 98: 59,98 BAD (HHHHHH)
|
||||
Size 99: 60,99 BAD (HHHHHH)
|
||||
Size 100: 60,100 BAD (HHHHHH)
|
||||
|
||||
Windows 8
|
||||
---------
|
||||
|
||||
Size 1: 1,2 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 2,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 3,5 BAD (HHHHHH)
|
||||
Size 6: 4,6 BAD (HHHHHH)
|
||||
Size 7: 4,7 BAD (HHHHHH)
|
||||
Size 8: 5,8 BAD (HHHHHH)
|
||||
Size 9: 5,9 BAD (HHHHHH)
|
||||
Size 10: 6,10 BAD (HHHHHH)
|
||||
Size 11: 7,11 BAD (HHHHHH)
|
||||
Size 12: 7,12 BAD (HHHHHH)
|
||||
Size 13: 8,13 BAD (HHHHHH)
|
||||
Size 14: 8,14 BAD (HHHHHH)
|
||||
Size 15: 9,15 BAD (HHHHHH)
|
||||
Size 16: 10,16 BAD (HHHHHH)
|
||||
Size 17: 10,17 BAD (HHHHHH)
|
||||
Size 18: 11,18 BAD (HHHHHH)
|
||||
Size 19: 11,19 BAD (HHHHHH)
|
||||
Size 20: 12,20 BAD (HHHHHH)
|
||||
Size 21: 13,21 BAD (HHHHHH)
|
||||
Size 22: 13,22 BAD (HHHHHH)
|
||||
Size 23: 14,23 BAD (HHHHHH)
|
||||
Size 24: 14,24 BAD (HHHHHH)
|
||||
Size 25: 15,25 BAD (HHHHHH)
|
||||
Size 26: 16,26 BAD (HHHHHH)
|
||||
Size 27: 16,27 BAD (HHHHHH)
|
||||
Size 28: 17,28 BAD (HHHHHH)
|
||||
Size 29: 17,29 BAD (HHHHHH)
|
||||
Size 30: 18,30 BAD (HHHHHH)
|
||||
Size 31: 19,31 BAD (HHHHHH)
|
||||
Size 32: 19,32 BAD (HHHHHH)
|
||||
Size 33: 20,33 BAD (HHHHHH)
|
||||
Size 34: 20,34 BAD (HHHHHH)
|
||||
Size 35: 21,35 BAD (HHHHHH)
|
||||
Size 36: 22,36 BAD (HHHHHH)
|
||||
Size 37: 22,37 BAD (HHHHHH)
|
||||
Size 38: 23,38 BAD (HHHHHH)
|
||||
Size 39: 23,39 BAD (HHHHHH)
|
||||
Size 40: 24,40 BAD (HHHHHH)
|
||||
Size 41: 25,41 BAD (HHHHHH)
|
||||
Size 42: 25,42 BAD (HHHHHH)
|
||||
Size 43: 26,43 BAD (HHHHHH)
|
||||
Size 44: 27,44 BAD (HHHHHH)
|
||||
Size 45: 27,45 BAD (HHHHHH)
|
||||
Size 46: 28,46 BAD (HHHHHH)
|
||||
Size 47: 28,47 BAD (HHHHHH)
|
||||
Size 48: 29,48 BAD (HHHHHH)
|
||||
Size 49: 30,49 BAD (HHHHHH)
|
||||
Size 50: 30,50 BAD (HHHHHH)
|
||||
Size 51: 31,51 BAD (HHHHHH)
|
||||
Size 52: 31,52 BAD (HHHHHH)
|
||||
Size 53: 32,53 BAD (HHHHHH)
|
||||
Size 54: 33,54 BAD (HHHHHH)
|
||||
Size 55: 33,55 BAD (HHHHHH)
|
||||
Size 56: 34,56 BAD (HHHHHH)
|
||||
Size 57: 34,57 BAD (HHHHHH)
|
||||
Size 58: 35,58 BAD (HHHHHH)
|
||||
Size 59: 36,59 BAD (HHHHHH)
|
||||
Size 60: 36,60 BAD (HHHHHH)
|
||||
Size 61: 37,61 BAD (HHHHHH)
|
||||
Size 62: 37,62 BAD (HHHHHH)
|
||||
Size 63: 38,63 BAD (HHHHHH)
|
||||
Size 64: 39,65 BAD (HHHHHH)
|
||||
Size 65: 39,65 BAD (HHHHHH)
|
||||
Size 66: 40,66 BAD (HHHHHH)
|
||||
Size 67: 40,67 BAD (HHHHHH)
|
||||
Size 68: 41,68 BAD (HHHHHH)
|
||||
Size 69: 42,69 BAD (HHHHHH)
|
||||
Size 70: 42,70 BAD (HHHHHH)
|
||||
Size 71: 43,71 BAD (HHHHHH)
|
||||
Size 72: 43,72 BAD (HHHHHH)
|
||||
Size 73: 44,73 BAD (HHHHHH)
|
||||
Size 74: 45,74 BAD (HHHHHH)
|
||||
Size 75: 45,75 BAD (HHHHHH)
|
||||
Size 76: 46,76 BAD (HHHHHH)
|
||||
Size 77: 46,77 BAD (HHHHHH)
|
||||
Size 78: 47,78 BAD (HHHHHH)
|
||||
Size 79: 48,79 BAD (HHHHHH)
|
||||
Size 80: 48,80 BAD (HHHHHH)
|
||||
Size 81: 49,81 BAD (HHHHHH)
|
||||
Size 82: 49,82 BAD (HHHHHH)
|
||||
Size 83: 50,83 BAD (HHHHHH)
|
||||
Size 84: 51,84 BAD (HHHHHH)
|
||||
Size 85: 51,85 BAD (HHHHHH)
|
||||
Size 86: 52,86 BAD (HHHHHH)
|
||||
Size 87: 52,87 BAD (HHHHHH)
|
||||
Size 88: 53,88 BAD (HHHHHH)
|
||||
Size 89: 54,89 BAD (HHHHHH)
|
||||
Size 90: 54,90 BAD (HHHHHH)
|
||||
Size 91: 55,91 BAD (HHHHHH)
|
||||
Size 92: 55,92 BAD (HHHHHH)
|
||||
Size 93: 56,93 BAD (HHHHHH)
|
||||
Size 94: 57,94 BAD (HHHHHH)
|
||||
Size 95: 57,95 BAD (HHHHHH)
|
||||
Size 96: 58,96 BAD (HHHHHH)
|
||||
Size 97: 58,97 BAD (HHHHHH)
|
||||
Size 98: 59,98 BAD (HHHHHH)
|
||||
Size 99: 60,99 BAD (HHHHHH)
|
||||
Size 100: 60,100 BAD (HHHHHH)
|
||||
|
||||
Windows 8.1
|
||||
-----------
|
||||
|
||||
Size 1: 1,2 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 2,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 3,5 BAD (HHHHHH)
|
||||
Size 6: 4,6 BAD (HHHHHH)
|
||||
Size 7: 4,7 BAD (HHHHHH)
|
||||
Size 8: 5,8 BAD (HHHHHH)
|
||||
Size 9: 5,9 BAD (HHHHHH)
|
||||
Size 10: 6,10 BAD (HHHHHH)
|
||||
Size 11: 7,11 BAD (HHHHHH)
|
||||
Size 12: 7,12 BAD (HHHHHH)
|
||||
Size 13: 8,13 BAD (HHHHHH)
|
||||
Size 14: 8,14 BAD (HHHHHH)
|
||||
Size 15: 9,15 BAD (HHHHHH)
|
||||
Size 16: 10,16 BAD (HHHHHH)
|
||||
Size 17: 10,17 BAD (HHHHHH)
|
||||
Size 18: 11,18 BAD (HHHHHH)
|
||||
Size 19: 11,19 BAD (HHHHHH)
|
||||
Size 20: 12,20 BAD (HHHHHH)
|
||||
Size 21: 13,21 BAD (HHHHHH)
|
||||
Size 22: 13,22 BAD (HHHHHH)
|
||||
Size 23: 14,23 BAD (HHHHHH)
|
||||
Size 24: 14,24 BAD (HHHHHH)
|
||||
Size 25: 15,25 BAD (HHHHHH)
|
||||
Size 26: 16,26 BAD (HHHHHH)
|
||||
Size 27: 16,27 BAD (HHHHHH)
|
||||
Size 28: 17,28 BAD (HHHHHH)
|
||||
Size 29: 17,29 BAD (HHHHHH)
|
||||
Size 30: 18,30 BAD (HHHHHH)
|
||||
Size 31: 19,31 BAD (HHHHHH)
|
||||
Size 32: 19,32 BAD (HHHHHH)
|
||||
Size 33: 20,33 BAD (HHHHHH)
|
||||
Size 34: 20,34 BAD (HHHHHH)
|
||||
Size 35: 21,35 BAD (HHHHHH)
|
||||
Size 36: 22,36 BAD (HHHHHH)
|
||||
Size 37: 22,37 BAD (HHHHHH)
|
||||
Size 38: 23,38 BAD (HHHHHH)
|
||||
Size 39: 23,39 BAD (HHHHHH)
|
||||
Size 40: 24,40 BAD (HHHHHH)
|
||||
Size 41: 25,41 BAD (HHHHHH)
|
||||
Size 42: 25,42 BAD (HHHHHH)
|
||||
Size 43: 26,43 BAD (HHHHHH)
|
||||
Size 44: 27,44 BAD (HHHHHH)
|
||||
Size 45: 27,45 BAD (HHHHHH)
|
||||
Size 46: 28,46 BAD (HHHHHH)
|
||||
Size 47: 28,47 BAD (HHHHHH)
|
||||
Size 48: 29,48 BAD (HHHHHH)
|
||||
Size 49: 30,49 BAD (HHHHHH)
|
||||
Size 50: 30,50 BAD (HHHHHH)
|
||||
Size 51: 31,51 BAD (HHHHHH)
|
||||
Size 52: 31,52 BAD (HHHHHH)
|
||||
Size 53: 32,53 BAD (HHHHHH)
|
||||
Size 54: 33,54 BAD (HHHHHH)
|
||||
Size 55: 33,55 BAD (HHHHHH)
|
||||
Size 56: 34,56 BAD (HHHHHH)
|
||||
Size 57: 34,57 BAD (HHHHHH)
|
||||
Size 58: 35,58 BAD (HHHHHH)
|
||||
Size 59: 36,59 BAD (HHHHHH)
|
||||
Size 60: 36,60 BAD (HHHHHH)
|
||||
Size 61: 37,61 BAD (HHHHHH)
|
||||
Size 62: 37,62 BAD (HHHHHH)
|
||||
Size 63: 38,63 BAD (HHHHHH)
|
||||
Size 64: 39,65 BAD (HHHHHH)
|
||||
Size 65: 39,65 BAD (HHHHHH)
|
||||
Size 66: 40,66 BAD (HHHHHH)
|
||||
Size 67: 40,67 BAD (HHHHHH)
|
||||
Size 68: 41,68 BAD (HHHHHH)
|
||||
Size 69: 42,69 BAD (HHHHHH)
|
||||
Size 70: 42,70 BAD (HHHHHH)
|
||||
Size 71: 43,71 BAD (HHHHHH)
|
||||
Size 72: 43,72 BAD (HHHHHH)
|
||||
Size 73: 44,73 BAD (HHHHHH)
|
||||
Size 74: 45,74 BAD (HHHHHH)
|
||||
Size 75: 45,75 BAD (HHHHHH)
|
||||
Size 76: 46,76 BAD (HHHHHH)
|
||||
Size 77: 46,77 BAD (HHHHHH)
|
||||
Size 78: 47,78 BAD (HHHHHH)
|
||||
Size 79: 48,79 BAD (HHHHHH)
|
||||
Size 80: 48,80 BAD (HHHHHH)
|
||||
Size 81: 49,81 BAD (HHHHHH)
|
||||
Size 82: 49,82 BAD (HHHHHH)
|
||||
Size 83: 50,83 BAD (HHHHHH)
|
||||
Size 84: 51,84 BAD (HHHHHH)
|
||||
Size 85: 51,85 BAD (HHHHHH)
|
||||
Size 86: 52,86 BAD (HHHHHH)
|
||||
Size 87: 52,87 BAD (HHHHHH)
|
||||
Size 88: 53,88 BAD (HHHHHH)
|
||||
Size 89: 54,89 BAD (HHHHHH)
|
||||
Size 90: 54,90 BAD (HHHHHH)
|
||||
Size 91: 55,91 BAD (HHHHHH)
|
||||
Size 92: 55,92 BAD (HHHHHH)
|
||||
Size 93: 56,93 BAD (HHHHHH)
|
||||
Size 94: 57,94 BAD (HHHHHH)
|
||||
Size 95: 57,95 BAD (HHHHHH)
|
||||
Size 96: 58,96 BAD (HHHHHH)
|
||||
Size 97: 58,97 BAD (HHHHHH)
|
||||
Size 98: 59,98 BAD (HHHHHH)
|
||||
Size 99: 60,99 BAD (HHHHHH)
|
||||
Size 100: 60,100 BAD (HHHHHH)
|
||||
|
||||
Windows 10 14342 Old Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,2 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 2,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 3,5 BAD (HHHHHH)
|
||||
Size 6: 4,6 BAD (HHHHHH)
|
||||
Size 7: 4,7 BAD (HHHHHH)
|
||||
Size 8: 5,8 BAD (HHHHHH)
|
||||
Size 9: 5,9 BAD (HHHHHH)
|
||||
Size 10: 6,10 BAD (HHHHHH)
|
||||
Size 11: 7,11 BAD (HHHHHH)
|
||||
Size 12: 7,12 BAD (HHHHHH)
|
||||
Size 13: 8,13 BAD (HHHHHH)
|
||||
Size 14: 8,14 BAD (HHHHHH)
|
||||
Size 15: 9,15 BAD (HHHHHH)
|
||||
Size 16: 10,16 BAD (HHHHHH)
|
||||
Size 17: 10,17 BAD (HHHHHH)
|
||||
Size 18: 11,18 BAD (HHHHHH)
|
||||
Size 19: 11,19 BAD (HHHHHH)
|
||||
Size 20: 12,20 BAD (HHHHHH)
|
||||
Size 21: 13,21 BAD (HHHHHH)
|
||||
Size 22: 13,22 BAD (HHHHHH)
|
||||
Size 23: 14,23 BAD (HHHHHH)
|
||||
Size 24: 14,24 BAD (HHHHHH)
|
||||
Size 25: 15,25 BAD (HHHHHH)
|
||||
Size 26: 16,26 BAD (HHHHHH)
|
||||
Size 27: 16,27 BAD (HHHHHH)
|
||||
Size 28: 17,28 BAD (HHHHHH)
|
||||
Size 29: 17,29 BAD (HHHHHH)
|
||||
Size 30: 18,30 BAD (HHHHHH)
|
||||
Size 31: 19,31 BAD (HHHHHH)
|
||||
Size 32: 19,32 BAD (HHHHHH)
|
||||
Size 33: 20,33 BAD (HHHHHH)
|
||||
Size 34: 20,34 BAD (HHHHHH)
|
||||
Size 35: 21,35 BAD (HHHHHH)
|
||||
Size 36: 22,36 BAD (HHHHHH)
|
||||
Size 37: 22,37 BAD (HHHHHH)
|
||||
Size 38: 23,38 BAD (HHHHHH)
|
||||
Size 39: 23,39 BAD (HHHHHH)
|
||||
Size 40: 24,40 BAD (HHHHHH)
|
||||
Size 41: 25,41 BAD (HHHHHH)
|
||||
Size 42: 25,42 BAD (HHHHHH)
|
||||
Size 43: 26,43 BAD (HHHHHH)
|
||||
Size 44: 27,44 BAD (HHHHHH)
|
||||
Size 45: 27,45 BAD (HHHHHH)
|
||||
Size 46: 28,46 BAD (HHHHHH)
|
||||
Size 47: 28,47 BAD (HHHHHH)
|
||||
Size 48: 29,48 BAD (HHHHHH)
|
||||
Size 49: 30,49 BAD (HHHHHH)
|
||||
Size 50: 30,50 BAD (HHHHHH)
|
||||
Size 51: 31,51 BAD (HHHHHH)
|
||||
Size 52: 31,52 BAD (HHHHHH)
|
||||
Size 53: 32,53 BAD (HHHHHH)
|
||||
Size 54: 33,54 BAD (HHHHHH)
|
||||
Size 55: 33,55 BAD (HHHHHH)
|
||||
Size 56: 34,56 BAD (HHHHHH)
|
||||
Size 57: 34,57 BAD (HHHHHH)
|
||||
Size 58: 35,58 BAD (HHHHHH)
|
||||
Size 59: 36,59 BAD (HHHHHH)
|
||||
Size 60: 36,60 BAD (HHHHHH)
|
||||
Size 61: 37,61 BAD (HHHHHH)
|
||||
Size 62: 37,62 BAD (HHHHHH)
|
||||
Size 63: 38,63 BAD (HHHHHH)
|
||||
Size 64: 39,65 BAD (HHHHHH)
|
||||
Size 65: 39,65 BAD (HHHHHH)
|
||||
Size 66: 40,66 BAD (HHHHHH)
|
||||
Size 67: 40,67 BAD (HHHHHH)
|
||||
Size 68: 41,68 BAD (HHHHHH)
|
||||
Size 69: 42,69 BAD (HHHHHH)
|
||||
Size 70: 42,70 BAD (HHHHHH)
|
||||
Size 71: 43,71 BAD (HHHHHH)
|
||||
Size 72: 43,72 BAD (HHHHHH)
|
||||
Size 73: 44,73 BAD (HHHHHH)
|
||||
Size 74: 45,74 BAD (HHHHHH)
|
||||
Size 75: 45,75 BAD (HHHHHH)
|
||||
Size 76: 46,76 BAD (HHHHHH)
|
||||
Size 77: 46,77 BAD (HHHHHH)
|
||||
Size 78: 47,78 BAD (HHHHHH)
|
||||
Size 79: 48,79 BAD (HHHHHH)
|
||||
Size 80: 48,80 BAD (HHHHHH)
|
||||
Size 81: 49,81 BAD (HHHHHH)
|
||||
Size 82: 49,82 BAD (HHHHHH)
|
||||
Size 83: 50,83 BAD (HHHHHH)
|
||||
Size 84: 51,84 BAD (HHHHHH)
|
||||
Size 85: 51,85 BAD (HHHHHH)
|
||||
Size 86: 52,86 BAD (HHHHHH)
|
||||
Size 87: 52,87 BAD (HHHHHH)
|
||||
Size 88: 53,88 BAD (HHHHHH)
|
||||
Size 89: 54,89 BAD (HHHHHH)
|
||||
Size 90: 54,90 BAD (HHHHHH)
|
||||
Size 91: 55,91 BAD (HHHHHH)
|
||||
Size 92: 55,92 BAD (HHHHHH)
|
||||
Size 93: 56,93 BAD (HHHHHH)
|
||||
Size 94: 57,94 BAD (HHHHHH)
|
||||
Size 95: 57,95 BAD (HHHHHH)
|
||||
Size 96: 58,96 BAD (HHHHHH)
|
||||
Size 97: 58,97 BAD (HHHHHH)
|
||||
Size 98: 59,98 BAD (HHHHHH)
|
||||
Size 99: 60,99 BAD (HHHHHH)
|
||||
Size 100: 60,100 BAD (HHHHHH)
|
||||
|
||||
Windows 10 14342 New Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,1 BAD (HHHHHH)
|
||||
Size 2: 1,2 BAD (HHHHHH)
|
||||
Size 3: 2,3 BAD (HHHHHH)
|
||||
Size 4: 2,4 BAD (HHHHHH)
|
||||
Size 5: 3,5 BAD (HHHHHH)
|
||||
Size 6: 4,6 BAD (HHHHHH)
|
||||
Size 7: 4,7 BAD (HHHHHH)
|
||||
Size 8: 5,8 BAD (HHHHHH)
|
||||
Size 9: 5,9 BAD (HHHHHH)
|
||||
Size 10: 6,10 BAD (HHHHHH)
|
||||
Size 11: 7,11 BAD (HHHHHH)
|
||||
Size 12: 7,12 BAD (HHHHHH)
|
||||
Size 13: 8,13 BAD (HHHHHH)
|
||||
Size 14: 8,14 BAD (HHHHHH)
|
||||
Size 15: 9,15 BAD (HHHHHH)
|
||||
Size 16: 10,16 BAD (HHHHHH)
|
||||
Size 17: 10,17 BAD (HHHHHH)
|
||||
Size 18: 11,18 BAD (HHHHHH)
|
||||
Size 19: 11,19 BAD (HHHHHH)
|
||||
Size 20: 12,20 BAD (HHHHHH)
|
||||
Size 21: 13,21 BAD (HHHHHH)
|
||||
Size 22: 13,22 BAD (HHHHHH)
|
||||
Size 23: 14,23 BAD (HHHHHH)
|
||||
Size 24: 14,24 BAD (HHHHHH)
|
||||
Size 25: 15,25 BAD (HHHHHH)
|
||||
Size 26: 16,26 BAD (HHHHHH)
|
||||
Size 27: 16,27 BAD (HHHHHH)
|
||||
Size 28: 17,28 BAD (HHHHHH)
|
||||
Size 29: 17,29 BAD (HHHHHH)
|
||||
Size 30: 18,30 BAD (HHHHHH)
|
||||
Size 31: 19,31 BAD (HHHHHH)
|
||||
Size 32: 19,32 BAD (HHHHHH)
|
||||
Size 33: 20,33 BAD (HHHHHH)
|
||||
Size 34: 20,34 BAD (HHHHHH)
|
||||
Size 35: 21,35 BAD (HHHHHH)
|
||||
Size 36: 22,36 BAD (HHHHHH)
|
||||
Size 37: 22,37 BAD (HHHHHH)
|
||||
Size 38: 23,38 BAD (HHHHHH)
|
||||
Size 39: 23,39 BAD (HHHHHH)
|
||||
Size 40: 24,40 BAD (HHHHHH)
|
||||
Size 41: 25,41 BAD (HHHHHH)
|
||||
Size 42: 25,42 BAD (HHHHHH)
|
||||
Size 43: 26,43 BAD (HHHHHH)
|
||||
Size 44: 27,44 BAD (HHHHHH)
|
||||
Size 45: 27,45 BAD (HHHHHH)
|
||||
Size 46: 28,46 BAD (HHHHHH)
|
||||
Size 47: 28,47 BAD (HHHHHH)
|
||||
Size 48: 29,48 BAD (HHHHHH)
|
||||
Size 49: 30,49 BAD (HHHHHH)
|
||||
Size 50: 30,50 BAD (HHHHHH)
|
||||
Size 51: 31,51 BAD (HHHHHH)
|
||||
Size 52: 31,52 BAD (HHHHHH)
|
||||
Size 53: 32,53 BAD (HHHHHH)
|
||||
Size 54: 33,54 BAD (HHHHHH)
|
||||
Size 55: 33,55 BAD (HHHHHH)
|
||||
Size 56: 34,56 BAD (HHHHHH)
|
||||
Size 57: 34,57 BAD (HHHHHH)
|
||||
Size 58: 35,58 BAD (HHHHHH)
|
||||
Size 59: 36,59 BAD (HHHHHH)
|
||||
Size 60: 36,60 BAD (HHHHHH)
|
||||
Size 61: 37,61 BAD (HHHHHH)
|
||||
Size 62: 37,62 BAD (HHHHHH)
|
||||
Size 63: 38,63 BAD (HHHHHH)
|
||||
Size 64: 39,64 BAD (HHHHHH)
|
||||
Size 65: 39,65 BAD (HHHHHH)
|
||||
Size 66: 40,66 BAD (HHHHHH)
|
||||
Size 67: 40,67 BAD (HHHHHH)
|
||||
Size 68: 41,68 BAD (HHHHHH)
|
||||
Size 69: 42,69 BAD (HHHHHH)
|
||||
Size 70: 42,70 BAD (HHHHHH)
|
||||
Size 71: 43,71 BAD (HHHHHH)
|
||||
Size 72: 43,72 BAD (HHHHHH)
|
||||
Size 73: 44,73 BAD (HHHHHH)
|
||||
Size 74: 45,74 BAD (HHHHHH)
|
||||
Size 75: 45,75 BAD (HHHHHH)
|
||||
Size 76: 46,76 BAD (HHHHHH)
|
||||
Size 77: 46,77 BAD (HHHHHH)
|
||||
Size 78: 47,78 BAD (HHHHHH)
|
||||
Size 79: 48,79 BAD (HHHHHH)
|
||||
Size 80: 48,80 BAD (HHHHHH)
|
||||
Size 81: 49,81 BAD (HHHHHH)
|
||||
Size 82: 49,82 BAD (HHHHHH)
|
||||
Size 83: 50,83 BAD (HHHHHH)
|
||||
Size 84: 51,84 BAD (HHHHHH)
|
||||
Size 85: 51,85 BAD (HHHHHH)
|
||||
Size 86: 52,86 BAD (HHHHHH)
|
||||
Size 87: 52,87 BAD (HHHHHH)
|
||||
Size 88: 53,88 BAD (HHHHHH)
|
||||
Size 89: 54,89 BAD (HHHHHH)
|
||||
Size 90: 54,90 BAD (HHHHHH)
|
||||
Size 91: 55,91 BAD (HHHHHH)
|
||||
Size 92: 55,92 BAD (HHHHHH)
|
||||
Size 93: 56,93 BAD (HHHHHH)
|
||||
Size 94: 57,94 BAD (HHHHHH)
|
||||
Size 95: 57,95 BAD (HHHHHH)
|
||||
Size 96: 58,96 BAD (HHHHHH)
|
||||
Size 97: 58,97 BAD (HHHHHH)
|
||||
Size 98: 59,98 BAD (HHHHHH)
|
||||
Size 99: 60,99 BAD (HHHHHH)
|
||||
Size 100: 60,100 BAD (HHHHHH)
|
||||
Generated
Vendored
+630
@@ -0,0 +1,630 @@
|
||||
=======================================
|
||||
Code Page 932, Japanese, MS Gothic font
|
||||
=======================================
|
||||
|
||||
Options: -face-gothic -family 0x36
|
||||
Chars: A2 A3 2014 3044 30FC 4000
|
||||
|
||||
Vista
|
||||
-----
|
||||
|
||||
Size 1: 1,2 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 BAD (FFFFHH)
|
||||
Size 4: 2,4 OK (HHHFFF)
|
||||
Size 5: 3,5 OK (HHHFFF)
|
||||
Size 6: 3,6 OK (HHHFFF)
|
||||
Size 7: 4,7 OK (HHHFFF)
|
||||
Size 8: 4,8 OK (HHHFFF)
|
||||
Size 9: 5,9 OK (HHHFFF)
|
||||
Size 10: 5,10 OK (HHHFFF)
|
||||
Size 11: 6,11 OK (HHHFFF)
|
||||
Size 12: 6,12 OK (HHHFFF)
|
||||
Size 13: 7,13 OK (HHHFFF)
|
||||
Size 14: 7,14 BAD (HHHFHH)
|
||||
Size 15: 8,15 OK (HHHFFF)
|
||||
Size 16: 8,16 BAD (HHHFHH)
|
||||
Size 17: 9,17 OK (HHHFFF)
|
||||
Size 18: 9,18 BAD (HHHFHH)
|
||||
Size 19: 10,19 OK (HHHFFF)
|
||||
Size 20: 10,20 BAD (HHHFHH)
|
||||
Size 21: 11,21 OK (HHHFFF)
|
||||
Size 22: 11,22 BAD (HHHFHH)
|
||||
Size 23: 12,23 BAD (HHHFHH)
|
||||
Size 24: 12,24 BAD (HHHFHH)
|
||||
Size 25: 13,25 BAD (HHHFHH)
|
||||
Size 26: 13,26 BAD (HHHFHH)
|
||||
Size 27: 14,27 BAD (HHHFHH)
|
||||
Size 28: 14,28 BAD (HHHFHH)
|
||||
Size 29: 15,29 BAD (HHHFHH)
|
||||
Size 30: 15,30 BAD (HHHFHH)
|
||||
Size 31: 16,31 BAD (HHHFHH)
|
||||
Size 32: 16,33 BAD (HHHFHH)
|
||||
Size 33: 17,33 BAD (HHHFHH)
|
||||
Size 34: 17,34 BAD (HHHFHH)
|
||||
Size 35: 18,35 BAD (HHHFHH)
|
||||
Size 36: 18,36 BAD (HHHFHH)
|
||||
Size 37: 19,37 BAD (HHHFHH)
|
||||
Size 38: 19,38 BAD (HHHFHH)
|
||||
Size 39: 20,39 BAD (HHHFHH)
|
||||
Size 40: 20,40 BAD (HHHFHH)
|
||||
Size 41: 21,41 BAD (HHHFHH)
|
||||
Size 42: 21,42 BAD (HHHFHH)
|
||||
Size 43: 22,43 BAD (HHHFHH)
|
||||
Size 44: 22,44 BAD (HHHFHH)
|
||||
Size 45: 23,45 BAD (HHHFHH)
|
||||
Size 46: 23,46 BAD (HHHFHH)
|
||||
Size 47: 24,47 BAD (HHHFHH)
|
||||
Size 48: 24,48 BAD (HHHFHH)
|
||||
Size 49: 25,49 BAD (HHHFHH)
|
||||
Size 50: 25,50 BAD (HHHFHH)
|
||||
Size 51: 26,51 BAD (HHHFHH)
|
||||
Size 52: 26,52 BAD (HHHFHH)
|
||||
Size 53: 27,53 BAD (HHHFHH)
|
||||
Size 54: 27,54 BAD (HHHFHH)
|
||||
Size 55: 28,55 BAD (HHHFHH)
|
||||
Size 56: 28,56 BAD (HHHFHH)
|
||||
Size 57: 29,57 BAD (HHHFHH)
|
||||
Size 58: 29,58 BAD (HHHFHH)
|
||||
Size 59: 30,59 BAD (HHHFHH)
|
||||
Size 60: 30,60 BAD (HHHFHH)
|
||||
Size 61: 31,61 BAD (HHHFHH)
|
||||
Size 62: 31,62 BAD (HHHFHH)
|
||||
Size 63: 32,63 BAD (HHHFHH)
|
||||
Size 64: 32,64 BAD (HHHFHH)
|
||||
Size 65: 33,65 BAD (HHHFHH)
|
||||
Size 66: 33,66 BAD (HHHFHH)
|
||||
Size 67: 34,67 BAD (HHHFHH)
|
||||
Size 68: 34,68 BAD (HHHFHH)
|
||||
Size 69: 35,69 BAD (HHHFHH)
|
||||
Size 70: 35,70 BAD (HHHFHH)
|
||||
Size 71: 36,71 BAD (HHHFHH)
|
||||
Size 72: 36,72 BAD (HHHFHH)
|
||||
Size 73: 37,73 BAD (HHHFHH)
|
||||
Size 74: 37,74 BAD (HHHFHH)
|
||||
Size 75: 38,75 BAD (HHHFHH)
|
||||
Size 76: 38,76 BAD (HHHFHH)
|
||||
Size 77: 39,77 BAD (HHHFHH)
|
||||
Size 78: 39,78 BAD (HHHFHH)
|
||||
Size 79: 40,79 BAD (HHHFHH)
|
||||
Size 80: 40,80 BAD (HHHFHH)
|
||||
Size 81: 41,81 BAD (HHHFHH)
|
||||
Size 82: 41,82 BAD (HHHFHH)
|
||||
Size 83: 42,83 BAD (HHHFHH)
|
||||
Size 84: 42,84 BAD (HHHFHH)
|
||||
Size 85: 43,85 BAD (HHHFHH)
|
||||
Size 86: 43,86 BAD (HHHFHH)
|
||||
Size 87: 44,87 BAD (HHHFHH)
|
||||
Size 88: 44,88 BAD (HHHFHH)
|
||||
Size 89: 45,89 BAD (HHHFHH)
|
||||
Size 90: 45,90 BAD (HHHFHH)
|
||||
Size 91: 46,91 BAD (HHHFHH)
|
||||
Size 92: 46,92 BAD (HHHFHH)
|
||||
Size 93: 47,93 BAD (HHHFHH)
|
||||
Size 94: 47,94 BAD (HHHFHH)
|
||||
Size 95: 48,95 BAD (HHHFHH)
|
||||
Size 96: 48,97 BAD (HHHFHH)
|
||||
Size 97: 49,97 BAD (HHHFHH)
|
||||
Size 98: 49,98 BAD (HHHFHH)
|
||||
Size 99: 50,99 BAD (HHHFHH)
|
||||
Size 100: 50,100 BAD (HHHFHH)
|
||||
|
||||
Windows 7
|
||||
---------
|
||||
|
||||
Size 1: 1,2 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 BAD (FFFFHH)
|
||||
Size 4: 2,4 OK (HHHFFF)
|
||||
Size 5: 3,5 OK (HHHFFF)
|
||||
Size 6: 3,6 OK (HHHFFF)
|
||||
Size 7: 4,7 OK (HHHFFF)
|
||||
Size 8: 4,8 OK (HHHFFF)
|
||||
Size 9: 5,9 OK (HHHFFF)
|
||||
Size 10: 5,10 OK (HHHFFF)
|
||||
Size 11: 6,11 OK (HHHFFF)
|
||||
Size 12: 6,12 OK (HHHFFF)
|
||||
Size 13: 7,13 OK (HHHFFF)
|
||||
Size 14: 7,14 BAD (FFFFFF)
|
||||
Size 15: 8,15 OK (HHHFFF)
|
||||
Size 16: 8,16 BAD (FFFFFF)
|
||||
Size 17: 9,17 OK (HHHFFF)
|
||||
Size 18: 9,18 BAD (FFFFFF)
|
||||
Size 19: 10,19 OK (HHHFFF)
|
||||
Size 20: 10,20 BAD (FFFFFF)
|
||||
Size 21: 11,21 OK (HHHFFF)
|
||||
Size 22: 11,22 BAD (FFFFFF)
|
||||
Size 23: 12,23 BAD (FFFFFF)
|
||||
Size 24: 12,24 BAD (FFFFFF)
|
||||
Size 25: 13,25 BAD (FFFFFF)
|
||||
Size 26: 13,26 BAD (FFFFFF)
|
||||
Size 27: 14,27 BAD (FFFFFF)
|
||||
Size 28: 14,28 BAD (FFFFFF)
|
||||
Size 29: 15,29 BAD (FFFFFF)
|
||||
Size 30: 15,30 BAD (FFFFFF)
|
||||
Size 31: 16,31 BAD (FFFFFF)
|
||||
Size 32: 16,33 BAD (FFFFFF)
|
||||
Size 33: 17,33 BAD (FFFFFF)
|
||||
Size 34: 17,34 BAD (FFFFFF)
|
||||
Size 35: 18,35 BAD (FFFFFF)
|
||||
Size 36: 18,36 BAD (FFFFFF)
|
||||
Size 37: 19,37 BAD (FFFFFF)
|
||||
Size 38: 19,38 BAD (FFFFFF)
|
||||
Size 39: 20,39 BAD (FFFFFF)
|
||||
Size 40: 20,40 BAD (FFFFFF)
|
||||
Size 41: 21,41 BAD (FFFFFF)
|
||||
Size 42: 21,42 BAD (FFFFFF)
|
||||
Size 43: 22,43 BAD (FFFFFF)
|
||||
Size 44: 22,44 BAD (FFFFFF)
|
||||
Size 45: 23,45 BAD (FFFFFF)
|
||||
Size 46: 23,46 BAD (FFFFFF)
|
||||
Size 47: 24,47 BAD (FFFFFF)
|
||||
Size 48: 24,48 BAD (FFFFFF)
|
||||
Size 49: 25,49 BAD (FFFFFF)
|
||||
Size 50: 25,50 BAD (FFFFFF)
|
||||
Size 51: 26,51 BAD (FFFFFF)
|
||||
Size 52: 26,52 BAD (FFFFFF)
|
||||
Size 53: 27,53 BAD (FFFFFF)
|
||||
Size 54: 27,54 BAD (FFFFFF)
|
||||
Size 55: 28,55 BAD (FFFFFF)
|
||||
Size 56: 28,56 BAD (FFFFFF)
|
||||
Size 57: 29,57 BAD (FFFFFF)
|
||||
Size 58: 29,58 BAD (FFFFFF)
|
||||
Size 59: 30,59 BAD (FFFFFF)
|
||||
Size 60: 30,60 BAD (FFFFFF)
|
||||
Size 61: 31,61 BAD (FFFFFF)
|
||||
Size 62: 31,62 BAD (FFFFFF)
|
||||
Size 63: 32,63 BAD (FFFFFF)
|
||||
Size 64: 32,64 BAD (FFFFFF)
|
||||
Size 65: 33,65 BAD (FFFFFF)
|
||||
Size 66: 33,66 BAD (FFFFFF)
|
||||
Size 67: 34,67 BAD (FFFFFF)
|
||||
Size 68: 34,68 BAD (FFFFFF)
|
||||
Size 69: 35,69 BAD (FFFFFF)
|
||||
Size 70: 35,70 BAD (FFFFFF)
|
||||
Size 71: 36,71 BAD (FFFFFF)
|
||||
Size 72: 36,72 BAD (FFFFFF)
|
||||
Size 73: 37,73 BAD (FFFFFF)
|
||||
Size 74: 37,74 BAD (FFFFFF)
|
||||
Size 75: 38,75 BAD (FFFFFF)
|
||||
Size 76: 38,76 BAD (FFFFFF)
|
||||
Size 77: 39,77 BAD (FFFFFF)
|
||||
Size 78: 39,78 BAD (FFFFFF)
|
||||
Size 79: 40,79 BAD (FFFFFF)
|
||||
Size 80: 40,80 BAD (FFFFFF)
|
||||
Size 81: 41,81 BAD (FFFFFF)
|
||||
Size 82: 41,82 BAD (FFFFFF)
|
||||
Size 83: 42,83 BAD (FFFFFF)
|
||||
Size 84: 42,84 BAD (FFFFFF)
|
||||
Size 85: 43,85 BAD (FFFFFF)
|
||||
Size 86: 43,86 BAD (FFFFFF)
|
||||
Size 87: 44,87 BAD (FFFFFF)
|
||||
Size 88: 44,88 BAD (FFFFFF)
|
||||
Size 89: 45,89 BAD (FFFFFF)
|
||||
Size 90: 45,90 BAD (FFFFFF)
|
||||
Size 91: 46,91 BAD (FFFFFF)
|
||||
Size 92: 46,92 BAD (FFFFFF)
|
||||
Size 93: 47,93 BAD (FFFFFF)
|
||||
Size 94: 47,94 BAD (FFFFFF)
|
||||
Size 95: 48,95 BAD (FFFFFF)
|
||||
Size 96: 48,97 BAD (FFFFFF)
|
||||
Size 97: 49,97 BAD (FFFFFF)
|
||||
Size 98: 49,98 BAD (FFFFFF)
|
||||
Size 99: 50,99 BAD (FFFFFF)
|
||||
Size 100: 50,100 BAD (FFFFFF)
|
||||
|
||||
Windows 8
|
||||
---------
|
||||
|
||||
Size 1: 1,2 BAD (FFFFHH)
|
||||
Size 2: 1,2 BAD (FFFFHH)
|
||||
Size 3: 2,3 BAD (FFFFFF)
|
||||
Size 4: 2,4 BAD (FFFFHH)
|
||||
Size 5: 3,5 BAD (FFFFFF)
|
||||
Size 6: 3,6 BAD (FFFFHH)
|
||||
Size 7: 4,7 BAD (FFFFFF)
|
||||
Size 8: 4,8 BAD (FFFFHH)
|
||||
Size 9: 5,9 BAD (FFFFFF)
|
||||
Size 10: 5,10 BAD (FFFFHH)
|
||||
Size 11: 6,11 BAD (FFFFFF)
|
||||
Size 12: 6,12 BAD (FFFFHH)
|
||||
Size 13: 7,13 BAD (FFFFFF)
|
||||
Size 14: 7,14 BAD (FFFFHH)
|
||||
Size 15: 8,15 BAD (FFFFFF)
|
||||
Size 16: 8,16 BAD (FFFFHH)
|
||||
Size 17: 9,17 BAD (FFFFFF)
|
||||
Size 18: 9,18 BAD (FFFFHH)
|
||||
Size 19: 10,19 BAD (FFFFFF)
|
||||
Size 20: 10,20 BAD (FFFFFF)
|
||||
Size 21: 11,21 BAD (FFFFFF)
|
||||
Size 22: 11,22 BAD (FFFFFF)
|
||||
Size 23: 12,23 BAD (FFFFFF)
|
||||
Size 24: 12,24 BAD (FFFFFF)
|
||||
Size 25: 13,25 BAD (FFFFFF)
|
||||
Size 26: 13,26 BAD (FFFFFF)
|
||||
Size 27: 14,27 BAD (FFFFFF)
|
||||
Size 28: 14,28 BAD (FFFFFF)
|
||||
Size 29: 15,29 BAD (FFFFFF)
|
||||
Size 30: 15,30 BAD (FFFFFF)
|
||||
Size 31: 16,31 BAD (FFFFFF)
|
||||
Size 32: 16,33 BAD (FFFFFF)
|
||||
Size 33: 17,33 BAD (FFFFFF)
|
||||
Size 34: 17,34 BAD (FFFFFF)
|
||||
Size 35: 18,35 BAD (FFFFFF)
|
||||
Size 36: 18,36 BAD (FFFFFF)
|
||||
Size 37: 19,37 BAD (FFFFFF)
|
||||
Size 38: 19,38 BAD (FFFFFF)
|
||||
Size 39: 20,39 BAD (FFFFFF)
|
||||
Size 40: 20,40 BAD (FFFFFF)
|
||||
Size 41: 21,41 BAD (FFFFFF)
|
||||
Size 42: 21,42 BAD (FFFFFF)
|
||||
Size 43: 22,43 BAD (FFFFFF)
|
||||
Size 44: 22,44 BAD (FFFFFF)
|
||||
Size 45: 23,45 BAD (FFFFFF)
|
||||
Size 46: 23,46 BAD (FFFFFF)
|
||||
Size 47: 24,47 BAD (FFFFFF)
|
||||
Size 48: 24,48 BAD (FFFFFF)
|
||||
Size 49: 25,49 BAD (FFFFFF)
|
||||
Size 50: 25,50 BAD (FFFFFF)
|
||||
Size 51: 26,51 BAD (FFFFFF)
|
||||
Size 52: 26,52 BAD (FFFFFF)
|
||||
Size 53: 27,53 BAD (FFFFFF)
|
||||
Size 54: 27,54 BAD (FFFFFF)
|
||||
Size 55: 28,55 BAD (FFFFFF)
|
||||
Size 56: 28,56 BAD (FFFFFF)
|
||||
Size 57: 29,57 BAD (FFFFFF)
|
||||
Size 58: 29,58 BAD (FFFFFF)
|
||||
Size 59: 30,59 BAD (FFFFFF)
|
||||
Size 60: 30,60 BAD (FFFFFF)
|
||||
Size 61: 31,61 BAD (FFFFFF)
|
||||
Size 62: 31,62 BAD (FFFFFF)
|
||||
Size 63: 32,63 BAD (FFFFFF)
|
||||
Size 64: 32,64 BAD (FFFFFF)
|
||||
Size 65: 33,65 BAD (FFFFFF)
|
||||
Size 66: 33,66 BAD (FFFFFF)
|
||||
Size 67: 34,67 BAD (FFFFFF)
|
||||
Size 68: 34,68 BAD (FFFFFF)
|
||||
Size 69: 35,69 BAD (FFFFFF)
|
||||
Size 70: 35,70 BAD (FFFFFF)
|
||||
Size 71: 36,71 BAD (FFFFFF)
|
||||
Size 72: 36,72 BAD (FFFFFF)
|
||||
Size 73: 37,73 BAD (FFFFFF)
|
||||
Size 74: 37,74 BAD (FFFFFF)
|
||||
Size 75: 38,75 BAD (FFFFFF)
|
||||
Size 76: 38,76 BAD (FFFFFF)
|
||||
Size 77: 39,77 BAD (FFFFFF)
|
||||
Size 78: 39,78 BAD (FFFFFF)
|
||||
Size 79: 40,79 BAD (FFFFFF)
|
||||
Size 80: 40,80 BAD (FFFFFF)
|
||||
Size 81: 41,81 BAD (FFFFFF)
|
||||
Size 82: 41,82 BAD (FFFFFF)
|
||||
Size 83: 42,83 BAD (FFFFFF)
|
||||
Size 84: 42,84 BAD (FFFFFF)
|
||||
Size 85: 43,85 BAD (FFFFFF)
|
||||
Size 86: 43,86 BAD (FFFFFF)
|
||||
Size 87: 44,87 BAD (FFFFFF)
|
||||
Size 88: 44,88 BAD (FFFFFF)
|
||||
Size 89: 45,89 BAD (FFFFFF)
|
||||
Size 90: 45,90 BAD (FFFFFF)
|
||||
Size 91: 46,91 BAD (FFFFFF)
|
||||
Size 92: 46,92 BAD (FFFFFF)
|
||||
Size 93: 47,93 BAD (FFFFFF)
|
||||
Size 94: 47,94 BAD (FFFFFF)
|
||||
Size 95: 48,95 BAD (FFFFFF)
|
||||
Size 96: 48,97 BAD (FFFFFF)
|
||||
Size 97: 49,97 BAD (FFFFFF)
|
||||
Size 98: 49,98 BAD (FFFFFF)
|
||||
Size 99: 50,99 BAD (FFFFFF)
|
||||
Size 100: 50,100 BAD (FFFFFF)
|
||||
|
||||
Windows 8.1
|
||||
-----------
|
||||
|
||||
Size 1: 1,2 BAD (FFFFHH)
|
||||
Size 2: 1,2 BAD (FFFFHH)
|
||||
Size 3: 2,3 BAD (FFFFFF)
|
||||
Size 4: 2,4 BAD (FFFFHH)
|
||||
Size 5: 3,5 BAD (FFFFFF)
|
||||
Size 6: 3,6 BAD (FFFFHH)
|
||||
Size 7: 4,7 BAD (FFFFFF)
|
||||
Size 8: 4,8 BAD (FFFFHH)
|
||||
Size 9: 5,9 BAD (FFFFFF)
|
||||
Size 10: 5,10 BAD (FFFFHH)
|
||||
Size 11: 6,11 BAD (FFFFFF)
|
||||
Size 12: 6,12 BAD (FFFFHH)
|
||||
Size 13: 7,13 BAD (FFFFFF)
|
||||
Size 14: 7,14 BAD (FFFFHH)
|
||||
Size 15: 8,15 BAD (FFFFFF)
|
||||
Size 16: 8,16 BAD (FFFFHH)
|
||||
Size 17: 9,17 BAD (FFFFFF)
|
||||
Size 18: 9,18 BAD (FFFFHH)
|
||||
Size 19: 10,19 BAD (FFFFFF)
|
||||
Size 20: 10,20 BAD (FFFFFF)
|
||||
Size 21: 11,21 BAD (FFFFFF)
|
||||
Size 22: 11,22 BAD (FFFFFF)
|
||||
Size 23: 12,23 BAD (FFFFFF)
|
||||
Size 24: 12,24 BAD (FFFFFF)
|
||||
Size 25: 13,25 BAD (FFFFFF)
|
||||
Size 26: 13,26 BAD (FFFFFF)
|
||||
Size 27: 14,27 BAD (FFFFFF)
|
||||
Size 28: 14,28 BAD (FFFFFF)
|
||||
Size 29: 15,29 BAD (FFFFFF)
|
||||
Size 30: 15,30 BAD (FFFFFF)
|
||||
Size 31: 16,31 BAD (FFFFFF)
|
||||
Size 32: 16,33 BAD (FFFFFF)
|
||||
Size 33: 17,33 BAD (FFFFFF)
|
||||
Size 34: 17,34 BAD (FFFFFF)
|
||||
Size 35: 18,35 BAD (FFFFFF)
|
||||
Size 36: 18,36 BAD (FFFFFF)
|
||||
Size 37: 19,37 BAD (FFFFFF)
|
||||
Size 38: 19,38 BAD (FFFFFF)
|
||||
Size 39: 20,39 BAD (FFFFFF)
|
||||
Size 40: 20,40 BAD (FFFFFF)
|
||||
Size 41: 21,41 BAD (FFFFFF)
|
||||
Size 42: 21,42 BAD (FFFFFF)
|
||||
Size 43: 22,43 BAD (FFFFFF)
|
||||
Size 44: 22,44 BAD (FFFFFF)
|
||||
Size 45: 23,45 BAD (FFFFFF)
|
||||
Size 46: 23,46 BAD (FFFFFF)
|
||||
Size 47: 24,47 BAD (FFFFFF)
|
||||
Size 48: 24,48 BAD (FFFFFF)
|
||||
Size 49: 25,49 BAD (FFFFFF)
|
||||
Size 50: 25,50 BAD (FFFFFF)
|
||||
Size 51: 26,51 BAD (FFFFFF)
|
||||
Size 52: 26,52 BAD (FFFFFF)
|
||||
Size 53: 27,53 BAD (FFFFFF)
|
||||
Size 54: 27,54 BAD (FFFFFF)
|
||||
Size 55: 28,55 BAD (FFFFFF)
|
||||
Size 56: 28,56 BAD (FFFFFF)
|
||||
Size 57: 29,57 BAD (FFFFFF)
|
||||
Size 58: 29,58 BAD (FFFFFF)
|
||||
Size 59: 30,59 BAD (FFFFFF)
|
||||
Size 60: 30,60 BAD (FFFFFF)
|
||||
Size 61: 31,61 BAD (FFFFFF)
|
||||
Size 62: 31,62 BAD (FFFFFF)
|
||||
Size 63: 32,63 BAD (FFFFFF)
|
||||
Size 64: 32,64 BAD (FFFFFF)
|
||||
Size 65: 33,65 BAD (FFFFFF)
|
||||
Size 66: 33,66 BAD (FFFFFF)
|
||||
Size 67: 34,67 BAD (FFFFFF)
|
||||
Size 68: 34,68 BAD (FFFFFF)
|
||||
Size 69: 35,69 BAD (FFFFFF)
|
||||
Size 70: 35,70 BAD (FFFFFF)
|
||||
Size 71: 36,71 BAD (FFFFFF)
|
||||
Size 72: 36,72 BAD (FFFFFF)
|
||||
Size 73: 37,73 BAD (FFFFFF)
|
||||
Size 74: 37,74 BAD (FFFFFF)
|
||||
Size 75: 38,75 BAD (FFFFFF)
|
||||
Size 76: 38,76 BAD (FFFFFF)
|
||||
Size 77: 39,77 BAD (FFFFFF)
|
||||
Size 78: 39,78 BAD (FFFFFF)
|
||||
Size 79: 40,79 BAD (FFFFFF)
|
||||
Size 80: 40,80 BAD (FFFFFF)
|
||||
Size 81: 41,81 BAD (FFFFFF)
|
||||
Size 82: 41,82 BAD (FFFFFF)
|
||||
Size 83: 42,83 BAD (FFFFFF)
|
||||
Size 84: 42,84 BAD (FFFFFF)
|
||||
Size 85: 43,85 BAD (FFFFFF)
|
||||
Size 86: 43,86 BAD (FFFFFF)
|
||||
Size 87: 44,87 BAD (FFFFFF)
|
||||
Size 88: 44,88 BAD (FFFFFF)
|
||||
Size 89: 45,89 BAD (FFFFFF)
|
||||
Size 90: 45,90 BAD (FFFFFF)
|
||||
Size 91: 46,91 BAD (FFFFFF)
|
||||
Size 92: 46,92 BAD (FFFFFF)
|
||||
Size 93: 47,93 BAD (FFFFFF)
|
||||
Size 94: 47,94 BAD (FFFFFF)
|
||||
Size 95: 48,95 BAD (FFFFFF)
|
||||
Size 96: 48,97 BAD (FFFFFF)
|
||||
Size 97: 49,97 BAD (FFFFFF)
|
||||
Size 98: 49,98 BAD (FFFFFF)
|
||||
Size 99: 50,99 BAD (FFFFFF)
|
||||
Size 100: 50,100 BAD (FFFFFF)
|
||||
|
||||
Windows 10 14342 Old Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,2 BAD (FFFFHH)
|
||||
Size 2: 1,2 BAD (FFFFHH)
|
||||
Size 3: 2,3 BAD (FFFFFF)
|
||||
Size 4: 2,4 BAD (FFFFHH)
|
||||
Size 5: 3,5 BAD (FFFFFF)
|
||||
Size 6: 3,6 BAD (FFFFHH)
|
||||
Size 7: 4,7 BAD (FFFFFF)
|
||||
Size 8: 4,8 BAD (FFFFHH)
|
||||
Size 9: 5,9 BAD (FFFFFF)
|
||||
Size 10: 5,10 BAD (FFFFHH)
|
||||
Size 11: 6,11 BAD (FFFFFF)
|
||||
Size 12: 6,12 BAD (FFFFHH)
|
||||
Size 13: 7,13 BAD (FFFFFF)
|
||||
Size 14: 7,14 BAD (FFFFHH)
|
||||
Size 15: 8,15 BAD (FFFFFF)
|
||||
Size 16: 8,16 BAD (FFFFHH)
|
||||
Size 17: 9,17 BAD (FFFFFF)
|
||||
Size 18: 9,18 BAD (FFFFHH)
|
||||
Size 19: 10,19 BAD (FFFFFF)
|
||||
Size 20: 10,20 BAD (FFFFFF)
|
||||
Size 21: 11,21 BAD (FFFFFF)
|
||||
Size 22: 11,22 BAD (FFFFFF)
|
||||
Size 23: 12,23 BAD (FFFFFF)
|
||||
Size 24: 12,24 BAD (FFFFFF)
|
||||
Size 25: 13,25 BAD (FFFFFF)
|
||||
Size 26: 13,26 BAD (FFFFFF)
|
||||
Size 27: 14,27 BAD (FFFFFF)
|
||||
Size 28: 14,28 BAD (FFFFFF)
|
||||
Size 29: 15,29 BAD (FFFFFF)
|
||||
Size 30: 15,30 BAD (FFFFFF)
|
||||
Size 31: 16,31 BAD (FFFFFF)
|
||||
Size 32: 16,33 BAD (FFFFFF)
|
||||
Size 33: 17,33 BAD (FFFFFF)
|
||||
Size 34: 17,34 BAD (FFFFFF)
|
||||
Size 35: 18,35 BAD (FFFFFF)
|
||||
Size 36: 18,36 BAD (FFFFFF)
|
||||
Size 37: 19,37 BAD (FFFFFF)
|
||||
Size 38: 19,38 BAD (FFFFFF)
|
||||
Size 39: 20,39 BAD (FFFFFF)
|
||||
Size 40: 20,40 BAD (FFFFFF)
|
||||
Size 41: 21,41 BAD (FFFFFF)
|
||||
Size 42: 21,42 BAD (FFFFFF)
|
||||
Size 43: 22,43 BAD (FFFFFF)
|
||||
Size 44: 22,44 BAD (FFFFFF)
|
||||
Size 45: 23,45 BAD (FFFFFF)
|
||||
Size 46: 23,46 BAD (FFFFFF)
|
||||
Size 47: 24,47 BAD (FFFFFF)
|
||||
Size 48: 24,48 BAD (FFFFFF)
|
||||
Size 49: 25,49 BAD (FFFFFF)
|
||||
Size 50: 25,50 BAD (FFFFFF)
|
||||
Size 51: 26,51 BAD (FFFFFF)
|
||||
Size 52: 26,52 BAD (FFFFFF)
|
||||
Size 53: 27,53 BAD (FFFFFF)
|
||||
Size 54: 27,54 BAD (FFFFFF)
|
||||
Size 55: 28,55 BAD (FFFFFF)
|
||||
Size 56: 28,56 BAD (FFFFFF)
|
||||
Size 57: 29,57 BAD (FFFFFF)
|
||||
Size 58: 29,58 BAD (FFFFFF)
|
||||
Size 59: 30,59 BAD (FFFFFF)
|
||||
Size 60: 30,60 BAD (FFFFFF)
|
||||
Size 61: 31,61 BAD (FFFFFF)
|
||||
Size 62: 31,62 BAD (FFFFFF)
|
||||
Size 63: 32,63 BAD (FFFFFF)
|
||||
Size 64: 32,64 BAD (FFFFFF)
|
||||
Size 65: 33,65 BAD (FFFFFF)
|
||||
Size 66: 33,66 BAD (FFFFFF)
|
||||
Size 67: 34,67 BAD (FFFFFF)
|
||||
Size 68: 34,68 BAD (FFFFFF)
|
||||
Size 69: 35,69 BAD (FFFFFF)
|
||||
Size 70: 35,70 BAD (FFFFFF)
|
||||
Size 71: 36,71 BAD (FFFFFF)
|
||||
Size 72: 36,72 BAD (FFFFFF)
|
||||
Size 73: 37,73 BAD (FFFFFF)
|
||||
Size 74: 37,74 BAD (FFFFFF)
|
||||
Size 75: 38,75 BAD (FFFFFF)
|
||||
Size 76: 38,76 BAD (FFFFFF)
|
||||
Size 77: 39,77 BAD (FFFFFF)
|
||||
Size 78: 39,78 BAD (FFFFFF)
|
||||
Size 79: 40,79 BAD (FFFFFF)
|
||||
Size 80: 40,80 BAD (FFFFFF)
|
||||
Size 81: 41,81 BAD (FFFFFF)
|
||||
Size 82: 41,82 BAD (FFFFFF)
|
||||
Size 83: 42,83 BAD (FFFFFF)
|
||||
Size 84: 42,84 BAD (FFFFFF)
|
||||
Size 85: 43,85 BAD (FFFFFF)
|
||||
Size 86: 43,86 BAD (FFFFFF)
|
||||
Size 87: 44,87 BAD (FFFFFF)
|
||||
Size 88: 44,88 BAD (FFFFFF)
|
||||
Size 89: 45,89 BAD (FFFFFF)
|
||||
Size 90: 45,90 BAD (FFFFFF)
|
||||
Size 91: 46,91 BAD (FFFFFF)
|
||||
Size 92: 46,92 BAD (FFFFFF)
|
||||
Size 93: 47,93 BAD (FFFFFF)
|
||||
Size 94: 47,94 BAD (FFFFFF)
|
||||
Size 95: 48,95 BAD (FFFFFF)
|
||||
Size 96: 48,97 BAD (FFFFFF)
|
||||
Size 97: 49,97 BAD (FFFFFF)
|
||||
Size 98: 49,98 BAD (FFFFFF)
|
||||
Size 99: 50,99 BAD (FFFFFF)
|
||||
Size 100: 50,100 BAD (FFFFFF)
|
||||
|
||||
Windows 10 14342 New Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,1 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 OK (HHHFFF)
|
||||
Size 4: 2,4 OK (HHHFFF)
|
||||
Size 5: 3,5 OK (HHHFFF)
|
||||
Size 6: 3,6 OK (HHHFFF)
|
||||
Size 7: 4,7 OK (HHHFFF)
|
||||
Size 8: 4,8 OK (HHHFFF)
|
||||
Size 9: 5,9 OK (HHHFFF)
|
||||
Size 10: 5,10 OK (HHHFFF)
|
||||
Size 11: 6,11 OK (HHHFFF)
|
||||
Size 12: 6,12 OK (HHHFFF)
|
||||
Size 13: 7,13 OK (HHHFFF)
|
||||
Size 14: 7,14 OK (HHHFFF)
|
||||
Size 15: 8,15 OK (HHHFFF)
|
||||
Size 16: 8,16 OK (HHHFFF)
|
||||
Size 17: 9,17 OK (HHHFFF)
|
||||
Size 18: 9,18 OK (HHHFFF)
|
||||
Size 19: 10,19 OK (HHHFFF)
|
||||
Size 20: 10,20 OK (HHHFFF)
|
||||
Size 21: 11,21 OK (HHHFFF)
|
||||
Size 22: 11,22 OK (HHHFFF)
|
||||
Size 23: 12,23 OK (HHHFFF)
|
||||
Size 24: 12,24 OK (HHHFFF)
|
||||
Size 25: 13,25 OK (HHHFFF)
|
||||
Size 26: 13,26 OK (HHHFFF)
|
||||
Size 27: 14,27 OK (HHHFFF)
|
||||
Size 28: 14,28 OK (HHHFFF)
|
||||
Size 29: 15,29 OK (HHHFFF)
|
||||
Size 30: 15,30 OK (HHHFFF)
|
||||
Size 31: 16,31 OK (HHHFFF)
|
||||
Size 32: 16,32 OK (HHHFFF)
|
||||
Size 33: 17,33 OK (HHHFFF)
|
||||
Size 34: 17,34 OK (HHHFFF)
|
||||
Size 35: 18,35 OK (HHHFFF)
|
||||
Size 36: 18,36 OK (HHHFFF)
|
||||
Size 37: 19,37 OK (HHHFFF)
|
||||
Size 38: 19,38 OK (HHHFFF)
|
||||
Size 39: 20,39 OK (HHHFFF)
|
||||
Size 40: 20,40 OK (HHHFFF)
|
||||
Size 41: 21,41 OK (HHHFFF)
|
||||
Size 42: 21,42 OK (HHHFFF)
|
||||
Size 43: 22,43 OK (HHHFFF)
|
||||
Size 44: 22,44 OK (HHHFFF)
|
||||
Size 45: 23,45 OK (HHHFFF)
|
||||
Size 46: 23,46 OK (HHHFFF)
|
||||
Size 47: 24,47 OK (HHHFFF)
|
||||
Size 48: 24,48 OK (HHHFFF)
|
||||
Size 49: 25,49 OK (HHHFFF)
|
||||
Size 50: 25,50 OK (HHHFFF)
|
||||
Size 51: 26,51 OK (HHHFFF)
|
||||
Size 52: 26,52 OK (HHHFFF)
|
||||
Size 53: 27,53 OK (HHHFFF)
|
||||
Size 54: 27,54 OK (HHHFFF)
|
||||
Size 55: 28,55 OK (HHHFFF)
|
||||
Size 56: 28,56 OK (HHHFFF)
|
||||
Size 57: 29,57 OK (HHHFFF)
|
||||
Size 58: 29,58 OK (HHHFFF)
|
||||
Size 59: 30,59 OK (HHHFFF)
|
||||
Size 60: 30,60 OK (HHHFFF)
|
||||
Size 61: 31,61 OK (HHHFFF)
|
||||
Size 62: 31,62 OK (HHHFFF)
|
||||
Size 63: 32,63 OK (HHHFFF)
|
||||
Size 64: 32,64 OK (HHHFFF)
|
||||
Size 65: 33,65 OK (HHHFFF)
|
||||
Size 66: 33,66 OK (HHHFFF)
|
||||
Size 67: 34,67 OK (HHHFFF)
|
||||
Size 68: 34,68 OK (HHHFFF)
|
||||
Size 69: 35,69 OK (HHHFFF)
|
||||
Size 70: 35,70 OK (HHHFFF)
|
||||
Size 71: 36,71 OK (HHHFFF)
|
||||
Size 72: 36,72 OK (HHHFFF)
|
||||
Size 73: 37,73 OK (HHHFFF)
|
||||
Size 74: 37,74 OK (HHHFFF)
|
||||
Size 75: 38,75 OK (HHHFFF)
|
||||
Size 76: 38,76 OK (HHHFFF)
|
||||
Size 77: 39,77 OK (HHHFFF)
|
||||
Size 78: 39,78 OK (HHHFFF)
|
||||
Size 79: 40,79 OK (HHHFFF)
|
||||
Size 80: 40,80 OK (HHHFFF)
|
||||
Size 81: 41,81 OK (HHHFFF)
|
||||
Size 82: 41,82 OK (HHHFFF)
|
||||
Size 83: 42,83 OK (HHHFFF)
|
||||
Size 84: 42,84 OK (HHHFFF)
|
||||
Size 85: 43,85 OK (HHHFFF)
|
||||
Size 86: 43,86 OK (HHHFFF)
|
||||
Size 87: 44,87 OK (HHHFFF)
|
||||
Size 88: 44,88 OK (HHHFFF)
|
||||
Size 89: 45,89 OK (HHHFFF)
|
||||
Size 90: 45,90 OK (HHHFFF)
|
||||
Size 91: 46,91 OK (HHHFFF)
|
||||
Size 92: 46,92 OK (HHHFFF)
|
||||
Size 93: 47,93 OK (HHHFFF)
|
||||
Size 94: 47,94 OK (HHHFFF)
|
||||
Size 95: 48,95 OK (HHHFFF)
|
||||
Size 96: 48,96 OK (HHHFFF)
|
||||
Size 97: 49,97 OK (HHHFFF)
|
||||
Size 98: 49,98 OK (HHHFFF)
|
||||
Size 99: 50,99 OK (HHHFFF)
|
||||
Size 100: 50,100 OK (HHHFFF)
|
||||
Generated
Vendored
+630
@@ -0,0 +1,630 @@
|
||||
==========================================================
|
||||
Code Page 936, Chinese Simplified (China/PRC), SimSun font
|
||||
==========================================================
|
||||
|
||||
Options: -face-simsun -family 0x36
|
||||
Chars: A2 A3 2014 3044 30FC 4000
|
||||
|
||||
Vista
|
||||
-----
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,3 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (HHHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (HHHFHH)
|
||||
Size 8: 4,9 GOOD (HHFFFF)
|
||||
Size 9: 5,10 BAD (HHHFHH)
|
||||
Size 10: 5,11 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (HHHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,15 BAD (HHHFHH)
|
||||
Size 14: 7,16 GOOD (HHFFFF)
|
||||
Size 15: 8,17 BAD (HHHFHH)
|
||||
Size 16: 8,18 GOOD (HHFFFF)
|
||||
Size 17: 9,19 BAD (HHHFHH)
|
||||
Size 18: 9,21 GOOD (HHFFFF)
|
||||
Size 19: 10,22 BAD (HHHFHH)
|
||||
Size 20: 10,23 GOOD (HHFFFF)
|
||||
Size 21: 11,24 BAD (HHHFHH)
|
||||
Size 22: 11,25 GOOD (HHFFFF)
|
||||
Size 23: 12,26 BAD (HHHFHH)
|
||||
Size 24: 12,27 GOOD (HHFFFF)
|
||||
Size 25: 13,29 BAD (HHHFHH)
|
||||
Size 26: 13,30 GOOD (HHFFFF)
|
||||
Size 27: 14,31 BAD (HHHFHH)
|
||||
Size 28: 14,32 GOOD (HHFFFF)
|
||||
Size 29: 15,33 BAD (HHHFHH)
|
||||
Size 30: 15,34 GOOD (HHFFFF)
|
||||
Size 31: 16,35 BAD (HHHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,38 BAD (HHHFHH)
|
||||
Size 34: 17,39 GOOD (HHFFFF)
|
||||
Size 35: 18,40 BAD (HHHFHH)
|
||||
Size 36: 18,41 GOOD (HHFFFF)
|
||||
Size 37: 19,42 BAD (HHHFHH)
|
||||
Size 38: 19,43 GOOD (HHFFFF)
|
||||
Size 39: 20,44 BAD (HHHFHH)
|
||||
Size 40: 20,46 GOOD (HHFFFF)
|
||||
Size 41: 21,47 BAD (HHHFHH)
|
||||
Size 42: 21,48 GOOD (HHFFFF)
|
||||
Size 43: 22,49 BAD (HHHFHH)
|
||||
Size 44: 22,50 GOOD (HHFFFF)
|
||||
Size 45: 23,51 BAD (HHHFHH)
|
||||
Size 46: 23,52 GOOD (HHFFFF)
|
||||
Size 47: 24,54 BAD (HHHFHH)
|
||||
Size 48: 24,55 GOOD (HHFFFF)
|
||||
Size 49: 25,56 BAD (HHHFHH)
|
||||
Size 50: 25,57 GOOD (HHFFFF)
|
||||
Size 51: 26,58 BAD (HHHFHH)
|
||||
Size 52: 26,59 GOOD (HHFFFF)
|
||||
Size 53: 27,60 BAD (HHHFHH)
|
||||
Size 54: 27,62 GOOD (HHFFFF)
|
||||
Size 55: 28,63 BAD (HHHFHH)
|
||||
Size 56: 28,64 GOOD (HHFFFF)
|
||||
Size 57: 29,65 BAD (HHHFHH)
|
||||
Size 58: 29,66 GOOD (HHFFFF)
|
||||
Size 59: 30,67 BAD (HHHFHH)
|
||||
Size 60: 30,68 GOOD (HHFFFF)
|
||||
Size 61: 31,70 BAD (HHHFHH)
|
||||
Size 62: 31,71 GOOD (HHFFFF)
|
||||
Size 63: 32,72 BAD (HHHFHH)
|
||||
Size 64: 32,73 GOOD (HHFFFF)
|
||||
Size 65: 33,74 GOOD (HHFFFF)
|
||||
Size 66: 33,75 GOOD (HHFFFF)
|
||||
Size 67: 34,76 GOOD (HHFFFF)
|
||||
Size 68: 34,78 GOOD (HHFFFF)
|
||||
Size 69: 35,79 GOOD (HHFFFF)
|
||||
Size 70: 35,80 GOOD (HHFFFF)
|
||||
Size 71: 36,81 GOOD (HHFFFF)
|
||||
Size 72: 36,82 GOOD (HHFFFF)
|
||||
Size 73: 37,83 GOOD (HHFFFF)
|
||||
Size 74: 37,84 GOOD (HHFFFF)
|
||||
Size 75: 38,86 GOOD (HHFFFF)
|
||||
Size 76: 38,87 GOOD (HHFFFF)
|
||||
Size 77: 39,88 GOOD (HHFFFF)
|
||||
Size 78: 39,89 GOOD (HHFFFF)
|
||||
Size 79: 40,90 GOOD (HHFFFF)
|
||||
Size 80: 40,91 GOOD (HHFFFF)
|
||||
Size 81: 41,92 GOOD (HHFFFF)
|
||||
Size 82: 41,94 GOOD (HHFFFF)
|
||||
Size 83: 42,95 GOOD (HHFFFF)
|
||||
Size 84: 42,96 GOOD (HHFFFF)
|
||||
Size 85: 43,97 GOOD (HHFFFF)
|
||||
Size 86: 43,98 GOOD (HHFFFF)
|
||||
Size 87: 44,99 GOOD (HHFFFF)
|
||||
Size 88: 44,100 GOOD (HHFFFF)
|
||||
Size 89: 45,102 GOOD (HHFFFF)
|
||||
Size 90: 45,103 GOOD (HHFFFF)
|
||||
Size 91: 46,104 GOOD (HHFFFF)
|
||||
Size 92: 46,105 GOOD (HHFFFF)
|
||||
Size 93: 47,106 GOOD (HHFFFF)
|
||||
Size 94: 47,107 GOOD (HHFFFF)
|
||||
Size 95: 48,108 GOOD (HHFFFF)
|
||||
Size 96: 48,111 GOOD (HHFFFF)
|
||||
Size 97: 49,111 GOOD (HHFFFF)
|
||||
Size 98: 49,112 GOOD (HHFFFF)
|
||||
Size 99: 50,113 GOOD (HHFFFF)
|
||||
Size 100: 50,114 GOOD (HHFFFF)
|
||||
|
||||
Windows 7
|
||||
---------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,3 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,9 GOOD (HHFFFF)
|
||||
Size 9: 5,10 BAD (FFHFHH)
|
||||
Size 10: 5,11 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,15 BAD (FFHFHH)
|
||||
Size 14: 7,16 GOOD (HHFFFF)
|
||||
Size 15: 8,17 BAD (FFHFHH)
|
||||
Size 16: 8,18 GOOD (HHFFFF)
|
||||
Size 17: 9,19 BAD (FFHFHH)
|
||||
Size 18: 9,21 GOOD (HHFFFF)
|
||||
Size 19: 10,22 BAD (FFHFHH)
|
||||
Size 20: 10,23 GOOD (HHFFFF)
|
||||
Size 21: 11,24 BAD (FFHFHH)
|
||||
Size 22: 11,25 GOOD (HHFFFF)
|
||||
Size 23: 12,26 BAD (FFHFHH)
|
||||
Size 24: 12,27 GOOD (HHFFFF)
|
||||
Size 25: 13,29 BAD (FFHFHH)
|
||||
Size 26: 13,30 GOOD (HHFFFF)
|
||||
Size 27: 14,31 BAD (FFHFHH)
|
||||
Size 28: 14,32 GOOD (HHFFFF)
|
||||
Size 29: 15,33 BAD (FFHFHH)
|
||||
Size 30: 15,34 GOOD (HHFFFF)
|
||||
Size 31: 16,35 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,38 BAD (FFHFHH)
|
||||
Size 34: 17,39 GOOD (HHFFFF)
|
||||
Size 35: 18,40 BAD (FFHFHH)
|
||||
Size 36: 18,41 GOOD (HHFFFF)
|
||||
Size 37: 19,42 BAD (FFHFHH)
|
||||
Size 38: 19,43 GOOD (HHFFFF)
|
||||
Size 39: 20,44 BAD (FFHFHH)
|
||||
Size 40: 20,46 GOOD (HHFFFF)
|
||||
Size 41: 21,47 BAD (FFHFHH)
|
||||
Size 42: 21,48 GOOD (HHFFFF)
|
||||
Size 43: 22,49 BAD (FFHFHH)
|
||||
Size 44: 22,50 GOOD (HHFFFF)
|
||||
Size 45: 23,51 BAD (FFHFHH)
|
||||
Size 46: 23,52 GOOD (HHFFFF)
|
||||
Size 47: 24,54 BAD (FFHFHH)
|
||||
Size 48: 24,55 GOOD (HHFFFF)
|
||||
Size 49: 25,56 BAD (FFHFHH)
|
||||
Size 50: 25,57 GOOD (HHFFFF)
|
||||
Size 51: 26,58 BAD (FFHFHH)
|
||||
Size 52: 26,59 GOOD (HHFFFF)
|
||||
Size 53: 27,60 BAD (FFHFHH)
|
||||
Size 54: 27,62 GOOD (HHFFFF)
|
||||
Size 55: 28,63 BAD (FFHFHH)
|
||||
Size 56: 28,64 GOOD (HHFFFF)
|
||||
Size 57: 29,65 BAD (FFHFHH)
|
||||
Size 58: 29,66 GOOD (HHFFFF)
|
||||
Size 59: 30,67 BAD (FFHFHH)
|
||||
Size 60: 30,68 GOOD (HHFFFF)
|
||||
Size 61: 31,70 BAD (FFHFHH)
|
||||
Size 62: 31,71 GOOD (HHFFFF)
|
||||
Size 63: 32,72 BAD (FFHFHH)
|
||||
Size 64: 32,73 GOOD (HHFFFF)
|
||||
Size 65: 33,74 GOOD (HHFFFF)
|
||||
Size 66: 33,75 GOOD (HHFFFF)
|
||||
Size 67: 34,76 GOOD (HHFFFF)
|
||||
Size 68: 34,78 GOOD (HHFFFF)
|
||||
Size 69: 35,79 GOOD (HHFFFF)
|
||||
Size 70: 35,80 GOOD (HHFFFF)
|
||||
Size 71: 36,81 GOOD (HHFFFF)
|
||||
Size 72: 36,82 GOOD (HHFFFF)
|
||||
Size 73: 37,83 GOOD (HHFFFF)
|
||||
Size 74: 37,84 GOOD (HHFFFF)
|
||||
Size 75: 38,86 GOOD (HHFFFF)
|
||||
Size 76: 38,87 GOOD (HHFFFF)
|
||||
Size 77: 39,88 GOOD (HHFFFF)
|
||||
Size 78: 39,89 GOOD (HHFFFF)
|
||||
Size 79: 40,90 GOOD (HHFFFF)
|
||||
Size 80: 40,91 GOOD (HHFFFF)
|
||||
Size 81: 41,92 GOOD (HHFFFF)
|
||||
Size 82: 41,94 GOOD (HHFFFF)
|
||||
Size 83: 42,95 GOOD (HHFFFF)
|
||||
Size 84: 42,96 GOOD (HHFFFF)
|
||||
Size 85: 43,97 GOOD (HHFFFF)
|
||||
Size 86: 43,98 GOOD (HHFFFF)
|
||||
Size 87: 44,99 GOOD (HHFFFF)
|
||||
Size 88: 44,100 GOOD (HHFFFF)
|
||||
Size 89: 45,102 GOOD (HHFFFF)
|
||||
Size 90: 45,103 GOOD (HHFFFF)
|
||||
Size 91: 46,104 GOOD (HHFFFF)
|
||||
Size 92: 46,105 GOOD (HHFFFF)
|
||||
Size 93: 47,106 GOOD (HHFFFF)
|
||||
Size 94: 47,107 GOOD (HHFFFF)
|
||||
Size 95: 48,108 GOOD (HHFFFF)
|
||||
Size 96: 48,111 GOOD (HHFFFF)
|
||||
Size 97: 49,111 GOOD (HHFFFF)
|
||||
Size 98: 49,112 GOOD (HHFFFF)
|
||||
Size 99: 50,113 GOOD (HHFFFF)
|
||||
Size 100: 50,114 GOOD (HHFFFF)
|
||||
|
||||
Windows 8
|
||||
---------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,3 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,9 GOOD (HHFFFF)
|
||||
Size 9: 5,10 BAD (FFHFHH)
|
||||
Size 10: 5,11 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,15 BAD (FFHFHH)
|
||||
Size 14: 7,16 GOOD (HHFFFF)
|
||||
Size 15: 8,17 BAD (FFHFHH)
|
||||
Size 16: 8,18 GOOD (HHFFFF)
|
||||
Size 17: 9,19 BAD (FFHFHH)
|
||||
Size 18: 9,21 GOOD (HHFFFF)
|
||||
Size 19: 10,22 BAD (FFHFHH)
|
||||
Size 20: 10,23 GOOD (HHFFFF)
|
||||
Size 21: 11,24 BAD (FFHFHH)
|
||||
Size 22: 11,25 GOOD (HHFFFF)
|
||||
Size 23: 12,26 BAD (FFHFHH)
|
||||
Size 24: 12,27 GOOD (HHFFFF)
|
||||
Size 25: 13,29 BAD (FFHFHH)
|
||||
Size 26: 13,30 GOOD (HHFFFF)
|
||||
Size 27: 14,31 BAD (FFHFHH)
|
||||
Size 28: 14,32 GOOD (HHFFFF)
|
||||
Size 29: 15,33 BAD (FFHFHH)
|
||||
Size 30: 15,34 GOOD (HHFFFF)
|
||||
Size 31: 16,35 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,38 BAD (FFHFHH)
|
||||
Size 34: 17,39 GOOD (HHFFFF)
|
||||
Size 35: 18,40 BAD (FFHFHH)
|
||||
Size 36: 18,41 GOOD (HHFFFF)
|
||||
Size 37: 19,42 BAD (FFHFHH)
|
||||
Size 38: 19,43 GOOD (HHFFFF)
|
||||
Size 39: 20,44 BAD (FFHFHH)
|
||||
Size 40: 20,46 GOOD (HHFFFF)
|
||||
Size 41: 21,47 BAD (FFHFHH)
|
||||
Size 42: 21,48 GOOD (HHFFFF)
|
||||
Size 43: 22,49 BAD (FFHFHH)
|
||||
Size 44: 22,50 GOOD (HHFFFF)
|
||||
Size 45: 23,51 BAD (FFHFHH)
|
||||
Size 46: 23,52 GOOD (HHFFFF)
|
||||
Size 47: 24,54 BAD (FFHFHH)
|
||||
Size 48: 24,55 GOOD (HHFFFF)
|
||||
Size 49: 25,56 BAD (FFHFHH)
|
||||
Size 50: 25,57 GOOD (HHFFFF)
|
||||
Size 51: 26,58 BAD (FFHFHH)
|
||||
Size 52: 26,59 GOOD (HHFFFF)
|
||||
Size 53: 27,60 BAD (FFHFHH)
|
||||
Size 54: 27,62 GOOD (HHFFFF)
|
||||
Size 55: 28,63 BAD (FFHFHH)
|
||||
Size 56: 28,64 GOOD (HHFFFF)
|
||||
Size 57: 29,65 BAD (FFHFHH)
|
||||
Size 58: 29,66 GOOD (HHFFFF)
|
||||
Size 59: 30,67 BAD (FFHFHH)
|
||||
Size 60: 30,68 GOOD (HHFFFF)
|
||||
Size 61: 31,70 BAD (FFHFHH)
|
||||
Size 62: 31,71 GOOD (HHFFFF)
|
||||
Size 63: 32,72 BAD (FFHFHH)
|
||||
Size 64: 32,73 GOOD (HHFFFF)
|
||||
Size 65: 33,74 GOOD (HHFFFF)
|
||||
Size 66: 33,75 GOOD (HHFFFF)
|
||||
Size 67: 34,76 GOOD (HHFFFF)
|
||||
Size 68: 34,78 GOOD (HHFFFF)
|
||||
Size 69: 35,79 GOOD (HHFFFF)
|
||||
Size 70: 35,80 GOOD (HHFFFF)
|
||||
Size 71: 36,81 GOOD (HHFFFF)
|
||||
Size 72: 36,82 GOOD (HHFFFF)
|
||||
Size 73: 37,83 GOOD (HHFFFF)
|
||||
Size 74: 37,84 GOOD (HHFFFF)
|
||||
Size 75: 38,86 GOOD (HHFFFF)
|
||||
Size 76: 38,87 GOOD (HHFFFF)
|
||||
Size 77: 39,88 GOOD (HHFFFF)
|
||||
Size 78: 39,89 GOOD (HHFFFF)
|
||||
Size 79: 40,90 GOOD (HHFFFF)
|
||||
Size 80: 40,91 GOOD (HHFFFF)
|
||||
Size 81: 41,92 GOOD (HHFFFF)
|
||||
Size 82: 41,94 GOOD (HHFFFF)
|
||||
Size 83: 42,95 GOOD (HHFFFF)
|
||||
Size 84: 42,96 GOOD (HHFFFF)
|
||||
Size 85: 43,97 GOOD (HHFFFF)
|
||||
Size 86: 43,98 GOOD (HHFFFF)
|
||||
Size 87: 44,99 GOOD (HHFFFF)
|
||||
Size 88: 44,100 GOOD (HHFFFF)
|
||||
Size 89: 45,102 GOOD (HHFFFF)
|
||||
Size 90: 45,103 GOOD (HHFFFF)
|
||||
Size 91: 46,104 GOOD (HHFFFF)
|
||||
Size 92: 46,105 GOOD (HHFFFF)
|
||||
Size 93: 47,106 GOOD (HHFFFF)
|
||||
Size 94: 47,107 GOOD (HHFFFF)
|
||||
Size 95: 48,108 GOOD (HHFFFF)
|
||||
Size 96: 48,111 GOOD (HHFFFF)
|
||||
Size 97: 49,111 GOOD (HHFFFF)
|
||||
Size 98: 49,112 GOOD (HHFFFF)
|
||||
Size 99: 50,113 GOOD (HHFFFF)
|
||||
Size 100: 50,114 GOOD (HHFFFF)
|
||||
|
||||
Windows 8.1
|
||||
-----------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,3 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,9 GOOD (HHFFFF)
|
||||
Size 9: 5,10 BAD (FFHFHH)
|
||||
Size 10: 5,11 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,15 BAD (FFHFHH)
|
||||
Size 14: 7,16 GOOD (HHFFFF)
|
||||
Size 15: 8,17 BAD (FFHFHH)
|
||||
Size 16: 8,18 GOOD (HHFFFF)
|
||||
Size 17: 9,19 BAD (FFHFHH)
|
||||
Size 18: 9,21 GOOD (HHFFFF)
|
||||
Size 19: 10,22 BAD (FFHFHH)
|
||||
Size 20: 10,23 GOOD (HHFFFF)
|
||||
Size 21: 11,24 BAD (FFHFHH)
|
||||
Size 22: 11,25 GOOD (HHFFFF)
|
||||
Size 23: 12,26 BAD (FFHFHH)
|
||||
Size 24: 12,27 GOOD (HHFFFF)
|
||||
Size 25: 13,29 BAD (FFHFHH)
|
||||
Size 26: 13,30 GOOD (HHFFFF)
|
||||
Size 27: 14,31 BAD (FFHFHH)
|
||||
Size 28: 14,32 GOOD (HHFFFF)
|
||||
Size 29: 15,33 BAD (FFHFHH)
|
||||
Size 30: 15,34 GOOD (HHFFFF)
|
||||
Size 31: 16,35 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,38 BAD (FFHFHH)
|
||||
Size 34: 17,39 GOOD (HHFFFF)
|
||||
Size 35: 18,40 BAD (FFHFHH)
|
||||
Size 36: 18,41 GOOD (HHFFFF)
|
||||
Size 37: 19,42 BAD (FFHFHH)
|
||||
Size 38: 19,43 GOOD (HHFFFF)
|
||||
Size 39: 20,44 BAD (FFHFHH)
|
||||
Size 40: 20,46 GOOD (HHFFFF)
|
||||
Size 41: 21,47 BAD (FFHFHH)
|
||||
Size 42: 21,48 GOOD (HHFFFF)
|
||||
Size 43: 22,49 BAD (FFHFHH)
|
||||
Size 44: 22,50 GOOD (HHFFFF)
|
||||
Size 45: 23,51 BAD (FFHFHH)
|
||||
Size 46: 23,52 GOOD (HHFFFF)
|
||||
Size 47: 24,54 BAD (FFHFHH)
|
||||
Size 48: 24,55 GOOD (HHFFFF)
|
||||
Size 49: 25,56 BAD (FFHFHH)
|
||||
Size 50: 25,57 GOOD (HHFFFF)
|
||||
Size 51: 26,58 BAD (FFHFHH)
|
||||
Size 52: 26,59 GOOD (HHFFFF)
|
||||
Size 53: 27,60 BAD (FFHFHH)
|
||||
Size 54: 27,62 GOOD (HHFFFF)
|
||||
Size 55: 28,63 BAD (FFHFHH)
|
||||
Size 56: 28,64 GOOD (HHFFFF)
|
||||
Size 57: 29,65 BAD (FFHFHH)
|
||||
Size 58: 29,66 GOOD (HHFFFF)
|
||||
Size 59: 30,67 BAD (FFHFHH)
|
||||
Size 60: 30,68 GOOD (HHFFFF)
|
||||
Size 61: 31,70 BAD (FFHFHH)
|
||||
Size 62: 31,71 GOOD (HHFFFF)
|
||||
Size 63: 32,72 BAD (FFHFHH)
|
||||
Size 64: 32,73 GOOD (HHFFFF)
|
||||
Size 65: 33,74 GOOD (HHFFFF)
|
||||
Size 66: 33,75 GOOD (HHFFFF)
|
||||
Size 67: 34,76 GOOD (HHFFFF)
|
||||
Size 68: 34,78 GOOD (HHFFFF)
|
||||
Size 69: 35,79 GOOD (HHFFFF)
|
||||
Size 70: 35,80 GOOD (HHFFFF)
|
||||
Size 71: 36,81 GOOD (HHFFFF)
|
||||
Size 72: 36,82 GOOD (HHFFFF)
|
||||
Size 73: 37,83 GOOD (HHFFFF)
|
||||
Size 74: 37,84 GOOD (HHFFFF)
|
||||
Size 75: 38,86 GOOD (HHFFFF)
|
||||
Size 76: 38,87 GOOD (HHFFFF)
|
||||
Size 77: 39,88 GOOD (HHFFFF)
|
||||
Size 78: 39,89 GOOD (HHFFFF)
|
||||
Size 79: 40,90 GOOD (HHFFFF)
|
||||
Size 80: 40,91 GOOD (HHFFFF)
|
||||
Size 81: 41,92 GOOD (HHFFFF)
|
||||
Size 82: 41,94 GOOD (HHFFFF)
|
||||
Size 83: 42,95 GOOD (HHFFFF)
|
||||
Size 84: 42,96 GOOD (HHFFFF)
|
||||
Size 85: 43,97 GOOD (HHFFFF)
|
||||
Size 86: 43,98 GOOD (HHFFFF)
|
||||
Size 87: 44,99 GOOD (HHFFFF)
|
||||
Size 88: 44,100 GOOD (HHFFFF)
|
||||
Size 89: 45,102 GOOD (HHFFFF)
|
||||
Size 90: 45,103 GOOD (HHFFFF)
|
||||
Size 91: 46,104 GOOD (HHFFFF)
|
||||
Size 92: 46,105 GOOD (HHFFFF)
|
||||
Size 93: 47,106 GOOD (HHFFFF)
|
||||
Size 94: 47,107 GOOD (HHFFFF)
|
||||
Size 95: 48,108 GOOD (HHFFFF)
|
||||
Size 96: 48,111 GOOD (HHFFFF)
|
||||
Size 97: 49,111 GOOD (HHFFFF)
|
||||
Size 98: 49,112 GOOD (HHFFFF)
|
||||
Size 99: 50,113 GOOD (HHFFFF)
|
||||
Size 100: 50,114 GOOD (HHFFFF)
|
||||
|
||||
Windows 10 14342 Old Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,3 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,9 GOOD (HHFFFF)
|
||||
Size 9: 5,10 BAD (FFHFHH)
|
||||
Size 10: 5,11 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,15 BAD (FFHFHH)
|
||||
Size 14: 7,16 GOOD (HHFFFF)
|
||||
Size 15: 8,17 BAD (FFHFHH)
|
||||
Size 16: 8,18 GOOD (HHFFFF)
|
||||
Size 17: 9,19 BAD (FFHFHH)
|
||||
Size 18: 9,21 GOOD (HHFFFF)
|
||||
Size 19: 10,22 BAD (FFHFHH)
|
||||
Size 20: 10,23 GOOD (HHFFFF)
|
||||
Size 21: 11,24 BAD (FFHFHH)
|
||||
Size 22: 11,25 GOOD (HHFFFF)
|
||||
Size 23: 12,26 BAD (FFHFHH)
|
||||
Size 24: 12,27 GOOD (HHFFFF)
|
||||
Size 25: 13,29 BAD (FFHFHH)
|
||||
Size 26: 13,30 GOOD (HHFFFF)
|
||||
Size 27: 14,31 BAD (FFHFHH)
|
||||
Size 28: 14,32 GOOD (HHFFFF)
|
||||
Size 29: 15,33 BAD (FFHFHH)
|
||||
Size 30: 15,34 GOOD (HHFFFF)
|
||||
Size 31: 16,35 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,38 BAD (FFHFHH)
|
||||
Size 34: 17,39 GOOD (HHFFFF)
|
||||
Size 35: 18,40 BAD (FFHFHH)
|
||||
Size 36: 18,41 GOOD (HHFFFF)
|
||||
Size 37: 19,42 BAD (FFHFHH)
|
||||
Size 38: 19,43 GOOD (HHFFFF)
|
||||
Size 39: 20,44 BAD (FFHFHH)
|
||||
Size 40: 20,46 GOOD (HHFFFF)
|
||||
Size 41: 21,47 BAD (FFHFHH)
|
||||
Size 42: 21,48 GOOD (HHFFFF)
|
||||
Size 43: 22,49 BAD (FFHFHH)
|
||||
Size 44: 22,50 GOOD (HHFFFF)
|
||||
Size 45: 23,51 BAD (FFHFHH)
|
||||
Size 46: 23,52 GOOD (HHFFFF)
|
||||
Size 47: 24,54 BAD (FFHFHH)
|
||||
Size 48: 24,55 GOOD (HHFFFF)
|
||||
Size 49: 25,56 BAD (FFHFHH)
|
||||
Size 50: 25,57 GOOD (HHFFFF)
|
||||
Size 51: 26,58 BAD (FFHFHH)
|
||||
Size 52: 26,59 GOOD (HHFFFF)
|
||||
Size 53: 27,60 BAD (FFHFHH)
|
||||
Size 54: 27,62 GOOD (HHFFFF)
|
||||
Size 55: 28,63 BAD (FFHFHH)
|
||||
Size 56: 28,64 GOOD (HHFFFF)
|
||||
Size 57: 29,65 BAD (FFHFHH)
|
||||
Size 58: 29,66 GOOD (HHFFFF)
|
||||
Size 59: 30,67 BAD (FFHFHH)
|
||||
Size 60: 30,68 GOOD (HHFFFF)
|
||||
Size 61: 31,70 BAD (FFHFHH)
|
||||
Size 62: 31,71 GOOD (HHFFFF)
|
||||
Size 63: 32,72 BAD (FFHFHH)
|
||||
Size 64: 32,73 GOOD (HHFFFF)
|
||||
Size 65: 33,74 GOOD (HHFFFF)
|
||||
Size 66: 33,75 GOOD (HHFFFF)
|
||||
Size 67: 34,76 GOOD (HHFFFF)
|
||||
Size 68: 34,78 GOOD (HHFFFF)
|
||||
Size 69: 35,79 GOOD (HHFFFF)
|
||||
Size 70: 35,80 GOOD (HHFFFF)
|
||||
Size 71: 36,81 GOOD (HHFFFF)
|
||||
Size 72: 36,82 GOOD (HHFFFF)
|
||||
Size 73: 37,83 GOOD (HHFFFF)
|
||||
Size 74: 37,84 GOOD (HHFFFF)
|
||||
Size 75: 38,86 GOOD (HHFFFF)
|
||||
Size 76: 38,87 GOOD (HHFFFF)
|
||||
Size 77: 39,88 GOOD (HHFFFF)
|
||||
Size 78: 39,89 GOOD (HHFFFF)
|
||||
Size 79: 40,90 GOOD (HHFFFF)
|
||||
Size 80: 40,91 GOOD (HHFFFF)
|
||||
Size 81: 41,92 GOOD (HHFFFF)
|
||||
Size 82: 41,94 GOOD (HHFFFF)
|
||||
Size 83: 42,95 GOOD (HHFFFF)
|
||||
Size 84: 42,96 GOOD (HHFFFF)
|
||||
Size 85: 43,97 GOOD (HHFFFF)
|
||||
Size 86: 43,98 GOOD (HHFFFF)
|
||||
Size 87: 44,99 GOOD (HHFFFF)
|
||||
Size 88: 44,100 GOOD (HHFFFF)
|
||||
Size 89: 45,102 GOOD (HHFFFF)
|
||||
Size 90: 45,103 GOOD (HHFFFF)
|
||||
Size 91: 46,104 GOOD (HHFFFF)
|
||||
Size 92: 46,105 GOOD (HHFFFF)
|
||||
Size 93: 47,106 GOOD (HHFFFF)
|
||||
Size 94: 47,107 GOOD (HHFFFF)
|
||||
Size 95: 48,108 GOOD (HHFFFF)
|
||||
Size 96: 48,111 GOOD (HHFFFF)
|
||||
Size 97: 49,111 GOOD (HHFFFF)
|
||||
Size 98: 49,112 GOOD (HHFFFF)
|
||||
Size 99: 50,113 GOOD (HHFFFF)
|
||||
Size 100: 50,114 GOOD (HHFFFF)
|
||||
|
||||
Windows 10 14342 New Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,1 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,3 GOOD (HHFFFF)
|
||||
Size 4: 2,4 GOOD (HHFFFF)
|
||||
Size 5: 3,5 GOOD (HHFFFF)
|
||||
Size 6: 3,6 GOOD (HHFFFF)
|
||||
Size 7: 4,7 GOOD (HHFFFF)
|
||||
Size 8: 4,8 GOOD (HHFFFF)
|
||||
Size 9: 5,9 GOOD (HHFFFF)
|
||||
Size 10: 5,10 GOOD (HHFFFF)
|
||||
Size 11: 6,11 GOOD (HHFFFF)
|
||||
Size 12: 6,12 GOOD (HHFFFF)
|
||||
Size 13: 7,13 GOOD (HHFFFF)
|
||||
Size 14: 7,14 GOOD (HHFFFF)
|
||||
Size 15: 8,15 GOOD (HHFFFF)
|
||||
Size 16: 8,16 GOOD (HHFFFF)
|
||||
Size 17: 9,17 GOOD (HHFFFF)
|
||||
Size 18: 9,18 GOOD (HHFFFF)
|
||||
Size 19: 10,19 GOOD (HHFFFF)
|
||||
Size 20: 10,20 GOOD (HHFFFF)
|
||||
Size 21: 11,21 GOOD (HHFFFF)
|
||||
Size 22: 11,22 GOOD (HHFFFF)
|
||||
Size 23: 12,23 GOOD (HHFFFF)
|
||||
Size 24: 12,24 GOOD (HHFFFF)
|
||||
Size 25: 13,25 GOOD (HHFFFF)
|
||||
Size 26: 13,26 GOOD (HHFFFF)
|
||||
Size 27: 14,27 GOOD (HHFFFF)
|
||||
Size 28: 14,28 GOOD (HHFFFF)
|
||||
Size 29: 15,29 GOOD (HHFFFF)
|
||||
Size 30: 15,30 GOOD (HHFFFF)
|
||||
Size 31: 16,31 GOOD (HHFFFF)
|
||||
Size 32: 16,32 GOOD (HHFFFF)
|
||||
Size 33: 17,33 GOOD (HHFFFF)
|
||||
Size 34: 17,34 GOOD (HHFFFF)
|
||||
Size 35: 18,35 GOOD (HHFFFF)
|
||||
Size 36: 18,36 GOOD (HHFFFF)
|
||||
Size 37: 19,37 GOOD (HHFFFF)
|
||||
Size 38: 19,38 GOOD (HHFFFF)
|
||||
Size 39: 20,39 GOOD (HHFFFF)
|
||||
Size 40: 20,40 GOOD (HHFFFF)
|
||||
Size 41: 21,41 GOOD (HHFFFF)
|
||||
Size 42: 21,42 GOOD (HHFFFF)
|
||||
Size 43: 22,43 GOOD (HHFFFF)
|
||||
Size 44: 22,44 GOOD (HHFFFF)
|
||||
Size 45: 23,45 GOOD (HHFFFF)
|
||||
Size 46: 23,46 GOOD (HHFFFF)
|
||||
Size 47: 24,47 GOOD (HHFFFF)
|
||||
Size 48: 24,48 GOOD (HHFFFF)
|
||||
Size 49: 25,49 GOOD (HHFFFF)
|
||||
Size 50: 25,50 GOOD (HHFFFF)
|
||||
Size 51: 26,51 GOOD (HHFFFF)
|
||||
Size 52: 26,52 GOOD (HHFFFF)
|
||||
Size 53: 27,53 GOOD (HHFFFF)
|
||||
Size 54: 27,54 GOOD (HHFFFF)
|
||||
Size 55: 28,55 GOOD (HHFFFF)
|
||||
Size 56: 28,56 GOOD (HHFFFF)
|
||||
Size 57: 29,57 GOOD (HHFFFF)
|
||||
Size 58: 29,58 GOOD (HHFFFF)
|
||||
Size 59: 30,59 GOOD (HHFFFF)
|
||||
Size 60: 30,60 GOOD (HHFFFF)
|
||||
Size 61: 31,61 GOOD (HHFFFF)
|
||||
Size 62: 31,62 GOOD (HHFFFF)
|
||||
Size 63: 32,63 GOOD (HHFFFF)
|
||||
Size 64: 32,64 GOOD (HHFFFF)
|
||||
Size 65: 33,65 GOOD (HHFFFF)
|
||||
Size 66: 33,66 GOOD (HHFFFF)
|
||||
Size 67: 34,67 GOOD (HHFFFF)
|
||||
Size 68: 34,68 GOOD (HHFFFF)
|
||||
Size 69: 35,69 GOOD (HHFFFF)
|
||||
Size 70: 35,70 GOOD (HHFFFF)
|
||||
Size 71: 36,71 GOOD (HHFFFF)
|
||||
Size 72: 36,72 GOOD (HHFFFF)
|
||||
Size 73: 37,73 GOOD (HHFFFF)
|
||||
Size 74: 37,74 GOOD (HHFFFF)
|
||||
Size 75: 38,75 GOOD (HHFFFF)
|
||||
Size 76: 38,76 GOOD (HHFFFF)
|
||||
Size 77: 39,77 GOOD (HHFFFF)
|
||||
Size 78: 39,78 GOOD (HHFFFF)
|
||||
Size 79: 40,79 GOOD (HHFFFF)
|
||||
Size 80: 40,80 GOOD (HHFFFF)
|
||||
Size 81: 41,81 GOOD (HHFFFF)
|
||||
Size 82: 41,82 GOOD (HHFFFF)
|
||||
Size 83: 42,83 GOOD (HHFFFF)
|
||||
Size 84: 42,84 GOOD (HHFFFF)
|
||||
Size 85: 43,85 GOOD (HHFFFF)
|
||||
Size 86: 43,86 GOOD (HHFFFF)
|
||||
Size 87: 44,87 GOOD (HHFFFF)
|
||||
Size 88: 44,88 GOOD (HHFFFF)
|
||||
Size 89: 45,89 GOOD (HHFFFF)
|
||||
Size 90: 45,90 GOOD (HHFFFF)
|
||||
Size 91: 46,91 GOOD (HHFFFF)
|
||||
Size 92: 46,92 GOOD (HHFFFF)
|
||||
Size 93: 47,93 GOOD (HHFFFF)
|
||||
Size 94: 47,94 GOOD (HHFFFF)
|
||||
Size 95: 48,95 GOOD (HHFFFF)
|
||||
Size 96: 48,96 GOOD (HHFFFF)
|
||||
Size 97: 49,97 GOOD (HHFFFF)
|
||||
Size 98: 49,98 GOOD (HHFFFF)
|
||||
Size 99: 50,99 GOOD (HHFFFF)
|
||||
Size 100: 50,100 GOOD (HHFFFF)
|
||||
Generated
Vendored
+630
@@ -0,0 +1,630 @@
|
||||
=====================================
|
||||
Code Page 949, Korean, GulimChe font
|
||||
=====================================
|
||||
|
||||
Options: -face-gulimche -family 0x36
|
||||
Chars: A2 A3 2014 3044 30FC 4000
|
||||
|
||||
Vista
|
||||
-----
|
||||
|
||||
Size 1: 1,2 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 BAD (FFFFHH)
|
||||
Size 4: 2,5 OK (HHHFFF)
|
||||
Size 5: 3,6 BAD (HHHFHH)
|
||||
Size 6: 3,7 OK (HHHFFF)
|
||||
Size 7: 4,8 BAD (HHHFHH)
|
||||
Size 8: 4,9 OK (HHHFFF)
|
||||
Size 9: 5,10 BAD (HHHFHH)
|
||||
Size 10: 5,11 OK (HHHFFF)
|
||||
Size 11: 6,13 BAD (HHHFHH)
|
||||
Size 12: 6,14 OK (HHHFFF)
|
||||
Size 13: 7,15 BAD (HHHFHH)
|
||||
Size 14: 7,16 OK (HHHFFF)
|
||||
Size 15: 8,17 BAD (HHHFHH)
|
||||
Size 16: 8,18 OK (HHHFFF)
|
||||
Size 17: 9,20 BAD (HHHFHH)
|
||||
Size 18: 9,21 OK (HHHFFF)
|
||||
Size 19: 10,22 BAD (HHHFHH)
|
||||
Size 20: 10,23 OK (HHHFFF)
|
||||
Size 21: 11,24 BAD (HHHFHH)
|
||||
Size 22: 11,25 OK (HHHFFF)
|
||||
Size 23: 12,26 BAD (HHHFHH)
|
||||
Size 24: 12,28 OK (HHHFFF)
|
||||
Size 25: 13,29 BAD (HHHFHH)
|
||||
Size 26: 13,30 OK (HHHFFF)
|
||||
Size 27: 14,31 BAD (HHHFHH)
|
||||
Size 28: 14,32 OK (HHHFFF)
|
||||
Size 29: 15,33 BAD (HHHFHH)
|
||||
Size 30: 15,34 OK (HHHFFF)
|
||||
Size 31: 16,36 BAD (HHHFHH)
|
||||
Size 32: 16,37 OK (HHHFFF)
|
||||
Size 33: 17,38 BAD (HHHFHH)
|
||||
Size 34: 17,39 OK (HHHFFF)
|
||||
Size 35: 18,40 BAD (HHHFHH)
|
||||
Size 36: 18,41 OK (HHHFFF)
|
||||
Size 37: 19,42 BAD (HHHFHH)
|
||||
Size 38: 19,44 OK (HHHFFF)
|
||||
Size 39: 20,45 BAD (HHHFHH)
|
||||
Size 40: 20,46 OK (HHHFFF)
|
||||
Size 41: 21,47 BAD (HHHFHH)
|
||||
Size 42: 21,48 OK (HHHFFF)
|
||||
Size 43: 22,49 BAD (HHHFHH)
|
||||
Size 44: 22,51 OK (HHHFFF)
|
||||
Size 45: 23,52 BAD (HHHFHH)
|
||||
Size 46: 23,53 OK (HHHFFF)
|
||||
Size 47: 24,54 BAD (HHHFHH)
|
||||
Size 48: 24,55 OK (HHHFFF)
|
||||
Size 49: 25,56 BAD (HHHFHH)
|
||||
Size 50: 25,57 OK (HHHFFF)
|
||||
Size 51: 26,59 BAD (HHHFHH)
|
||||
Size 52: 26,60 OK (HHHFFF)
|
||||
Size 53: 27,61 BAD (HHHFHH)
|
||||
Size 54: 27,62 OK (HHHFFF)
|
||||
Size 55: 28,63 BAD (HHHFHH)
|
||||
Size 56: 28,64 OK (HHHFFF)
|
||||
Size 57: 29,65 BAD (HHHFHH)
|
||||
Size 58: 29,67 OK (HHHFFF)
|
||||
Size 59: 30,68 BAD (HHHFHH)
|
||||
Size 60: 30,69 OK (HHHFFF)
|
||||
Size 61: 31,70 BAD (HHHFHH)
|
||||
Size 62: 31,71 OK (HHHFFF)
|
||||
Size 63: 32,72 BAD (HHHFHH)
|
||||
Size 64: 32,74 OK (HHHFFF)
|
||||
Size 65: 33,75 BAD (HHHFHH)
|
||||
Size 66: 33,76 OK (HHHFFF)
|
||||
Size 67: 34,77 BAD (HHHFHH)
|
||||
Size 68: 34,78 OK (HHHFFF)
|
||||
Size 69: 35,79 BAD (HHHFHH)
|
||||
Size 70: 35,80 OK (HHHFFF)
|
||||
Size 71: 36,82 BAD (HHHFHH)
|
||||
Size 72: 36,83 OK (HHHFFF)
|
||||
Size 73: 37,84 BAD (HHHFHH)
|
||||
Size 74: 37,85 OK (HHHFFF)
|
||||
Size 75: 38,86 BAD (HHHFHH)
|
||||
Size 76: 38,87 OK (HHHFFF)
|
||||
Size 77: 39,88 BAD (HHHFHH)
|
||||
Size 78: 39,90 OK (HHHFFF)
|
||||
Size 79: 40,91 BAD (HHHFHH)
|
||||
Size 80: 40,92 OK (HHHFFF)
|
||||
Size 81: 41,93 BAD (HHHFHH)
|
||||
Size 82: 41,94 OK (HHHFFF)
|
||||
Size 83: 42,95 BAD (HHHFHH)
|
||||
Size 84: 42,96 OK (HHHFFF)
|
||||
Size 85: 43,98 BAD (HHHFHH)
|
||||
Size 86: 43,99 OK (HHHFFF)
|
||||
Size 87: 44,100 BAD (HHHFHH)
|
||||
Size 88: 44,101 OK (HHHFFF)
|
||||
Size 89: 45,102 BAD (HHHFHH)
|
||||
Size 90: 45,103 OK (HHHFFF)
|
||||
Size 91: 46,105 BAD (HHHFHH)
|
||||
Size 92: 46,106 OK (HHHFFF)
|
||||
Size 93: 47,107 BAD (HHHFHH)
|
||||
Size 94: 47,108 OK (HHHFFF)
|
||||
Size 95: 48,109 BAD (HHHFHH)
|
||||
Size 96: 48,110 OK (HHHFFF)
|
||||
Size 97: 49,111 BAD (HHHFHH)
|
||||
Size 98: 49,113 OK (HHHFFF)
|
||||
Size 99: 50,114 BAD (HHHFHH)
|
||||
Size 100: 50,115 OK (HHHFFF)
|
||||
|
||||
Windows 7
|
||||
---------
|
||||
|
||||
Size 1: 1,2 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 BAD (FFFFHH)
|
||||
Size 4: 2,5 OK (HHHFFF)
|
||||
Size 5: 3,6 BAD (FFFFHH)
|
||||
Size 6: 3,7 OK (HHHFFF)
|
||||
Size 7: 4,8 BAD (FFFFHH)
|
||||
Size 8: 4,9 OK (HHHFFF)
|
||||
Size 9: 5,10 BAD (FFFFHH)
|
||||
Size 10: 5,11 OK (HHHFFF)
|
||||
Size 11: 6,13 BAD (FFFFHH)
|
||||
Size 12: 6,14 OK (HHHFFF)
|
||||
Size 13: 7,15 BAD (FFFFHH)
|
||||
Size 14: 7,16 OK (HHHFFF)
|
||||
Size 15: 8,17 BAD (FFFFHH)
|
||||
Size 16: 8,18 OK (HHHFFF)
|
||||
Size 17: 9,20 BAD (FFFFHH)
|
||||
Size 18: 9,21 OK (HHHFFF)
|
||||
Size 19: 10,22 BAD (FFFFHH)
|
||||
Size 20: 10,23 OK (HHHFFF)
|
||||
Size 21: 11,24 BAD (FFFFHH)
|
||||
Size 22: 11,25 OK (HHHFFF)
|
||||
Size 23: 12,26 BAD (FFFFHH)
|
||||
Size 24: 12,28 OK (HHHFFF)
|
||||
Size 25: 13,29 BAD (FFFFHH)
|
||||
Size 26: 13,30 OK (HHHFFF)
|
||||
Size 27: 14,31 BAD (FFFFHH)
|
||||
Size 28: 14,32 OK (HHHFFF)
|
||||
Size 29: 15,33 BAD (FFFFHH)
|
||||
Size 30: 15,34 OK (HHHFFF)
|
||||
Size 31: 16,36 BAD (FFFFHH)
|
||||
Size 32: 16,37 OK (HHHFFF)
|
||||
Size 33: 17,38 BAD (FFFFHH)
|
||||
Size 34: 17,39 OK (HHHFFF)
|
||||
Size 35: 18,40 BAD (FFFFHH)
|
||||
Size 36: 18,41 OK (HHHFFF)
|
||||
Size 37: 19,42 BAD (FFFFHH)
|
||||
Size 38: 19,44 OK (HHHFFF)
|
||||
Size 39: 20,45 BAD (FFFFHH)
|
||||
Size 40: 20,46 OK (HHHFFF)
|
||||
Size 41: 21,47 BAD (FFFFHH)
|
||||
Size 42: 21,48 OK (HHHFFF)
|
||||
Size 43: 22,49 BAD (FFFFHH)
|
||||
Size 44: 22,51 OK (HHHFFF)
|
||||
Size 45: 23,52 BAD (FFFFHH)
|
||||
Size 46: 23,53 OK (HHHFFF)
|
||||
Size 47: 24,54 BAD (FFFFHH)
|
||||
Size 48: 24,55 OK (HHHFFF)
|
||||
Size 49: 25,56 BAD (FFFFHH)
|
||||
Size 50: 25,57 OK (HHHFFF)
|
||||
Size 51: 26,59 BAD (FFFFHH)
|
||||
Size 52: 26,60 OK (HHHFFF)
|
||||
Size 53: 27,61 BAD (FFFFHH)
|
||||
Size 54: 27,62 OK (HHHFFF)
|
||||
Size 55: 28,63 BAD (FFFFHH)
|
||||
Size 56: 28,64 OK (HHHFFF)
|
||||
Size 57: 29,65 BAD (FFFFHH)
|
||||
Size 58: 29,67 OK (HHHFFF)
|
||||
Size 59: 30,68 BAD (FFFFHH)
|
||||
Size 60: 30,69 OK (HHHFFF)
|
||||
Size 61: 31,70 BAD (FFFFHH)
|
||||
Size 62: 31,71 OK (HHHFFF)
|
||||
Size 63: 32,72 BAD (FFFFHH)
|
||||
Size 64: 32,74 OK (HHHFFF)
|
||||
Size 65: 33,75 BAD (FFFFHH)
|
||||
Size 66: 33,76 OK (HHHFFF)
|
||||
Size 67: 34,77 BAD (FFFFHH)
|
||||
Size 68: 34,78 OK (HHHFFF)
|
||||
Size 69: 35,79 BAD (FFFFHH)
|
||||
Size 70: 35,80 OK (HHHFFF)
|
||||
Size 71: 36,82 BAD (FFFFHH)
|
||||
Size 72: 36,83 OK (HHHFFF)
|
||||
Size 73: 37,84 BAD (FFFFHH)
|
||||
Size 74: 37,85 OK (HHHFFF)
|
||||
Size 75: 38,86 BAD (FFFFHH)
|
||||
Size 76: 38,87 OK (HHHFFF)
|
||||
Size 77: 39,88 BAD (FFFFHH)
|
||||
Size 78: 39,90 OK (HHHFFF)
|
||||
Size 79: 40,91 BAD (FFFFHH)
|
||||
Size 80: 40,92 OK (HHHFFF)
|
||||
Size 81: 41,93 BAD (FFFFHH)
|
||||
Size 82: 41,94 OK (HHHFFF)
|
||||
Size 83: 42,95 BAD (FFFFHH)
|
||||
Size 84: 42,96 OK (HHHFFF)
|
||||
Size 85: 43,98 BAD (FFFFHH)
|
||||
Size 86: 43,99 OK (HHHFFF)
|
||||
Size 87: 44,100 BAD (FFFFHH)
|
||||
Size 88: 44,101 OK (HHHFFF)
|
||||
Size 89: 45,102 BAD (FFFFHH)
|
||||
Size 90: 45,103 OK (HHHFFF)
|
||||
Size 91: 46,105 BAD (FFFFHH)
|
||||
Size 92: 46,106 OK (HHHFFF)
|
||||
Size 93: 47,107 BAD (FFFFHH)
|
||||
Size 94: 47,108 OK (HHHFFF)
|
||||
Size 95: 48,109 BAD (FFFFHH)
|
||||
Size 96: 48,110 OK (HHHFFF)
|
||||
Size 97: 49,111 BAD (FFFFHH)
|
||||
Size 98: 49,113 OK (HHHFFF)
|
||||
Size 99: 50,114 BAD (FFFFHH)
|
||||
Size 100: 50,115 OK (HHHFFF)
|
||||
|
||||
Windows 8
|
||||
---------
|
||||
|
||||
Size 1: 1,2 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 BAD (FFFFHH)
|
||||
Size 4: 2,5 OK (HHHFFF)
|
||||
Size 5: 3,6 BAD (FFFFHH)
|
||||
Size 6: 3,7 OK (HHHFFF)
|
||||
Size 7: 4,8 BAD (FFFFHH)
|
||||
Size 8: 4,9 OK (HHHFFF)
|
||||
Size 9: 5,10 BAD (FFFFHH)
|
||||
Size 10: 5,11 OK (HHHFFF)
|
||||
Size 11: 6,13 BAD (FFFFHH)
|
||||
Size 12: 6,14 OK (HHHFFF)
|
||||
Size 13: 7,15 BAD (FFFFHH)
|
||||
Size 14: 7,16 OK (HHHFFF)
|
||||
Size 15: 8,17 BAD (FFFFHH)
|
||||
Size 16: 8,18 OK (HHHFFF)
|
||||
Size 17: 9,20 BAD (FFFFHH)
|
||||
Size 18: 9,21 OK (HHHFFF)
|
||||
Size 19: 10,22 BAD (FFFFHH)
|
||||
Size 20: 10,23 OK (HHHFFF)
|
||||
Size 21: 11,24 BAD (FFFFHH)
|
||||
Size 22: 11,25 OK (HHHFFF)
|
||||
Size 23: 12,26 BAD (FFFFHH)
|
||||
Size 24: 12,28 OK (HHHFFF)
|
||||
Size 25: 13,29 BAD (FFFFHH)
|
||||
Size 26: 13,30 OK (HHHFFF)
|
||||
Size 27: 14,31 BAD (FFFFHH)
|
||||
Size 28: 14,32 OK (HHHFFF)
|
||||
Size 29: 15,33 BAD (FFFFHH)
|
||||
Size 30: 15,34 OK (HHHFFF)
|
||||
Size 31: 16,36 BAD (FFFFHH)
|
||||
Size 32: 16,37 OK (HHHFFF)
|
||||
Size 33: 17,38 BAD (FFFFHH)
|
||||
Size 34: 17,39 OK (HHHFFF)
|
||||
Size 35: 18,40 BAD (FFFFHH)
|
||||
Size 36: 18,41 OK (HHHFFF)
|
||||
Size 37: 19,42 BAD (FFFFHH)
|
||||
Size 38: 19,44 OK (HHHFFF)
|
||||
Size 39: 20,45 BAD (FFFFHH)
|
||||
Size 40: 20,46 OK (HHHFFF)
|
||||
Size 41: 21,47 BAD (FFFFHH)
|
||||
Size 42: 21,48 OK (HHHFFF)
|
||||
Size 43: 22,49 BAD (FFFFHH)
|
||||
Size 44: 22,51 OK (HHHFFF)
|
||||
Size 45: 23,52 BAD (FFFFHH)
|
||||
Size 46: 23,53 OK (HHHFFF)
|
||||
Size 47: 24,54 BAD (FFFFHH)
|
||||
Size 48: 24,55 OK (HHHFFF)
|
||||
Size 49: 25,56 BAD (FFFFHH)
|
||||
Size 50: 25,57 OK (HHHFFF)
|
||||
Size 51: 26,59 BAD (FFFFHH)
|
||||
Size 52: 26,60 OK (HHHFFF)
|
||||
Size 53: 27,61 BAD (FFFFHH)
|
||||
Size 54: 27,62 OK (HHHFFF)
|
||||
Size 55: 28,63 BAD (FFFFHH)
|
||||
Size 56: 28,64 OK (HHHFFF)
|
||||
Size 57: 29,65 BAD (FFFFHH)
|
||||
Size 58: 29,67 OK (HHHFFF)
|
||||
Size 59: 30,68 BAD (FFFFHH)
|
||||
Size 60: 30,69 OK (HHHFFF)
|
||||
Size 61: 31,70 BAD (FFFFHH)
|
||||
Size 62: 31,71 OK (HHHFFF)
|
||||
Size 63: 32,72 BAD (FFFFHH)
|
||||
Size 64: 32,74 OK (HHHFFF)
|
||||
Size 65: 33,75 BAD (FFFFHH)
|
||||
Size 66: 33,76 OK (HHHFFF)
|
||||
Size 67: 34,77 BAD (FFFFHH)
|
||||
Size 68: 34,78 OK (HHHFFF)
|
||||
Size 69: 35,79 BAD (FFFFHH)
|
||||
Size 70: 35,80 OK (HHHFFF)
|
||||
Size 71: 36,82 BAD (FFFFHH)
|
||||
Size 72: 36,83 OK (HHHFFF)
|
||||
Size 73: 37,84 BAD (FFFFHH)
|
||||
Size 74: 37,85 OK (HHHFFF)
|
||||
Size 75: 38,86 BAD (FFFFHH)
|
||||
Size 76: 38,87 OK (HHHFFF)
|
||||
Size 77: 39,88 BAD (FFFFHH)
|
||||
Size 78: 39,90 OK (HHHFFF)
|
||||
Size 79: 40,91 BAD (FFFFHH)
|
||||
Size 80: 40,92 OK (HHHFFF)
|
||||
Size 81: 41,93 BAD (FFFFHH)
|
||||
Size 82: 41,94 OK (HHHFFF)
|
||||
Size 83: 42,95 BAD (FFFFHH)
|
||||
Size 84: 42,96 OK (HHHFFF)
|
||||
Size 85: 43,98 BAD (FFFFHH)
|
||||
Size 86: 43,99 OK (HHHFFF)
|
||||
Size 87: 44,100 BAD (FFFFHH)
|
||||
Size 88: 44,101 OK (HHHFFF)
|
||||
Size 89: 45,102 BAD (FFFFHH)
|
||||
Size 90: 45,103 OK (HHHFFF)
|
||||
Size 91: 46,105 BAD (FFFFHH)
|
||||
Size 92: 46,106 OK (HHHFFF)
|
||||
Size 93: 47,107 BAD (FFFFHH)
|
||||
Size 94: 47,108 OK (HHHFFF)
|
||||
Size 95: 48,109 BAD (FFFFHH)
|
||||
Size 96: 48,110 OK (HHHFFF)
|
||||
Size 97: 49,111 BAD (FFFFHH)
|
||||
Size 98: 49,113 OK (HHHFFF)
|
||||
Size 99: 50,114 BAD (FFFFHH)
|
||||
Size 100: 50,115 OK (HHHFFF)
|
||||
|
||||
Windows 8.1
|
||||
-----------
|
||||
|
||||
Size 1: 1,2 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 BAD (FFFFHH)
|
||||
Size 4: 2,5 OK (HHHFFF)
|
||||
Size 5: 3,6 BAD (FFFFHH)
|
||||
Size 6: 3,7 OK (HHHFFF)
|
||||
Size 7: 4,8 BAD (FFFFHH)
|
||||
Size 8: 4,9 OK (HHHFFF)
|
||||
Size 9: 5,10 BAD (FFFFHH)
|
||||
Size 10: 5,11 OK (HHHFFF)
|
||||
Size 11: 6,13 BAD (FFFFHH)
|
||||
Size 12: 6,14 OK (HHHFFF)
|
||||
Size 13: 7,15 BAD (FFFFHH)
|
||||
Size 14: 7,16 OK (HHHFFF)
|
||||
Size 15: 8,17 BAD (FFFFHH)
|
||||
Size 16: 8,18 OK (HHHFFF)
|
||||
Size 17: 9,20 BAD (FFFFHH)
|
||||
Size 18: 9,21 OK (HHHFFF)
|
||||
Size 19: 10,22 BAD (FFFFHH)
|
||||
Size 20: 10,23 OK (HHHFFF)
|
||||
Size 21: 11,24 BAD (FFFFHH)
|
||||
Size 22: 11,25 OK (HHHFFF)
|
||||
Size 23: 12,26 BAD (FFFFHH)
|
||||
Size 24: 12,28 OK (HHHFFF)
|
||||
Size 25: 13,29 BAD (FFFFHH)
|
||||
Size 26: 13,30 OK (HHHFFF)
|
||||
Size 27: 14,31 BAD (FFFFHH)
|
||||
Size 28: 14,32 OK (HHHFFF)
|
||||
Size 29: 15,33 BAD (FFFFHH)
|
||||
Size 30: 15,34 OK (HHHFFF)
|
||||
Size 31: 16,36 BAD (FFFFHH)
|
||||
Size 32: 16,37 OK (HHHFFF)
|
||||
Size 33: 17,38 BAD (FFFFHH)
|
||||
Size 34: 17,39 OK (HHHFFF)
|
||||
Size 35: 18,40 BAD (FFFFHH)
|
||||
Size 36: 18,41 OK (HHHFFF)
|
||||
Size 37: 19,42 BAD (FFFFHH)
|
||||
Size 38: 19,44 OK (HHHFFF)
|
||||
Size 39: 20,45 BAD (FFFFHH)
|
||||
Size 40: 20,46 OK (HHHFFF)
|
||||
Size 41: 21,47 BAD (FFFFHH)
|
||||
Size 42: 21,48 OK (HHHFFF)
|
||||
Size 43: 22,49 BAD (FFFFHH)
|
||||
Size 44: 22,51 OK (HHHFFF)
|
||||
Size 45: 23,52 BAD (FFFFHH)
|
||||
Size 46: 23,53 OK (HHHFFF)
|
||||
Size 47: 24,54 BAD (FFFFHH)
|
||||
Size 48: 24,55 OK (HHHFFF)
|
||||
Size 49: 25,56 BAD (FFFFHH)
|
||||
Size 50: 25,57 OK (HHHFFF)
|
||||
Size 51: 26,59 BAD (FFFFHH)
|
||||
Size 52: 26,60 OK (HHHFFF)
|
||||
Size 53: 27,61 BAD (FFFFHH)
|
||||
Size 54: 27,62 OK (HHHFFF)
|
||||
Size 55: 28,63 BAD (FFFFHH)
|
||||
Size 56: 28,64 OK (HHHFFF)
|
||||
Size 57: 29,65 BAD (FFFFHH)
|
||||
Size 58: 29,67 OK (HHHFFF)
|
||||
Size 59: 30,68 BAD (FFFFHH)
|
||||
Size 60: 30,69 OK (HHHFFF)
|
||||
Size 61: 31,70 BAD (FFFFHH)
|
||||
Size 62: 31,71 OK (HHHFFF)
|
||||
Size 63: 32,72 BAD (FFFFHH)
|
||||
Size 64: 32,74 OK (HHHFFF)
|
||||
Size 65: 33,75 BAD (FFFFHH)
|
||||
Size 66: 33,76 OK (HHHFFF)
|
||||
Size 67: 34,77 BAD (FFFFHH)
|
||||
Size 68: 34,78 OK (HHHFFF)
|
||||
Size 69: 35,79 BAD (FFFFHH)
|
||||
Size 70: 35,80 OK (HHHFFF)
|
||||
Size 71: 36,82 BAD (FFFFHH)
|
||||
Size 72: 36,83 OK (HHHFFF)
|
||||
Size 73: 37,84 BAD (FFFFHH)
|
||||
Size 74: 37,85 OK (HHHFFF)
|
||||
Size 75: 38,86 BAD (FFFFHH)
|
||||
Size 76: 38,87 OK (HHHFFF)
|
||||
Size 77: 39,88 BAD (FFFFHH)
|
||||
Size 78: 39,90 OK (HHHFFF)
|
||||
Size 79: 40,91 BAD (FFFFHH)
|
||||
Size 80: 40,92 OK (HHHFFF)
|
||||
Size 81: 41,93 BAD (FFFFHH)
|
||||
Size 82: 41,94 OK (HHHFFF)
|
||||
Size 83: 42,95 BAD (FFFFHH)
|
||||
Size 84: 42,96 OK (HHHFFF)
|
||||
Size 85: 43,98 BAD (FFFFHH)
|
||||
Size 86: 43,99 OK (HHHFFF)
|
||||
Size 87: 44,100 BAD (FFFFHH)
|
||||
Size 88: 44,101 OK (HHHFFF)
|
||||
Size 89: 45,102 BAD (FFFFHH)
|
||||
Size 90: 45,103 OK (HHHFFF)
|
||||
Size 91: 46,105 BAD (FFFFHH)
|
||||
Size 92: 46,106 OK (HHHFFF)
|
||||
Size 93: 47,107 BAD (FFFFHH)
|
||||
Size 94: 47,108 OK (HHHFFF)
|
||||
Size 95: 48,109 BAD (FFFFHH)
|
||||
Size 96: 48,110 OK (HHHFFF)
|
||||
Size 97: 49,111 BAD (FFFFHH)
|
||||
Size 98: 49,113 OK (HHHFFF)
|
||||
Size 99: 50,114 BAD (FFFFHH)
|
||||
Size 100: 50,115 OK (HHHFFF)
|
||||
|
||||
Windows 10 14342 Old Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,2 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 BAD (FFFFHH)
|
||||
Size 4: 2,5 OK (HHHFFF)
|
||||
Size 5: 3,6 BAD (FFFFHH)
|
||||
Size 6: 3,7 OK (HHHFFF)
|
||||
Size 7: 4,8 BAD (FFFFHH)
|
||||
Size 8: 4,9 OK (HHHFFF)
|
||||
Size 9: 5,10 BAD (FFFFHH)
|
||||
Size 10: 5,11 OK (HHHFFF)
|
||||
Size 11: 6,13 BAD (FFFFHH)
|
||||
Size 12: 6,14 OK (HHHFFF)
|
||||
Size 13: 7,15 BAD (FFFFHH)
|
||||
Size 14: 7,16 OK (HHHFFF)
|
||||
Size 15: 8,17 BAD (FFFFHH)
|
||||
Size 16: 8,18 OK (HHHFFF)
|
||||
Size 17: 9,20 BAD (FFFFHH)
|
||||
Size 18: 9,21 OK (HHHFFF)
|
||||
Size 19: 10,22 BAD (FFFFHH)
|
||||
Size 20: 10,23 OK (HHHFFF)
|
||||
Size 21: 11,24 BAD (FFFFHH)
|
||||
Size 22: 11,25 OK (HHHFFF)
|
||||
Size 23: 12,26 BAD (FFFFHH)
|
||||
Size 24: 12,28 OK (HHHFFF)
|
||||
Size 25: 13,29 BAD (FFFFHH)
|
||||
Size 26: 13,30 OK (HHHFFF)
|
||||
Size 27: 14,31 BAD (FFFFHH)
|
||||
Size 28: 14,32 OK (HHHFFF)
|
||||
Size 29: 15,33 BAD (FFFFHH)
|
||||
Size 30: 15,34 OK (HHHFFF)
|
||||
Size 31: 16,36 BAD (FFFFHH)
|
||||
Size 32: 16,37 OK (HHHFFF)
|
||||
Size 33: 17,38 BAD (FFFFHH)
|
||||
Size 34: 17,39 OK (HHHFFF)
|
||||
Size 35: 18,40 BAD (FFFFHH)
|
||||
Size 36: 18,41 OK (HHHFFF)
|
||||
Size 37: 19,42 BAD (FFFFHH)
|
||||
Size 38: 19,44 OK (HHHFFF)
|
||||
Size 39: 20,45 BAD (FFFFHH)
|
||||
Size 40: 20,46 OK (HHHFFF)
|
||||
Size 41: 21,47 BAD (FFFFHH)
|
||||
Size 42: 21,48 OK (HHHFFF)
|
||||
Size 43: 22,49 BAD (FFFFHH)
|
||||
Size 44: 22,51 OK (HHHFFF)
|
||||
Size 45: 23,52 BAD (FFFFHH)
|
||||
Size 46: 23,53 OK (HHHFFF)
|
||||
Size 47: 24,54 BAD (FFFFHH)
|
||||
Size 48: 24,55 OK (HHHFFF)
|
||||
Size 49: 25,56 BAD (FFFFHH)
|
||||
Size 50: 25,57 OK (HHHFFF)
|
||||
Size 51: 26,59 BAD (FFFFHH)
|
||||
Size 52: 26,60 OK (HHHFFF)
|
||||
Size 53: 27,61 BAD (FFFFHH)
|
||||
Size 54: 27,62 OK (HHHFFF)
|
||||
Size 55: 28,63 BAD (FFFFHH)
|
||||
Size 56: 28,64 OK (HHHFFF)
|
||||
Size 57: 29,65 BAD (FFFFHH)
|
||||
Size 58: 29,67 OK (HHHFFF)
|
||||
Size 59: 30,68 BAD (FFFFHH)
|
||||
Size 60: 30,69 OK (HHHFFF)
|
||||
Size 61: 31,70 BAD (FFFFHH)
|
||||
Size 62: 31,71 OK (HHHFFF)
|
||||
Size 63: 32,72 BAD (FFFFHH)
|
||||
Size 64: 32,74 OK (HHHFFF)
|
||||
Size 65: 33,75 BAD (FFFFHH)
|
||||
Size 66: 33,76 OK (HHHFFF)
|
||||
Size 67: 34,77 BAD (FFFFHH)
|
||||
Size 68: 34,78 OK (HHHFFF)
|
||||
Size 69: 35,79 BAD (FFFFHH)
|
||||
Size 70: 35,80 OK (HHHFFF)
|
||||
Size 71: 36,82 BAD (FFFFHH)
|
||||
Size 72: 36,83 OK (HHHFFF)
|
||||
Size 73: 37,84 BAD (FFFFHH)
|
||||
Size 74: 37,85 OK (HHHFFF)
|
||||
Size 75: 38,86 BAD (FFFFHH)
|
||||
Size 76: 38,87 OK (HHHFFF)
|
||||
Size 77: 39,88 BAD (FFFFHH)
|
||||
Size 78: 39,90 OK (HHHFFF)
|
||||
Size 79: 40,91 BAD (FFFFHH)
|
||||
Size 80: 40,92 OK (HHHFFF)
|
||||
Size 81: 41,93 BAD (FFFFHH)
|
||||
Size 82: 41,94 OK (HHHFFF)
|
||||
Size 83: 42,95 BAD (FFFFHH)
|
||||
Size 84: 42,96 OK (HHHFFF)
|
||||
Size 85: 43,98 BAD (FFFFHH)
|
||||
Size 86: 43,99 OK (HHHFFF)
|
||||
Size 87: 44,100 BAD (FFFFHH)
|
||||
Size 88: 44,101 OK (HHHFFF)
|
||||
Size 89: 45,102 BAD (FFFFHH)
|
||||
Size 90: 45,103 OK (HHHFFF)
|
||||
Size 91: 46,105 BAD (FFFFHH)
|
||||
Size 92: 46,106 OK (HHHFFF)
|
||||
Size 93: 47,107 BAD (FFFFHH)
|
||||
Size 94: 47,108 OK (HHHFFF)
|
||||
Size 95: 48,109 BAD (FFFFHH)
|
||||
Size 96: 48,110 OK (HHHFFF)
|
||||
Size 97: 49,111 BAD (FFFFHH)
|
||||
Size 98: 49,113 OK (HHHFFF)
|
||||
Size 99: 50,114 BAD (FFFFHH)
|
||||
Size 100: 50,115 OK (HHHFFF)
|
||||
|
||||
Windows 10 14342 New Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,1 OK (HHHFFF)
|
||||
Size 2: 1,2 OK (HHHFFF)
|
||||
Size 3: 2,3 OK (HHHFFF)
|
||||
Size 4: 2,4 OK (HHHFFF)
|
||||
Size 5: 3,5 OK (HHHFFF)
|
||||
Size 6: 3,6 OK (HHHFFF)
|
||||
Size 7: 4,7 OK (HHHFFF)
|
||||
Size 8: 4,8 OK (HHHFFF)
|
||||
Size 9: 5,9 OK (HHHFFF)
|
||||
Size 10: 5,10 OK (HHHFFF)
|
||||
Size 11: 6,11 OK (HHHFFF)
|
||||
Size 12: 6,12 OK (HHHFFF)
|
||||
Size 13: 7,13 OK (HHHFFF)
|
||||
Size 14: 7,14 OK (HHHFFF)
|
||||
Size 15: 8,15 OK (HHHFFF)
|
||||
Size 16: 8,16 OK (HHHFFF)
|
||||
Size 17: 9,17 OK (HHHFFF)
|
||||
Size 18: 9,18 OK (HHHFFF)
|
||||
Size 19: 10,19 OK (HHHFFF)
|
||||
Size 20: 10,20 OK (HHHFFF)
|
||||
Size 21: 11,21 OK (HHHFFF)
|
||||
Size 22: 11,22 OK (HHHFFF)
|
||||
Size 23: 12,23 OK (HHHFFF)
|
||||
Size 24: 12,24 OK (HHHFFF)
|
||||
Size 25: 13,25 OK (HHHFFF)
|
||||
Size 26: 13,26 OK (HHHFFF)
|
||||
Size 27: 14,27 OK (HHHFFF)
|
||||
Size 28: 14,28 OK (HHHFFF)
|
||||
Size 29: 15,29 OK (HHHFFF)
|
||||
Size 30: 15,30 OK (HHHFFF)
|
||||
Size 31: 16,31 OK (HHHFFF)
|
||||
Size 32: 16,32 OK (HHHFFF)
|
||||
Size 33: 17,33 OK (HHHFFF)
|
||||
Size 34: 17,34 OK (HHHFFF)
|
||||
Size 35: 18,35 OK (HHHFFF)
|
||||
Size 36: 18,36 OK (HHHFFF)
|
||||
Size 37: 19,37 OK (HHHFFF)
|
||||
Size 38: 19,38 OK (HHHFFF)
|
||||
Size 39: 20,39 OK (HHHFFF)
|
||||
Size 40: 20,40 OK (HHHFFF)
|
||||
Size 41: 21,41 OK (HHHFFF)
|
||||
Size 42: 21,42 OK (HHHFFF)
|
||||
Size 43: 22,43 OK (HHHFFF)
|
||||
Size 44: 22,44 OK (HHHFFF)
|
||||
Size 45: 23,45 OK (HHHFFF)
|
||||
Size 46: 23,46 OK (HHHFFF)
|
||||
Size 47: 24,47 OK (HHHFFF)
|
||||
Size 48: 24,48 OK (HHHFFF)
|
||||
Size 49: 25,49 OK (HHHFFF)
|
||||
Size 50: 25,50 OK (HHHFFF)
|
||||
Size 51: 26,51 OK (HHHFFF)
|
||||
Size 52: 26,52 OK (HHHFFF)
|
||||
Size 53: 27,53 OK (HHHFFF)
|
||||
Size 54: 27,54 OK (HHHFFF)
|
||||
Size 55: 28,55 OK (HHHFFF)
|
||||
Size 56: 28,56 OK (HHHFFF)
|
||||
Size 57: 29,57 OK (HHHFFF)
|
||||
Size 58: 29,58 OK (HHHFFF)
|
||||
Size 59: 30,59 OK (HHHFFF)
|
||||
Size 60: 30,60 OK (HHHFFF)
|
||||
Size 61: 31,61 OK (HHHFFF)
|
||||
Size 62: 31,62 OK (HHHFFF)
|
||||
Size 63: 32,63 OK (HHHFFF)
|
||||
Size 64: 32,64 OK (HHHFFF)
|
||||
Size 65: 33,65 OK (HHHFFF)
|
||||
Size 66: 33,66 OK (HHHFFF)
|
||||
Size 67: 34,67 OK (HHHFFF)
|
||||
Size 68: 34,68 OK (HHHFFF)
|
||||
Size 69: 35,69 OK (HHHFFF)
|
||||
Size 70: 35,70 OK (HHHFFF)
|
||||
Size 71: 36,71 OK (HHHFFF)
|
||||
Size 72: 36,72 OK (HHHFFF)
|
||||
Size 73: 37,73 OK (HHHFFF)
|
||||
Size 74: 37,74 OK (HHHFFF)
|
||||
Size 75: 38,75 OK (HHHFFF)
|
||||
Size 76: 38,76 OK (HHHFFF)
|
||||
Size 77: 39,77 OK (HHHFFF)
|
||||
Size 78: 39,78 OK (HHHFFF)
|
||||
Size 79: 40,79 OK (HHHFFF)
|
||||
Size 80: 40,80 OK (HHHFFF)
|
||||
Size 81: 41,81 OK (HHHFFF)
|
||||
Size 82: 41,82 OK (HHHFFF)
|
||||
Size 83: 42,83 OK (HHHFFF)
|
||||
Size 84: 42,84 OK (HHHFFF)
|
||||
Size 85: 43,85 OK (HHHFFF)
|
||||
Size 86: 43,86 OK (HHHFFF)
|
||||
Size 87: 44,87 OK (HHHFFF)
|
||||
Size 88: 44,88 OK (HHHFFF)
|
||||
Size 89: 45,89 OK (HHHFFF)
|
||||
Size 90: 45,90 OK (HHHFFF)
|
||||
Size 91: 46,91 OK (HHHFFF)
|
||||
Size 92: 46,92 OK (HHHFFF)
|
||||
Size 93: 47,93 OK (HHHFFF)
|
||||
Size 94: 47,94 OK (HHHFFF)
|
||||
Size 95: 48,95 OK (HHHFFF)
|
||||
Size 96: 48,96 OK (HHHFFF)
|
||||
Size 97: 49,97 OK (HHHFFF)
|
||||
Size 98: 49,98 OK (HHHFFF)
|
||||
Size 99: 50,99 OK (HHHFFF)
|
||||
Size 100: 50,100 OK (HHHFFF)
|
||||
Generated
Vendored
+630
@@ -0,0 +1,630 @@
|
||||
===========================================================
|
||||
Code Page 950, Chinese Traditional (Taiwan), MingLight font
|
||||
===========================================================
|
||||
|
||||
Options: -face-minglight -family 0x36
|
||||
Chars: A2 A3 2014 3044 30FC 4000
|
||||
|
||||
Vista
|
||||
-----
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,4 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (HHHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (HHHFHH)
|
||||
Size 8: 4,10 GOOD (HHFFFF)
|
||||
Size 9: 5,11 BAD (HHHFHH)
|
||||
Size 10: 5,12 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (HHHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,16 BAD (HHHFHH)
|
||||
Size 14: 7,17 GOOD (HHFFFF)
|
||||
Size 15: 8,18 BAD (HHHFHH)
|
||||
Size 16: 8,19 GOOD (HHFFFF)
|
||||
Size 17: 9,20 BAD (HHHFHH)
|
||||
Size 18: 9,22 GOOD (HHFFFF)
|
||||
Size 19: 10,23 BAD (HHHFHH)
|
||||
Size 20: 10,24 GOOD (HHFFFF)
|
||||
Size 21: 11,25 BAD (HHHFHH)
|
||||
Size 22: 11,26 GOOD (HHFFFF)
|
||||
Size 23: 12,28 BAD (HHHFHH)
|
||||
Size 24: 12,29 GOOD (HHFFFF)
|
||||
Size 25: 13,30 BAD (HHHFHH)
|
||||
Size 26: 13,31 GOOD (HHFFFF)
|
||||
Size 27: 14,32 BAD (HHHFHH)
|
||||
Size 28: 14,34 GOOD (HHFFFF)
|
||||
Size 29: 15,35 BAD (HHHFHH)
|
||||
Size 30: 15,36 GOOD (HHFFFF)
|
||||
Size 31: 16,37 BAD (HHHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,40 BAD (HHHFHH)
|
||||
Size 34: 17,41 GOOD (HHFFFF)
|
||||
Size 35: 18,42 BAD (HHHFHH)
|
||||
Size 36: 18,43 GOOD (HHFFFF)
|
||||
Size 37: 19,44 BAD (HHHFHH)
|
||||
Size 38: 19,46 GOOD (HHFFFF)
|
||||
Size 39: 20,47 BAD (HHHFHH)
|
||||
Size 40: 20,48 GOOD (HHFFFF)
|
||||
Size 41: 21,49 BAD (HHHFHH)
|
||||
Size 42: 21,50 GOOD (HHFFFF)
|
||||
Size 43: 22,52 BAD (HHHFHH)
|
||||
Size 44: 22,53 GOOD (HHFFFF)
|
||||
Size 45: 23,54 BAD (HHHFHH)
|
||||
Size 46: 23,55 GOOD (HHFFFF)
|
||||
Size 47: 24,56 BAD (HHHFHH)
|
||||
Size 48: 24,58 GOOD (HHFFFF)
|
||||
Size 49: 25,59 BAD (HHHFHH)
|
||||
Size 50: 25,60 GOOD (HHFFFF)
|
||||
Size 51: 26,61 BAD (HHHFHH)
|
||||
Size 52: 26,62 GOOD (HHFFFF)
|
||||
Size 53: 27,64 BAD (HHHFHH)
|
||||
Size 54: 27,65 GOOD (HHFFFF)
|
||||
Size 55: 28,66 BAD (HHHFHH)
|
||||
Size 56: 28,67 GOOD (HHFFFF)
|
||||
Size 57: 29,68 BAD (HHHFHH)
|
||||
Size 58: 29,70 GOOD (HHFFFF)
|
||||
Size 59: 30,71 BAD (HHHFHH)
|
||||
Size 60: 30,72 GOOD (HHFFFF)
|
||||
Size 61: 31,73 BAD (HHHFHH)
|
||||
Size 62: 31,74 GOOD (HHFFFF)
|
||||
Size 63: 32,76 BAD (HHHFHH)
|
||||
Size 64: 32,77 GOOD (HHFFFF)
|
||||
Size 65: 33,78 BAD (HHHFHH)
|
||||
Size 66: 33,79 GOOD (HHFFFF)
|
||||
Size 67: 34,80 BAD (HHHFHH)
|
||||
Size 68: 34,82 GOOD (HHFFFF)
|
||||
Size 69: 35,83 BAD (HHHFHH)
|
||||
Size 70: 35,84 GOOD (HHFFFF)
|
||||
Size 71: 36,85 BAD (HHHFHH)
|
||||
Size 72: 36,86 GOOD (HHFFFF)
|
||||
Size 73: 37,88 BAD (HHHFHH)
|
||||
Size 74: 37,89 GOOD (HHFFFF)
|
||||
Size 75: 38,90 BAD (HHHFHH)
|
||||
Size 76: 38,91 GOOD (HHFFFF)
|
||||
Size 77: 39,92 BAD (HHHFHH)
|
||||
Size 78: 39,94 GOOD (HHFFFF)
|
||||
Size 79: 40,95 BAD (HHHFHH)
|
||||
Size 80: 40,96 GOOD (HHFFFF)
|
||||
Size 81: 41,97 BAD (HHHFHH)
|
||||
Size 82: 41,98 GOOD (HHFFFF)
|
||||
Size 83: 42,100 BAD (HHHFHH)
|
||||
Size 84: 42,101 GOOD (HHFFFF)
|
||||
Size 85: 43,102 BAD (HHHFHH)
|
||||
Size 86: 43,103 GOOD (HHFFFF)
|
||||
Size 87: 44,104 BAD (HHHFHH)
|
||||
Size 88: 44,106 GOOD (HHFFFF)
|
||||
Size 89: 45,107 BAD (HHHFHH)
|
||||
Size 90: 45,108 GOOD (HHFFFF)
|
||||
Size 91: 46,109 BAD (HHHFHH)
|
||||
Size 92: 46,110 GOOD (HHFFFF)
|
||||
Size 93: 47,112 BAD (HHHFHH)
|
||||
Size 94: 47,113 GOOD (HHFFFF)
|
||||
Size 95: 48,114 BAD (HHHFHH)
|
||||
Size 96: 48,115 GOOD (HHFFFF)
|
||||
Size 97: 49,116 BAD (HHHFHH)
|
||||
Size 98: 49,118 GOOD (HHFFFF)
|
||||
Size 99: 50,119 BAD (HHHFHH)
|
||||
Size 100: 50,120 GOOD (HHFFFF)
|
||||
|
||||
Windows 7
|
||||
---------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,4 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,10 GOOD (HHFFFF)
|
||||
Size 9: 5,11 BAD (FFHFHH)
|
||||
Size 10: 5,12 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,16 BAD (FFHFHH)
|
||||
Size 14: 7,17 GOOD (HHFFFF)
|
||||
Size 15: 8,18 BAD (FFHFHH)
|
||||
Size 16: 8,19 GOOD (HHFFFF)
|
||||
Size 17: 9,20 BAD (FFHFHH)
|
||||
Size 18: 9,22 GOOD (HHFFFF)
|
||||
Size 19: 10,23 BAD (FFHFHH)
|
||||
Size 20: 10,24 GOOD (HHFFFF)
|
||||
Size 21: 11,25 BAD (FFHFHH)
|
||||
Size 22: 11,26 GOOD (HHFFFF)
|
||||
Size 23: 12,28 BAD (FFHFHH)
|
||||
Size 24: 12,29 GOOD (HHFFFF)
|
||||
Size 25: 13,30 BAD (FFHFHH)
|
||||
Size 26: 13,31 GOOD (HHFFFF)
|
||||
Size 27: 14,32 BAD (FFHFHH)
|
||||
Size 28: 14,34 GOOD (HHFFFF)
|
||||
Size 29: 15,35 BAD (FFHFHH)
|
||||
Size 30: 15,36 GOOD (HHFFFF)
|
||||
Size 31: 16,37 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,40 BAD (FFHFHH)
|
||||
Size 34: 17,41 GOOD (HHFFFF)
|
||||
Size 35: 18,42 BAD (FFHFHH)
|
||||
Size 36: 18,43 GOOD (HHFFFF)
|
||||
Size 37: 19,44 BAD (FFHFHH)
|
||||
Size 38: 19,46 GOOD (HHFFFF)
|
||||
Size 39: 20,47 BAD (FFHFHH)
|
||||
Size 40: 20,48 GOOD (HHFFFF)
|
||||
Size 41: 21,49 BAD (FFHFHH)
|
||||
Size 42: 21,50 GOOD (HHFFFF)
|
||||
Size 43: 22,52 BAD (FFHFHH)
|
||||
Size 44: 22,53 GOOD (HHFFFF)
|
||||
Size 45: 23,54 BAD (FFHFHH)
|
||||
Size 46: 23,55 GOOD (HHFFFF)
|
||||
Size 47: 24,56 BAD (FFHFHH)
|
||||
Size 48: 24,58 GOOD (HHFFFF)
|
||||
Size 49: 25,59 BAD (FFHFHH)
|
||||
Size 50: 25,60 GOOD (HHFFFF)
|
||||
Size 51: 26,61 BAD (FFHFHH)
|
||||
Size 52: 26,62 GOOD (HHFFFF)
|
||||
Size 53: 27,64 BAD (FFHFHH)
|
||||
Size 54: 27,65 GOOD (HHFFFF)
|
||||
Size 55: 28,66 BAD (FFHFHH)
|
||||
Size 56: 28,67 GOOD (HHFFFF)
|
||||
Size 57: 29,68 BAD (FFHFHH)
|
||||
Size 58: 29,70 GOOD (HHFFFF)
|
||||
Size 59: 30,71 BAD (FFHFHH)
|
||||
Size 60: 30,72 GOOD (HHFFFF)
|
||||
Size 61: 31,73 BAD (FFHFHH)
|
||||
Size 62: 31,74 GOOD (HHFFFF)
|
||||
Size 63: 32,76 BAD (FFHFHH)
|
||||
Size 64: 32,77 GOOD (HHFFFF)
|
||||
Size 65: 33,78 BAD (FFHFHH)
|
||||
Size 66: 33,79 GOOD (HHFFFF)
|
||||
Size 67: 34,80 BAD (FFHFHH)
|
||||
Size 68: 34,82 GOOD (HHFFFF)
|
||||
Size 69: 35,83 BAD (FFHFHH)
|
||||
Size 70: 35,84 GOOD (HHFFFF)
|
||||
Size 71: 36,85 BAD (FFHFHH)
|
||||
Size 72: 36,86 GOOD (HHFFFF)
|
||||
Size 73: 37,88 BAD (FFHFHH)
|
||||
Size 74: 37,89 GOOD (HHFFFF)
|
||||
Size 75: 38,90 BAD (FFHFHH)
|
||||
Size 76: 38,91 GOOD (HHFFFF)
|
||||
Size 77: 39,92 BAD (FFHFHH)
|
||||
Size 78: 39,94 GOOD (HHFFFF)
|
||||
Size 79: 40,95 BAD (FFHFHH)
|
||||
Size 80: 40,96 GOOD (HHFFFF)
|
||||
Size 81: 41,97 BAD (FFHFHH)
|
||||
Size 82: 41,98 GOOD (HHFFFF)
|
||||
Size 83: 42,100 BAD (FFHFHH)
|
||||
Size 84: 42,101 GOOD (HHFFFF)
|
||||
Size 85: 43,102 BAD (FFHFHH)
|
||||
Size 86: 43,103 GOOD (HHFFFF)
|
||||
Size 87: 44,104 BAD (FFHFHH)
|
||||
Size 88: 44,106 GOOD (HHFFFF)
|
||||
Size 89: 45,107 BAD (FFHFHH)
|
||||
Size 90: 45,108 GOOD (HHFFFF)
|
||||
Size 91: 46,109 BAD (FFHFHH)
|
||||
Size 92: 46,110 GOOD (HHFFFF)
|
||||
Size 93: 47,112 BAD (FFHFHH)
|
||||
Size 94: 47,113 GOOD (HHFFFF)
|
||||
Size 95: 48,114 BAD (FFHFHH)
|
||||
Size 96: 48,115 GOOD (HHFFFF)
|
||||
Size 97: 49,116 BAD (FFHFHH)
|
||||
Size 98: 49,118 GOOD (HHFFFF)
|
||||
Size 99: 50,119 BAD (FFHFHH)
|
||||
Size 100: 50,120 GOOD (HHFFFF)
|
||||
|
||||
Windows 8
|
||||
---------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,4 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,10 GOOD (HHFFFF)
|
||||
Size 9: 5,11 BAD (FFHFHH)
|
||||
Size 10: 5,12 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,16 BAD (FFHFHH)
|
||||
Size 14: 7,17 GOOD (HHFFFF)
|
||||
Size 15: 8,18 BAD (FFHFHH)
|
||||
Size 16: 8,19 GOOD (HHFFFF)
|
||||
Size 17: 9,20 BAD (FFHFHH)
|
||||
Size 18: 9,22 GOOD (HHFFFF)
|
||||
Size 19: 10,23 BAD (FFHFHH)
|
||||
Size 20: 10,24 GOOD (HHFFFF)
|
||||
Size 21: 11,25 BAD (FFHFHH)
|
||||
Size 22: 11,26 GOOD (HHFFFF)
|
||||
Size 23: 12,28 BAD (FFHFHH)
|
||||
Size 24: 12,29 GOOD (HHFFFF)
|
||||
Size 25: 13,30 BAD (FFHFHH)
|
||||
Size 26: 13,31 GOOD (HHFFFF)
|
||||
Size 27: 14,32 BAD (FFHFHH)
|
||||
Size 28: 14,34 GOOD (HHFFFF)
|
||||
Size 29: 15,35 BAD (FFHFHH)
|
||||
Size 30: 15,36 GOOD (HHFFFF)
|
||||
Size 31: 16,37 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,40 BAD (FFHFHH)
|
||||
Size 34: 17,41 GOOD (HHFFFF)
|
||||
Size 35: 18,42 BAD (FFHFHH)
|
||||
Size 36: 18,43 GOOD (HHFFFF)
|
||||
Size 37: 19,44 BAD (FFHFHH)
|
||||
Size 38: 19,46 GOOD (HHFFFF)
|
||||
Size 39: 20,47 BAD (FFHFHH)
|
||||
Size 40: 20,48 GOOD (HHFFFF)
|
||||
Size 41: 21,49 BAD (FFHFHH)
|
||||
Size 42: 21,50 GOOD (HHFFFF)
|
||||
Size 43: 22,52 BAD (FFHFHH)
|
||||
Size 44: 22,53 GOOD (HHFFFF)
|
||||
Size 45: 23,54 BAD (FFHFHH)
|
||||
Size 46: 23,55 GOOD (HHFFFF)
|
||||
Size 47: 24,56 BAD (FFHFHH)
|
||||
Size 48: 24,58 GOOD (HHFFFF)
|
||||
Size 49: 25,59 BAD (FFHFHH)
|
||||
Size 50: 25,60 GOOD (HHFFFF)
|
||||
Size 51: 26,61 BAD (FFHFHH)
|
||||
Size 52: 26,62 GOOD (HHFFFF)
|
||||
Size 53: 27,64 BAD (FFHFHH)
|
||||
Size 54: 27,65 GOOD (HHFFFF)
|
||||
Size 55: 28,66 BAD (FFHFHH)
|
||||
Size 56: 28,67 GOOD (HHFFFF)
|
||||
Size 57: 29,68 BAD (FFHFHH)
|
||||
Size 58: 29,70 GOOD (HHFFFF)
|
||||
Size 59: 30,71 BAD (FFHFHH)
|
||||
Size 60: 30,72 GOOD (HHFFFF)
|
||||
Size 61: 31,73 BAD (FFHFHH)
|
||||
Size 62: 31,74 GOOD (HHFFFF)
|
||||
Size 63: 32,76 BAD (FFHFHH)
|
||||
Size 64: 32,77 GOOD (HHFFFF)
|
||||
Size 65: 33,78 BAD (FFHFHH)
|
||||
Size 66: 33,79 GOOD (HHFFFF)
|
||||
Size 67: 34,80 BAD (FFHFHH)
|
||||
Size 68: 34,82 GOOD (HHFFFF)
|
||||
Size 69: 35,83 BAD (FFHFHH)
|
||||
Size 70: 35,84 GOOD (HHFFFF)
|
||||
Size 71: 36,85 BAD (FFHFHH)
|
||||
Size 72: 36,86 GOOD (HHFFFF)
|
||||
Size 73: 37,88 BAD (FFHFHH)
|
||||
Size 74: 37,89 GOOD (HHFFFF)
|
||||
Size 75: 38,90 BAD (FFHFHH)
|
||||
Size 76: 38,91 GOOD (HHFFFF)
|
||||
Size 77: 39,92 BAD (FFHFHH)
|
||||
Size 78: 39,94 GOOD (HHFFFF)
|
||||
Size 79: 40,95 BAD (FFHFHH)
|
||||
Size 80: 40,96 GOOD (HHFFFF)
|
||||
Size 81: 41,97 BAD (FFHFHH)
|
||||
Size 82: 41,98 GOOD (HHFFFF)
|
||||
Size 83: 42,100 BAD (FFHFHH)
|
||||
Size 84: 42,101 GOOD (HHFFFF)
|
||||
Size 85: 43,102 BAD (FFHFHH)
|
||||
Size 86: 43,103 GOOD (HHFFFF)
|
||||
Size 87: 44,104 BAD (FFHFHH)
|
||||
Size 88: 44,106 GOOD (HHFFFF)
|
||||
Size 89: 45,107 BAD (FFHFHH)
|
||||
Size 90: 45,108 GOOD (HHFFFF)
|
||||
Size 91: 46,109 BAD (FFHFHH)
|
||||
Size 92: 46,110 GOOD (HHFFFF)
|
||||
Size 93: 47,112 BAD (FFHFHH)
|
||||
Size 94: 47,113 GOOD (HHFFFF)
|
||||
Size 95: 48,114 BAD (FFHFHH)
|
||||
Size 96: 48,115 GOOD (HHFFFF)
|
||||
Size 97: 49,116 BAD (FFHFHH)
|
||||
Size 98: 49,118 GOOD (HHFFFF)
|
||||
Size 99: 50,119 BAD (FFHFHH)
|
||||
Size 100: 50,120 GOOD (HHFFFF)
|
||||
|
||||
Windows 8.1
|
||||
-----------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,4 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,10 GOOD (HHFFFF)
|
||||
Size 9: 5,11 BAD (FFHFHH)
|
||||
Size 10: 5,12 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,16 BAD (FFHFHH)
|
||||
Size 14: 7,17 GOOD (HHFFFF)
|
||||
Size 15: 8,18 BAD (FFHFHH)
|
||||
Size 16: 8,19 GOOD (HHFFFF)
|
||||
Size 17: 9,20 BAD (FFHFHH)
|
||||
Size 18: 9,22 GOOD (HHFFFF)
|
||||
Size 19: 10,23 BAD (FFHFHH)
|
||||
Size 20: 10,24 GOOD (HHFFFF)
|
||||
Size 21: 11,25 BAD (FFHFHH)
|
||||
Size 22: 11,26 GOOD (HHFFFF)
|
||||
Size 23: 12,28 BAD (FFHFHH)
|
||||
Size 24: 12,29 GOOD (HHFFFF)
|
||||
Size 25: 13,30 BAD (FFHFHH)
|
||||
Size 26: 13,31 GOOD (HHFFFF)
|
||||
Size 27: 14,32 BAD (FFHFHH)
|
||||
Size 28: 14,34 GOOD (HHFFFF)
|
||||
Size 29: 15,35 BAD (FFHFHH)
|
||||
Size 30: 15,36 GOOD (HHFFFF)
|
||||
Size 31: 16,37 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,40 BAD (FFHFHH)
|
||||
Size 34: 17,41 GOOD (HHFFFF)
|
||||
Size 35: 18,42 BAD (FFHFHH)
|
||||
Size 36: 18,43 GOOD (HHFFFF)
|
||||
Size 37: 19,44 BAD (FFHFHH)
|
||||
Size 38: 19,46 GOOD (HHFFFF)
|
||||
Size 39: 20,47 BAD (FFHFHH)
|
||||
Size 40: 20,48 GOOD (HHFFFF)
|
||||
Size 41: 21,49 BAD (FFHFHH)
|
||||
Size 42: 21,50 GOOD (HHFFFF)
|
||||
Size 43: 22,52 BAD (FFHFHH)
|
||||
Size 44: 22,53 GOOD (HHFFFF)
|
||||
Size 45: 23,54 BAD (FFHFHH)
|
||||
Size 46: 23,55 GOOD (HHFFFF)
|
||||
Size 47: 24,56 BAD (FFHFHH)
|
||||
Size 48: 24,58 GOOD (HHFFFF)
|
||||
Size 49: 25,59 BAD (FFHFHH)
|
||||
Size 50: 25,60 GOOD (HHFFFF)
|
||||
Size 51: 26,61 BAD (FFHFHH)
|
||||
Size 52: 26,62 GOOD (HHFFFF)
|
||||
Size 53: 27,64 BAD (FFHFHH)
|
||||
Size 54: 27,65 GOOD (HHFFFF)
|
||||
Size 55: 28,66 BAD (FFHFHH)
|
||||
Size 56: 28,67 GOOD (HHFFFF)
|
||||
Size 57: 29,68 BAD (FFHFHH)
|
||||
Size 58: 29,70 GOOD (HHFFFF)
|
||||
Size 59: 30,71 BAD (FFHFHH)
|
||||
Size 60: 30,72 GOOD (HHFFFF)
|
||||
Size 61: 31,73 BAD (FFHFHH)
|
||||
Size 62: 31,74 GOOD (HHFFFF)
|
||||
Size 63: 32,76 BAD (FFHFHH)
|
||||
Size 64: 32,77 GOOD (HHFFFF)
|
||||
Size 65: 33,78 BAD (FFHFHH)
|
||||
Size 66: 33,79 GOOD (HHFFFF)
|
||||
Size 67: 34,80 BAD (FFHFHH)
|
||||
Size 68: 34,82 GOOD (HHFFFF)
|
||||
Size 69: 35,83 BAD (FFHFHH)
|
||||
Size 70: 35,84 GOOD (HHFFFF)
|
||||
Size 71: 36,85 BAD (FFHFHH)
|
||||
Size 72: 36,86 GOOD (HHFFFF)
|
||||
Size 73: 37,88 BAD (FFHFHH)
|
||||
Size 74: 37,89 GOOD (HHFFFF)
|
||||
Size 75: 38,90 BAD (FFHFHH)
|
||||
Size 76: 38,91 GOOD (HHFFFF)
|
||||
Size 77: 39,92 BAD (FFHFHH)
|
||||
Size 78: 39,94 GOOD (HHFFFF)
|
||||
Size 79: 40,95 BAD (FFHFHH)
|
||||
Size 80: 40,96 GOOD (HHFFFF)
|
||||
Size 81: 41,97 BAD (FFHFHH)
|
||||
Size 82: 41,98 GOOD (HHFFFF)
|
||||
Size 83: 42,100 BAD (FFHFHH)
|
||||
Size 84: 42,101 GOOD (HHFFFF)
|
||||
Size 85: 43,102 BAD (FFHFHH)
|
||||
Size 86: 43,103 GOOD (HHFFFF)
|
||||
Size 87: 44,104 BAD (FFHFHH)
|
||||
Size 88: 44,106 GOOD (HHFFFF)
|
||||
Size 89: 45,107 BAD (FFHFHH)
|
||||
Size 90: 45,108 GOOD (HHFFFF)
|
||||
Size 91: 46,109 BAD (FFHFHH)
|
||||
Size 92: 46,110 GOOD (HHFFFF)
|
||||
Size 93: 47,112 BAD (FFHFHH)
|
||||
Size 94: 47,113 GOOD (HHFFFF)
|
||||
Size 95: 48,114 BAD (FFHFHH)
|
||||
Size 96: 48,115 GOOD (HHFFFF)
|
||||
Size 97: 49,116 BAD (FFHFHH)
|
||||
Size 98: 49,118 GOOD (HHFFFF)
|
||||
Size 99: 50,119 BAD (FFHFHH)
|
||||
Size 100: 50,120 GOOD (HHFFFF)
|
||||
|
||||
Windows 10 14342 Old Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,2 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,4 BAD (FFHFHH)
|
||||
Size 4: 2,5 GOOD (HHFFFF)
|
||||
Size 5: 3,6 BAD (FFHFHH)
|
||||
Size 6: 3,7 GOOD (HHFFFF)
|
||||
Size 7: 4,8 BAD (FFHFHH)
|
||||
Size 8: 4,10 GOOD (HHFFFF)
|
||||
Size 9: 5,11 BAD (FFHFHH)
|
||||
Size 10: 5,12 GOOD (HHFFFF)
|
||||
Size 11: 6,13 BAD (FFHFHH)
|
||||
Size 12: 6,14 GOOD (HHFFFF)
|
||||
Size 13: 7,16 BAD (FFHFHH)
|
||||
Size 14: 7,17 GOOD (HHFFFF)
|
||||
Size 15: 8,18 BAD (FFHFHH)
|
||||
Size 16: 8,19 GOOD (HHFFFF)
|
||||
Size 17: 9,20 BAD (FFHFHH)
|
||||
Size 18: 9,22 GOOD (HHFFFF)
|
||||
Size 19: 10,23 BAD (FFHFHH)
|
||||
Size 20: 10,24 GOOD (HHFFFF)
|
||||
Size 21: 11,25 BAD (FFHFHH)
|
||||
Size 22: 11,26 GOOD (HHFFFF)
|
||||
Size 23: 12,28 BAD (FFHFHH)
|
||||
Size 24: 12,29 GOOD (HHFFFF)
|
||||
Size 25: 13,30 BAD (FFHFHH)
|
||||
Size 26: 13,31 GOOD (HHFFFF)
|
||||
Size 27: 14,32 BAD (FFHFHH)
|
||||
Size 28: 14,34 GOOD (HHFFFF)
|
||||
Size 29: 15,35 BAD (FFHFHH)
|
||||
Size 30: 15,36 GOOD (HHFFFF)
|
||||
Size 31: 16,37 BAD (FFHFHH)
|
||||
Size 32: 16,38 GOOD (HHFFFF)
|
||||
Size 33: 17,40 BAD (FFHFHH)
|
||||
Size 34: 17,41 GOOD (HHFFFF)
|
||||
Size 35: 18,42 BAD (FFHFHH)
|
||||
Size 36: 18,43 GOOD (HHFFFF)
|
||||
Size 37: 19,44 BAD (FFHFHH)
|
||||
Size 38: 19,46 GOOD (HHFFFF)
|
||||
Size 39: 20,47 BAD (FFHFHH)
|
||||
Size 40: 20,48 GOOD (HHFFFF)
|
||||
Size 41: 21,49 BAD (FFHFHH)
|
||||
Size 42: 21,50 GOOD (HHFFFF)
|
||||
Size 43: 22,52 BAD (FFHFHH)
|
||||
Size 44: 22,53 GOOD (HHFFFF)
|
||||
Size 45: 23,54 BAD (FFHFHH)
|
||||
Size 46: 23,55 GOOD (HHFFFF)
|
||||
Size 47: 24,56 BAD (FFHFHH)
|
||||
Size 48: 24,58 GOOD (HHFFFF)
|
||||
Size 49: 25,59 BAD (FFHFHH)
|
||||
Size 50: 25,60 GOOD (HHFFFF)
|
||||
Size 51: 26,61 BAD (FFHFHH)
|
||||
Size 52: 26,62 GOOD (HHFFFF)
|
||||
Size 53: 27,64 BAD (FFHFHH)
|
||||
Size 54: 27,65 GOOD (HHFFFF)
|
||||
Size 55: 28,66 BAD (FFHFHH)
|
||||
Size 56: 28,67 GOOD (HHFFFF)
|
||||
Size 57: 29,68 BAD (FFHFHH)
|
||||
Size 58: 29,70 GOOD (HHFFFF)
|
||||
Size 59: 30,71 BAD (FFHFHH)
|
||||
Size 60: 30,72 GOOD (HHFFFF)
|
||||
Size 61: 31,73 BAD (FFHFHH)
|
||||
Size 62: 31,74 GOOD (HHFFFF)
|
||||
Size 63: 32,76 BAD (FFHFHH)
|
||||
Size 64: 32,77 GOOD (HHFFFF)
|
||||
Size 65: 33,78 BAD (FFHFHH)
|
||||
Size 66: 33,79 GOOD (HHFFFF)
|
||||
Size 67: 34,80 BAD (FFHFHH)
|
||||
Size 68: 34,82 GOOD (HHFFFF)
|
||||
Size 69: 35,83 BAD (FFHFHH)
|
||||
Size 70: 35,84 GOOD (HHFFFF)
|
||||
Size 71: 36,85 BAD (FFHFHH)
|
||||
Size 72: 36,86 GOOD (HHFFFF)
|
||||
Size 73: 37,88 BAD (FFHFHH)
|
||||
Size 74: 37,89 GOOD (HHFFFF)
|
||||
Size 75: 38,90 BAD (FFHFHH)
|
||||
Size 76: 38,91 GOOD (HHFFFF)
|
||||
Size 77: 39,92 BAD (FFHFHH)
|
||||
Size 78: 39,94 GOOD (HHFFFF)
|
||||
Size 79: 40,95 BAD (FFHFHH)
|
||||
Size 80: 40,96 GOOD (HHFFFF)
|
||||
Size 81: 41,97 BAD (FFHFHH)
|
||||
Size 82: 41,98 GOOD (HHFFFF)
|
||||
Size 83: 42,100 BAD (FFHFHH)
|
||||
Size 84: 42,101 GOOD (HHFFFF)
|
||||
Size 85: 43,102 BAD (FFHFHH)
|
||||
Size 86: 43,103 GOOD (HHFFFF)
|
||||
Size 87: 44,104 BAD (FFHFHH)
|
||||
Size 88: 44,106 GOOD (HHFFFF)
|
||||
Size 89: 45,107 BAD (FFHFHH)
|
||||
Size 90: 45,108 GOOD (HHFFFF)
|
||||
Size 91: 46,109 BAD (FFHFHH)
|
||||
Size 92: 46,110 GOOD (HHFFFF)
|
||||
Size 93: 47,112 BAD (FFHFHH)
|
||||
Size 94: 47,113 GOOD (HHFFFF)
|
||||
Size 95: 48,114 BAD (FFHFHH)
|
||||
Size 96: 48,115 GOOD (HHFFFF)
|
||||
Size 97: 49,116 BAD (FFHFHH)
|
||||
Size 98: 49,118 GOOD (HHFFFF)
|
||||
Size 99: 50,119 BAD (FFHFHH)
|
||||
Size 100: 50,120 GOOD (HHFFFF)
|
||||
|
||||
Windows 10 14342 New Console
|
||||
----------------------------
|
||||
|
||||
Size 1: 1,1 GOOD (HHFFFF)
|
||||
Size 2: 1,2 GOOD (HHFFFF)
|
||||
Size 3: 2,3 GOOD (HHFFFF)
|
||||
Size 4: 2,4 GOOD (HHFFFF)
|
||||
Size 5: 3,5 GOOD (HHFFFF)
|
||||
Size 6: 3,6 GOOD (HHFFFF)
|
||||
Size 7: 4,7 GOOD (HHFFFF)
|
||||
Size 8: 4,8 GOOD (HHFFFF)
|
||||
Size 9: 5,9 GOOD (HHFFFF)
|
||||
Size 10: 5,10 GOOD (HHFFFF)
|
||||
Size 11: 6,11 GOOD (HHFFFF)
|
||||
Size 12: 6,12 GOOD (HHFFFF)
|
||||
Size 13: 7,13 GOOD (HHFFFF)
|
||||
Size 14: 7,14 GOOD (HHFFFF)
|
||||
Size 15: 8,15 GOOD (HHFFFF)
|
||||
Size 16: 8,16 GOOD (HHFFFF)
|
||||
Size 17: 9,17 GOOD (HHFFFF)
|
||||
Size 18: 9,18 GOOD (HHFFFF)
|
||||
Size 19: 10,19 GOOD (HHFFFF)
|
||||
Size 20: 10,20 GOOD (HHFFFF)
|
||||
Size 21: 11,21 GOOD (HHFFFF)
|
||||
Size 22: 11,22 GOOD (HHFFFF)
|
||||
Size 23: 12,23 GOOD (HHFFFF)
|
||||
Size 24: 12,24 GOOD (HHFFFF)
|
||||
Size 25: 13,25 GOOD (HHFFFF)
|
||||
Size 26: 13,26 GOOD (HHFFFF)
|
||||
Size 27: 14,27 GOOD (HHFFFF)
|
||||
Size 28: 14,28 GOOD (HHFFFF)
|
||||
Size 29: 15,29 GOOD (HHFFFF)
|
||||
Size 30: 15,30 GOOD (HHFFFF)
|
||||
Size 31: 16,31 GOOD (HHFFFF)
|
||||
Size 32: 16,32 GOOD (HHFFFF)
|
||||
Size 33: 17,33 GOOD (HHFFFF)
|
||||
Size 34: 17,34 GOOD (HHFFFF)
|
||||
Size 35: 18,35 GOOD (HHFFFF)
|
||||
Size 36: 18,36 GOOD (HHFFFF)
|
||||
Size 37: 19,37 GOOD (HHFFFF)
|
||||
Size 38: 19,38 GOOD (HHFFFF)
|
||||
Size 39: 20,39 GOOD (HHFFFF)
|
||||
Size 40: 20,40 GOOD (HHFFFF)
|
||||
Size 41: 21,41 GOOD (HHFFFF)
|
||||
Size 42: 21,42 GOOD (HHFFFF)
|
||||
Size 43: 22,43 GOOD (HHFFFF)
|
||||
Size 44: 22,44 GOOD (HHFFFF)
|
||||
Size 45: 23,45 GOOD (HHFFFF)
|
||||
Size 46: 23,46 GOOD (HHFFFF)
|
||||
Size 47: 24,47 GOOD (HHFFFF)
|
||||
Size 48: 24,48 GOOD (HHFFFF)
|
||||
Size 49: 25,49 GOOD (HHFFFF)
|
||||
Size 50: 25,50 GOOD (HHFFFF)
|
||||
Size 51: 26,51 GOOD (HHFFFF)
|
||||
Size 52: 26,52 GOOD (HHFFFF)
|
||||
Size 53: 27,53 GOOD (HHFFFF)
|
||||
Size 54: 27,54 GOOD (HHFFFF)
|
||||
Size 55: 28,55 GOOD (HHFFFF)
|
||||
Size 56: 28,56 GOOD (HHFFFF)
|
||||
Size 57: 29,57 GOOD (HHFFFF)
|
||||
Size 58: 29,58 GOOD (HHFFFF)
|
||||
Size 59: 30,59 GOOD (HHFFFF)
|
||||
Size 60: 30,60 GOOD (HHFFFF)
|
||||
Size 61: 31,61 GOOD (HHFFFF)
|
||||
Size 62: 31,62 GOOD (HHFFFF)
|
||||
Size 63: 32,63 GOOD (HHFFFF)
|
||||
Size 64: 32,64 GOOD (HHFFFF)
|
||||
Size 65: 33,65 GOOD (HHFFFF)
|
||||
Size 66: 33,66 GOOD (HHFFFF)
|
||||
Size 67: 34,67 GOOD (HHFFFF)
|
||||
Size 68: 34,68 GOOD (HHFFFF)
|
||||
Size 69: 35,69 GOOD (HHFFFF)
|
||||
Size 70: 35,70 GOOD (HHFFFF)
|
||||
Size 71: 36,71 GOOD (HHFFFF)
|
||||
Size 72: 36,72 GOOD (HHFFFF)
|
||||
Size 73: 37,73 GOOD (HHFFFF)
|
||||
Size 74: 37,74 GOOD (HHFFFF)
|
||||
Size 75: 38,75 GOOD (HHFFFF)
|
||||
Size 76: 38,76 GOOD (HHFFFF)
|
||||
Size 77: 39,77 GOOD (HHFFFF)
|
||||
Size 78: 39,78 GOOD (HHFFFF)
|
||||
Size 79: 40,79 GOOD (HHFFFF)
|
||||
Size 80: 40,80 GOOD (HHFFFF)
|
||||
Size 81: 41,81 GOOD (HHFFFF)
|
||||
Size 82: 41,82 GOOD (HHFFFF)
|
||||
Size 83: 42,83 GOOD (HHFFFF)
|
||||
Size 84: 42,84 GOOD (HHFFFF)
|
||||
Size 85: 43,85 GOOD (HHFFFF)
|
||||
Size 86: 43,86 GOOD (HHFFFF)
|
||||
Size 87: 44,87 GOOD (HHFFFF)
|
||||
Size 88: 44,88 GOOD (HHFFFF)
|
||||
Size 89: 45,89 GOOD (HHFFFF)
|
||||
Size 90: 45,90 GOOD (HHFFFF)
|
||||
Size 91: 46,91 GOOD (HHFFFF)
|
||||
Size 92: 46,92 GOOD (HHFFFF)
|
||||
Size 93: 47,93 GOOD (HHFFFF)
|
||||
Size 94: 47,94 GOOD (HHFFFF)
|
||||
Size 95: 48,95 GOOD (HHFFFF)
|
||||
Size 96: 48,96 GOOD (HHFFFF)
|
||||
Size 97: 49,97 GOOD (HHFFFF)
|
||||
Size 98: 49,98 GOOD (HHFFFF)
|
||||
Size 99: 50,99 GOOD (HHFFFF)
|
||||
Size 100: 50,100 GOOD (HHFFFF)
|
||||
Generated
Vendored
+16
@@ -0,0 +1,16 @@
|
||||
The narrowest allowed console window, in pixels, on a conventional (~96dpi)
|
||||
monitor:
|
||||
|
||||
(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 1 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12
|
||||
|
||||
(mode con: cols=40 lines=40) && SetFont.exe -face "Lucida Console" -h 16 && (ping -n 4 127.0.0.1 > NUL) && cls && GetConsolePos.exe && SetFont.exe -face "Lucida Console" -h 12
|
||||
|
||||
sz1:px sz1:col sz16:px sz16:col
|
||||
Vista: 124 104 137 10
|
||||
Windows 7: 132 112 147 11
|
||||
Windows 8: 140 120 147 11
|
||||
Windows 8.1: 140 120 147 11
|
||||
Windows 10 OLD: 136 116 147 11
|
||||
Windows 10 NEW: 136 103 136 10
|
||||
|
||||
I used build 14342 to test Windows 10.
|
||||
Generated
Vendored
+4
@@ -0,0 +1,4 @@
|
||||
As before, avoid odd sizes in favor of even sizes.
|
||||
|
||||
It's curious that the Japanese font is handled so poorly, especially with
|
||||
Windows 8 and later.
|
||||
Generated
Vendored
+144
@@ -0,0 +1,144 @@
|
||||
Issues:
|
||||
|
||||
- Starting with the 14342 build, changing the font using
|
||||
SetCurrentConsoleFontEx does not affect the window size. e.g. The content
|
||||
itself will resize/redraw, but the window neither shrinks nor expands.
|
||||
Presumably this is an oversight? It's almost a convenience; if a program
|
||||
is going to resize the window anyway, then it's nice that the window size
|
||||
contraints don't get in the way. Ordinarily, changing the font doesn't just
|
||||
change the window size in pixels--it can also change the size as measured in
|
||||
rows and columns.
|
||||
|
||||
- (Aside: in the 14342 build, there is also a bug with wmic.exe. Open a console
|
||||
with more than 300 lines of screen buffer, then fill those lines with, e.g.,
|
||||
dir /s. Then run wmic.exe. You won't be able to see the wmic.exe prompt.
|
||||
If you query the screen buffer info somehow, you'll notice that the srWindow
|
||||
is not contained within the dwSize. This breaks winpty's scraping, because
|
||||
it's invalid.)
|
||||
|
||||
- In build 14316, with the Japanese locale, with the 437 code page, attempting
|
||||
to set the Consolas font instead sets the Terminal (raster) font. It seems
|
||||
to pick an appropriate vertical size.
|
||||
|
||||
- It seems necessary to specify "-family 0x36" for maximum reliability.
|
||||
Setting the family to 0 almost always works, and specifying just -tt rarely
|
||||
works.
|
||||
|
||||
Win7
|
||||
English locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 932 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt unreliable
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
|
||||
Win10 Build 10586
|
||||
New console
|
||||
Japanese locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
|
||||
Win10 Build 14316
|
||||
Old console
|
||||
English locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 932 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selected very small Consolas font
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
New console
|
||||
English locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt works
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 932 code page:
|
||||
SetFont.exe -face Consolas -h 16 selects gothic instead
|
||||
SetFont.exe -face Consolas -h 16 -tt selects gothic instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead
|
||||
Japanese locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36(*) selects Terminal font instead
|
||||
|
||||
Win10 Build 14342
|
||||
Old Console
|
||||
English locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 932 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
New console
|
||||
English locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 works
|
||||
SetFont.exe -face Consolas -h 16 -tt works
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
Japanese locale / 932 code page:
|
||||
SetFont.exe -face Consolas -h 16 selects gothic instead
|
||||
SetFont.exe -face Consolas -h 16 -tt selects gothic instead
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 selects gothic instead
|
||||
Japanese locale / 437 code page:
|
||||
SetFont.exe -face Consolas -h 16 selects Terminal font instead
|
||||
SetFont.exe -face Consolas -h 16 -tt works
|
||||
SetFont.exe -face Consolas -h 16 -family 0x36 works
|
||||
|
||||
(*) I was trying to figure out whether the inconsistency was at when I stumbled
|
||||
onto this completely unexpected bug. Here's more detail:
|
||||
|
||||
F:\>SetFont.exe -face Consolas -h 16 -family 0x36 -weight normal -w 8
|
||||
Setting to: nFont=0 dwFontSize=(8,16) FontFamily=0x36 FontWeight=400 FaceName="Consolas"
|
||||
SetCurrentConsoleFontEx returned 1
|
||||
|
||||
F:\>GetFont.exe
|
||||
largestConsoleWindowSize=(96,50)
|
||||
maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
|
||||
maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
|
||||
00-00: 12x16
|
||||
GetNumberOfConsoleFonts returned 0
|
||||
CP=437 OutputCP=437
|
||||
|
||||
F:\>SetFont.exe -face "Lucida Console" -h 16 -family 0x36 -weight normal
|
||||
Setting to: nFont=0 dwFontSize=(0,16) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console"
|
||||
SetCurrentConsoleFontEx returned 1
|
||||
|
||||
F:\>GetFont.exe
|
||||
largestConsoleWindowSize=(96,50)
|
||||
maxWnd=0: nFont=0 dwFontSize=(12,16) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
|
||||
maxWnd=1: nFont=0 dwFontSize=(96,25) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
|
||||
00-00: 12x16
|
||||
GetNumberOfConsoleFonts returned 0
|
||||
CP=437 OutputCP=437
|
||||
|
||||
F:\>SetFont.exe -face "Lucida Console" -h 12 -family 0x36 -weight normal
|
||||
Setting to: nFont=0 dwFontSize=(0,12) FontFamily=0x36 FontWeight=400 FaceName="Lucida Console"
|
||||
SetCurrentConsoleFontEx returned 1
|
||||
|
||||
F:\>GetFont.exe
|
||||
largestConsoleWindowSize=(230,66)
|
||||
maxWnd=0: nFont=0 dwFontSize=(5,12) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
|
||||
maxWnd=1: nFont=0 dwFontSize=(116,36) FontFamily=0x30 FontWeight=400 FaceName=Terminal (54 65 72 6D 69 6E 61 6C)
|
||||
00-00: 5x12
|
||||
GetNumberOfConsoleFonts returned 0
|
||||
CP=437 OutputCP=437
|
||||
|
||||
Even attempting to set to a Lucida Console / Consolas font from the Console
|
||||
properties dialog fails.
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "TestUtil.cc"
|
||||
|
||||
#define COUNT_OF(array) (sizeof(array) / sizeof((array)[0]))
|
||||
|
||||
// See https://en.wikipedia.org/wiki/List_of_CJK_fonts
|
||||
const wchar_t kMSGothic[] = { 0xff2d, 0xff33, 0x0020, 0x30b4, 0x30b7, 0x30c3, 0x30af, 0 }; // Japanese
|
||||
const wchar_t kNSimSun[] = { 0x65b0, 0x5b8b, 0x4f53, 0 }; // Simplified Chinese
|
||||
const wchar_t kMingLight[] = { 0x7d30, 0x660e, 0x9ad4, 0 }; // Traditional Chinese
|
||||
const wchar_t kGulimChe[] = { 0xad74, 0xb9bc, 0xccb4, 0 }; // Korean
|
||||
|
||||
std::vector<bool> condense(const std::vector<CHAR_INFO> &buf) {
|
||||
std::vector<bool> ret;
|
||||
size_t i = 0;
|
||||
while (i < buf.size()) {
|
||||
if (buf[i].Char.UnicodeChar == L' ' &&
|
||||
((buf[i].Attributes & 0x300) == 0)) {
|
||||
// end of line
|
||||
break;
|
||||
} else if (i + 1 < buf.size() &&
|
||||
((buf[i].Attributes & 0x300) == 0x100) &&
|
||||
((buf[i + 1].Attributes & 0x300) == 0x200) &&
|
||||
buf[i].Char.UnicodeChar != L' ' &&
|
||||
buf[i].Char.UnicodeChar == buf[i + 1].Char.UnicodeChar) {
|
||||
// double-width
|
||||
ret.push_back(true);
|
||||
i += 2;
|
||||
} else if ((buf[i].Attributes & 0x300) == 0) {
|
||||
// single-width
|
||||
ret.push_back(false);
|
||||
i++;
|
||||
} else {
|
||||
ASSERT(false && "unexpected output");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
if (argc != 2) {
|
||||
printf("Usage: %s \"arguments for SetFont.exe\"\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
const char *setFontArgs = argv[1];
|
||||
|
||||
const wchar_t testLine[] = { 0xA2, 0xA3, 0x2014, 0x3044, 0x30FC, 0x4000, 0 };
|
||||
const HANDLE conout = openConout();
|
||||
|
||||
char setFontCmd[1024];
|
||||
for (int h = 1; h <= 100; ++h) {
|
||||
sprintf(setFontCmd, ".\\SetFont.exe %s -h %d && cls", setFontArgs, h);
|
||||
system(setFontCmd);
|
||||
|
||||
CONSOLE_FONT_INFOEX infoex = {};
|
||||
infoex.cbSize = sizeof(infoex);
|
||||
BOOL success = GetCurrentConsoleFontEx(conout, FALSE, &infoex);
|
||||
ASSERT(success && "GetCurrentConsoleFontEx failed");
|
||||
|
||||
DWORD actual = 0;
|
||||
success = WriteConsoleW(conout, testLine, wcslen(testLine), &actual, nullptr);
|
||||
ASSERT(success && actual == wcslen(testLine));
|
||||
|
||||
std::vector<CHAR_INFO> readBuf(14);
|
||||
const SMALL_RECT readRegion = {0, 0, static_cast<short>(readBuf.size() - 1), 0};
|
||||
SMALL_RECT readRegion2 = readRegion;
|
||||
success = ReadConsoleOutputW(
|
||||
conout, readBuf.data(),
|
||||
{static_cast<short>(readBuf.size()), 1},
|
||||
{0, 0},
|
||||
&readRegion2);
|
||||
ASSERT(success && !memcmp(&readRegion, &readRegion2, sizeof(readRegion)));
|
||||
|
||||
const auto widths = condense(readBuf);
|
||||
std::string widthsStr;
|
||||
for (bool width : widths) {
|
||||
widthsStr.append(width ? "F" : "H");
|
||||
}
|
||||
char size[16];
|
||||
sprintf(size, "%d,%d", infoex.dwFontSize.X, infoex.dwFontSize.Y);
|
||||
const char *status = "";
|
||||
if (widthsStr == "HHFFFF") {
|
||||
status = "GOOD";
|
||||
} else if (widthsStr == "HHHFFF") {
|
||||
status = "OK";
|
||||
} else {
|
||||
status = "BAD";
|
||||
}
|
||||
trace("Size %3d: %-7s %-4s (%s)", h, size, status, widthsStr.c_str());
|
||||
}
|
||||
sprintf(setFontCmd, ".\\SetFont.exe %s -h 14", setFontArgs);
|
||||
system(setFontCmd);
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
static inline void formatChar(char *str, char ch)
|
||||
{
|
||||
// Print some common control codes.
|
||||
switch (ch) {
|
||||
case '\r': strcpy(str, "CR "); break;
|
||||
case '\n': strcpy(str, "LF "); break;
|
||||
case ' ': strcpy(str, "SP "); break;
|
||||
case 27: strcpy(str, "^[ "); break;
|
||||
case 3: strcpy(str, "^C "); break;
|
||||
default:
|
||||
if (isgraph(ch))
|
||||
sprintf(str, "%c ", ch);
|
||||
else
|
||||
sprintf(str, "%02x ", ch);
|
||||
break;
|
||||
}
|
||||
}
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include "TestUtil.cc"
|
||||
|
||||
const int SC_CONSOLE_MARK = 0xFFF2;
|
||||
const int SC_CONSOLE_SELECT_ALL = 0xFFF5;
|
||||
|
||||
int main(int argc, char *argv[0]) {
|
||||
|
||||
if (argc != 2) {
|
||||
printf("Usage: %s (mark|selectall|read)\n", argv[0]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
enum class Test { Mark, SelectAll, Read } test;
|
||||
if (!strcmp(argv[1], "mark")) {
|
||||
test = Test::Mark;
|
||||
} else if (!strcmp(argv[1], "selectall")) {
|
||||
test = Test::SelectAll;
|
||||
} else if (!strcmp(argv[1], "read")) {
|
||||
test = Test::Read;
|
||||
} else {
|
||||
printf("Invalid test: %s\n", argv[1]);
|
||||
return 1;
|
||||
}
|
||||
|
||||
HANDLE conout = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
TimeMeasurement tm;
|
||||
HWND hwnd = GetConsoleWindow();
|
||||
|
||||
setWindowPos(0, 0, 1, 1);
|
||||
setBufferSize(100, 3000);
|
||||
system("cls");
|
||||
setWindowPos(0, 2975, 100, 25);
|
||||
setCursorPos(0, 2999);
|
||||
|
||||
ShowWindow(hwnd, SW_HIDE);
|
||||
|
||||
for (int i = 0; i < 1000; ++i) {
|
||||
// CONSOLE_SCREEN_BUFFER_INFO info = {};
|
||||
// GetConsoleScreenBufferInfo(conout, &info);
|
||||
|
||||
if (test == Test::Mark) {
|
||||
SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_MARK, 0);
|
||||
SendMessage(hwnd, WM_CHAR, 27, 0x00010001);
|
||||
} else if (test == Test::SelectAll) {
|
||||
SendMessage(hwnd, WM_SYSCOMMAND, SC_CONSOLE_SELECT_ALL, 0);
|
||||
SendMessage(hwnd, WM_CHAR, 27, 0x00010001);
|
||||
} else if (test == Test::Read) {
|
||||
static CHAR_INFO buffer[100 * 3000];
|
||||
const SMALL_RECT readRegion = {0, 0, 99, 2999};
|
||||
SMALL_RECT tmp = readRegion;
|
||||
BOOL ret = ReadConsoleOutput(conout, buffer, {100, 3000}, {0, 0}, &tmp);
|
||||
ASSERT(ret && !memcmp(&tmp, &readRegion, sizeof(tmp)));
|
||||
}
|
||||
}
|
||||
|
||||
ShowWindow(hwnd, SW_SHOW);
|
||||
|
||||
printf("elapsed: %f\n", tm.elapsed());
|
||||
return 0;
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
#include <conio.h>
|
||||
#include <ctype.h>
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("\nPress any keys -- Ctrl-D exits\n\n");
|
||||
|
||||
while (true) {
|
||||
const int ch = getch();
|
||||
printf("0x%x", ch);
|
||||
if (isgraph(ch)) {
|
||||
printf(" '%c'", ch);
|
||||
}
|
||||
printf("\n");
|
||||
if (ch == 0x4) { // Ctrl-D
|
||||
break;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
#include <windows.h>
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "TestUtil.cc"
|
||||
|
||||
int main() {
|
||||
const HANDLE conout = openConout();
|
||||
|
||||
CONSOLE_SCREEN_BUFFER_INFO info = {};
|
||||
BOOL ret = GetConsoleScreenBufferInfo(conout, &info);
|
||||
ASSERT(ret && "GetConsoleScreenBufferInfo failed");
|
||||
|
||||
trace("cursor=%d,%d", info.dwCursorPosition.X, info.dwCursorPosition.Y);
|
||||
printf("cursor=%d,%d\n", info.dwCursorPosition.X, info.dwCursorPosition.Y);
|
||||
|
||||
trace("srWindow={L=%d,T=%d,R=%d,B=%d}", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom);
|
||||
printf("srWindow={L=%d,T=%d,R=%d,B=%d}\n", info.srWindow.Left, info.srWindow.Top, info.srWindow.Right, info.srWindow.Bottom);
|
||||
|
||||
trace("dwSize=%d,%d", info.dwSize.X, info.dwSize.Y);
|
||||
printf("dwSize=%d,%d\n", info.dwSize.X, info.dwSize.Y);
|
||||
|
||||
const HWND hwnd = GetConsoleWindow();
|
||||
if (hwnd != NULL) {
|
||||
RECT r = {};
|
||||
if (GetWindowRect(hwnd, &r)) {
|
||||
const int w = r.right - r.left;
|
||||
const int h = r.bottom - r.top;
|
||||
trace("hwnd: pos=(%d,%d) size=(%d,%d)", r.left, r.top, w, h);
|
||||
printf("hwnd: pos=(%d,%d) size=(%d,%d)\n", r.left, r.top, w, h);
|
||||
} else {
|
||||
trace("GetWindowRect failed");
|
||||
printf("GetWindowRect failed\n");
|
||||
}
|
||||
} else {
|
||||
trace("GetConsoleWindow returned NULL");
|
||||
printf("GetConsoleWindow returned NULL\n");
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
+261
@@ -0,0 +1,261 @@
|
||||
#include <windows.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <wchar.h>
|
||||
|
||||
#include "../src/shared/OsModule.h"
|
||||
#include "../src/shared/StringUtil.h"
|
||||
|
||||
#include "TestUtil.cc"
|
||||
#include "../src/shared/StringUtil.cc"
|
||||
|
||||
#define COUNT_OF(x) (sizeof(x) / sizeof((x)[0]))
|
||||
|
||||
// Some of these types and functions are missing from the MinGW headers.
|
||||
// Others are undocumented.
|
||||
|
||||
struct AGENT_CONSOLE_FONT_INFO {
|
||||
DWORD nFont;
|
||||
COORD dwFontSize;
|
||||
};
|
||||
|
||||
struct AGENT_CONSOLE_FONT_INFOEX {
|
||||
ULONG cbSize;
|
||||
DWORD nFont;
|
||||
COORD dwFontSize;
|
||||
UINT FontFamily;
|
||||
UINT FontWeight;
|
||||
WCHAR FaceName[LF_FACESIZE];
|
||||
};
|
||||
|
||||
// undocumented XP API
|
||||
typedef BOOL WINAPI SetConsoleFont_t(
|
||||
HANDLE hOutput,
|
||||
DWORD dwFontIndex);
|
||||
|
||||
// undocumented XP API
|
||||
typedef DWORD WINAPI GetNumberOfConsoleFonts_t();
|
||||
|
||||
// XP and up
|
||||
typedef BOOL WINAPI GetCurrentConsoleFont_t(
|
||||
HANDLE hOutput,
|
||||
BOOL bMaximumWindow,
|
||||
AGENT_CONSOLE_FONT_INFO *lpConsoleCurrentFont);
|
||||
|
||||
// XP and up
|
||||
typedef COORD WINAPI GetConsoleFontSize_t(
|
||||
HANDLE hConsoleOutput,
|
||||
DWORD nFont);
|
||||
|
||||
// Vista and up
|
||||
typedef BOOL WINAPI GetCurrentConsoleFontEx_t(
|
||||
HANDLE hConsoleOutput,
|
||||
BOOL bMaximumWindow,
|
||||
AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx);
|
||||
|
||||
// Vista and up
|
||||
typedef BOOL WINAPI SetCurrentConsoleFontEx_t(
|
||||
HANDLE hConsoleOutput,
|
||||
BOOL bMaximumWindow,
|
||||
AGENT_CONSOLE_FONT_INFOEX *lpConsoleCurrentFontEx);
|
||||
|
||||
#define GET_MODULE_PROC(mod, funcName) \
|
||||
m_##funcName = reinterpret_cast<funcName##_t*>((mod).proc(#funcName)); \
|
||||
|
||||
#define DEFINE_ACCESSOR(funcName) \
|
||||
funcName##_t &funcName() const { \
|
||||
ASSERT(valid()); \
|
||||
return *m_##funcName; \
|
||||
}
|
||||
|
||||
class XPFontAPI {
|
||||
public:
|
||||
XPFontAPI() : m_kernel32(L"kernel32.dll") {
|
||||
GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFont);
|
||||
GET_MODULE_PROC(m_kernel32, GetConsoleFontSize);
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return m_GetCurrentConsoleFont != NULL &&
|
||||
m_GetConsoleFontSize != NULL;
|
||||
}
|
||||
|
||||
DEFINE_ACCESSOR(GetCurrentConsoleFont)
|
||||
DEFINE_ACCESSOR(GetConsoleFontSize)
|
||||
|
||||
private:
|
||||
OsModule m_kernel32;
|
||||
GetCurrentConsoleFont_t *m_GetCurrentConsoleFont;
|
||||
GetConsoleFontSize_t *m_GetConsoleFontSize;
|
||||
};
|
||||
|
||||
class UndocumentedXPFontAPI : public XPFontAPI {
|
||||
public:
|
||||
UndocumentedXPFontAPI() : m_kernel32(L"kernel32.dll") {
|
||||
GET_MODULE_PROC(m_kernel32, SetConsoleFont);
|
||||
GET_MODULE_PROC(m_kernel32, GetNumberOfConsoleFonts);
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return this->XPFontAPI::valid() &&
|
||||
m_SetConsoleFont != NULL &&
|
||||
m_GetNumberOfConsoleFonts != NULL;
|
||||
}
|
||||
|
||||
DEFINE_ACCESSOR(SetConsoleFont)
|
||||
DEFINE_ACCESSOR(GetNumberOfConsoleFonts)
|
||||
|
||||
private:
|
||||
OsModule m_kernel32;
|
||||
SetConsoleFont_t *m_SetConsoleFont;
|
||||
GetNumberOfConsoleFonts_t *m_GetNumberOfConsoleFonts;
|
||||
};
|
||||
|
||||
class VistaFontAPI : public XPFontAPI {
|
||||
public:
|
||||
VistaFontAPI() : m_kernel32(L"kernel32.dll") {
|
||||
GET_MODULE_PROC(m_kernel32, GetCurrentConsoleFontEx);
|
||||
GET_MODULE_PROC(m_kernel32, SetCurrentConsoleFontEx);
|
||||
}
|
||||
|
||||
bool valid() const {
|
||||
return this->XPFontAPI::valid() &&
|
||||
m_GetCurrentConsoleFontEx != NULL &&
|
||||
m_SetCurrentConsoleFontEx != NULL;
|
||||
}
|
||||
|
||||
DEFINE_ACCESSOR(GetCurrentConsoleFontEx)
|
||||
DEFINE_ACCESSOR(SetCurrentConsoleFontEx)
|
||||
|
||||
private:
|
||||
OsModule m_kernel32;
|
||||
GetCurrentConsoleFontEx_t *m_GetCurrentConsoleFontEx;
|
||||
SetCurrentConsoleFontEx_t *m_SetCurrentConsoleFontEx;
|
||||
};
|
||||
|
||||
static std::vector<std::pair<DWORD, COORD> > readFontTable(
|
||||
XPFontAPI &api, HANDLE conout, DWORD maxCount) {
|
||||
std::vector<std::pair<DWORD, COORD> > ret;
|
||||
for (DWORD i = 0; i < maxCount; ++i) {
|
||||
COORD size = api.GetConsoleFontSize()(conout, i);
|
||||
if (size.X == 0 && size.Y == 0) {
|
||||
break;
|
||||
}
|
||||
ret.push_back(std::make_pair(i, size));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dumpFontTable(HANDLE conout) {
|
||||
const int kMaxCount = 1000;
|
||||
XPFontAPI api;
|
||||
if (!api.valid()) {
|
||||
printf("dumpFontTable: cannot dump font table -- missing APIs\n");
|
||||
return;
|
||||
}
|
||||
std::vector<std::pair<DWORD, COORD> > table =
|
||||
readFontTable(api, conout, kMaxCount);
|
||||
std::string line;
|
||||
char tmp[128];
|
||||
size_t first = 0;
|
||||
while (first < table.size()) {
|
||||
size_t last = std::min(table.size() - 1, first + 10 - 1);
|
||||
winpty_snprintf(tmp, "%02u-%02u:",
|
||||
static_cast<unsigned>(first), static_cast<unsigned>(last));
|
||||
line = tmp;
|
||||
for (size_t i = first; i <= last; ++i) {
|
||||
if (i % 10 == 5) {
|
||||
line += " - ";
|
||||
}
|
||||
winpty_snprintf(tmp, " %2dx%-2d",
|
||||
table[i].second.X, table[i].second.Y);
|
||||
line += tmp;
|
||||
}
|
||||
printf("%s\n", line.c_str());
|
||||
first = last + 1;
|
||||
}
|
||||
if (table.size() == kMaxCount) {
|
||||
printf("... stopped reading at %d fonts ...\n", kMaxCount);
|
||||
}
|
||||
}
|
||||
|
||||
static std::string stringToCodePoints(const std::wstring &str) {
|
||||
std::string ret = "(";
|
||||
for (size_t i = 0; i < str.size(); ++i) {
|
||||
char tmp[32];
|
||||
winpty_snprintf(tmp, "%X", str[i]);
|
||||
if (ret.size() > 1) {
|
||||
ret.push_back(' ');
|
||||
}
|
||||
ret += tmp;
|
||||
}
|
||||
ret.push_back(')');
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void dumpFontInfoEx(
|
||||
const AGENT_CONSOLE_FONT_INFOEX &infoex) {
|
||||
std::wstring faceName(infoex.FaceName,
|
||||
winpty_wcsnlen(infoex.FaceName, COUNT_OF(infoex.FaceName)));
|
||||
cprintf(L"nFont=%u dwFontSize=(%d,%d) "
|
||||
"FontFamily=0x%x FontWeight=%u FaceName=%ls %hs\n",
|
||||
static_cast<unsigned>(infoex.nFont),
|
||||
infoex.dwFontSize.X, infoex.dwFontSize.Y,
|
||||
infoex.FontFamily, infoex.FontWeight, faceName.c_str(),
|
||||
stringToCodePoints(faceName).c_str());
|
||||
}
|
||||
|
||||
static void dumpVistaFont(VistaFontAPI &api, HANDLE conout, BOOL maxWindow) {
|
||||
AGENT_CONSOLE_FONT_INFOEX infoex = {0};
|
||||
infoex.cbSize = sizeof(infoex);
|
||||
if (!api.GetCurrentConsoleFontEx()(conout, maxWindow, &infoex)) {
|
||||
printf("GetCurrentConsoleFontEx call failed\n");
|
||||
return;
|
||||
}
|
||||
dumpFontInfoEx(infoex);
|
||||
}
|
||||
|
||||
static void dumpXPFont(XPFontAPI &api, HANDLE conout, BOOL maxWindow) {
|
||||
AGENT_CONSOLE_FONT_INFO info = {0};
|
||||
if (!api.GetCurrentConsoleFont()(conout, maxWindow, &info)) {
|
||||
printf("GetCurrentConsoleFont call failed\n");
|
||||
return;
|
||||
}
|
||||
printf("nFont=%u dwFontSize=(%d,%d)\n",
|
||||
static_cast<unsigned>(info.nFont),
|
||||
info.dwFontSize.X, info.dwFontSize.Y);
|
||||
}
|
||||
|
||||
static void dumpFontAndTable(HANDLE conout) {
|
||||
VistaFontAPI vista;
|
||||
if (vista.valid()) {
|
||||
printf("maxWnd=0: "); dumpVistaFont(vista, conout, FALSE);
|
||||
printf("maxWnd=1: "); dumpVistaFont(vista, conout, TRUE);
|
||||
dumpFontTable(conout);
|
||||
return;
|
||||
}
|
||||
UndocumentedXPFontAPI xp;
|
||||
if (xp.valid()) {
|
||||
printf("maxWnd=0: "); dumpXPFont(xp, conout, FALSE);
|
||||
printf("maxWnd=1: "); dumpXPFont(xp, conout, TRUE);
|
||||
dumpFontTable(conout);
|
||||
return;
|
||||
}
|
||||
printf("setSmallFont: neither Vista nor XP APIs detected -- giving up\n");
|
||||
dumpFontTable(conout);
|
||||
}
|
||||
|
||||
int main() {
|
||||
const HANDLE conout = openConout();
|
||||
const COORD largest = GetLargestConsoleWindowSize(conout);
|
||||
printf("largestConsoleWindowSize=(%d,%d)\n", largest.X, largest.Y);
|
||||
dumpFontAndTable(conout);
|
||||
UndocumentedXPFontAPI xp;
|
||||
if (xp.valid()) {
|
||||
printf("GetNumberOfConsoleFonts returned %u\n", xp.GetNumberOfConsoleFonts()());
|
||||
} else {
|
||||
printf("The GetNumberOfConsoleFonts API was missing\n");
|
||||
}
|
||||
printf("CP=%u OutputCP=%u\n", GetConsoleCP(), GetConsoleOutputCP());
|
||||
return 0;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user