@winccoa-tools-pack/npm-winccoa-ctrl-code-style 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +122 -0
  3. package/dist/cjs/cli.js +204 -0
  4. package/dist/cjs/cli.js.map +1 -0
  5. package/dist/cjs/index.js +29 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/cjs/paths.js +50 -0
  8. package/dist/cjs/paths.js.map +1 -0
  9. package/dist/cjs/register.js +154 -0
  10. package/dist/cjs/register.js.map +1 -0
  11. package/dist/cjs/style-check.js +110 -0
  12. package/dist/cjs/style-check.js.map +1 -0
  13. package/dist/cjs/types.js +3 -0
  14. package/dist/cjs/types.js.map +1 -0
  15. package/dist/cli.d.ts +15 -0
  16. package/dist/cli.d.ts.map +1 -0
  17. package/dist/esm/cli.js +204 -0
  18. package/dist/esm/cli.js.map +1 -0
  19. package/dist/esm/index.js +29 -0
  20. package/dist/esm/index.js.map +1 -0
  21. package/dist/esm/paths.js +50 -0
  22. package/dist/esm/paths.js.map +1 -0
  23. package/dist/esm/register.js +154 -0
  24. package/dist/esm/register.js.map +1 -0
  25. package/dist/esm/style-check.js +110 -0
  26. package/dist/esm/style-check.js.map +1 -0
  27. package/dist/esm/types.js +3 -0
  28. package/dist/esm/types.js.map +1 -0
  29. package/dist/index.d.ts +13 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/paths.d.ts +16 -0
  32. package/dist/paths.d.ts.map +1 -0
  33. package/dist/register.d.ts +49 -0
  34. package/dist/register.d.ts.map +1 -0
  35. package/dist/style-check.d.ts +31 -0
  36. package/dist/style-check.d.ts.map +1 -0
  37. package/dist/types.d.ts +71 -0
  38. package/dist/types.d.ts.map +1 -0
  39. package/package.json +68 -0
  40. package/scripts/approve-open-pr-workflows.sh +85 -0
  41. package/scripts/check-changelog-version.mjs +37 -0
  42. package/scripts/fix-esm-imports.mjs +115 -0
  43. package/scripts/generate-changelog.mjs +190 -0
  44. package/scripts/register-stylecheck-projects.ps1 +92 -0
  45. package/scripts/register-stylecheck-projects.sh +193 -0
  46. package/scripts/run-node-tests.ts +74 -0
  47. package/scripts/wait-for-winccoa.sh +26 -0
  48. package/winccoa/StyleCheck/scripts/astyle.ctl +142 -0
@@ -0,0 +1,190 @@
1
+ /* eslint-env node */
2
+ /* global console, process */
3
+ import fs from 'node:fs';
4
+ import path from 'node:path';
5
+ import { execFileSync } from 'node:child_process';
6
+
7
+ function runGit(args) {
8
+ return execFileSync('git', args, { encoding: 'utf8' }).trim();
9
+ }
10
+
11
+ function todayIsoDate() {
12
+ const d = new Date();
13
+ const yyyy = String(d.getFullYear()).padStart(4, '0');
14
+ const mm = String(d.getMonth() + 1).padStart(2, '0');
15
+ const dd = String(d.getDate()).padStart(2, '0');
16
+ return `${yyyy}-${mm}-${dd}`;
17
+ }
18
+
19
+ function parseArgs(argv) {
20
+ const args = new Set(argv);
21
+ const getValue = (flag) => {
22
+ const idx = argv.indexOf(flag);
23
+ if (idx === -1) return undefined;
24
+ return argv[idx + 1];
25
+ };
26
+
27
+ return {
28
+ write: args.has('--write'),
29
+ fromTag: getValue('--from-tag'),
30
+ date: getValue('--date'),
31
+ };
32
+ }
33
+
34
+ function getStableTags() {
35
+ // Only stable SemVer tags like v2.3.1 (no suffix)
36
+ const out = runGit(['tag', '--list', 'v[0-9]*.[0-9]*.[0-9]*', '--sort=version:refname']);
37
+ return out ? out.split(/\r?\n/).map((t) => t.trim()).filter(Boolean) : [];
38
+ }
39
+
40
+ function getCommitSubjects(range) {
41
+ const args = ['log', '--no-decorate', '--pretty=%s'];
42
+ if (range) args.push(range);
43
+ const out = runGit(args);
44
+ if (!out) return [];
45
+ return out
46
+ .split(/\r?\n/)
47
+ .map((s) => s.trim())
48
+ .filter(Boolean);
49
+ }
50
+
51
+ function isNoiseSubject(subject) {
52
+ if (subject.startsWith('Merge ')) return true;
53
+ if (subject.startsWith('chore(release):')) return true;
54
+ return false;
55
+ }
56
+
57
+ function categorize(subject) {
58
+ // Conventional commits: type(scope)!: subject
59
+ const match = /^(?<type>[a-z]+)(\([^\r\n()]+\))?(!)?:\s+(?<msg>.+)$/.exec(subject);
60
+ const type = match?.groups?.type;
61
+ const msg = match?.groups?.msg ?? subject;
62
+
63
+ switch (type) {
64
+ case 'feat':
65
+ return { section: 'Added', text: msg };
66
+ case 'fix':
67
+ return { section: 'Fixed', text: msg };
68
+ case 'perf':
69
+ case 'refactor':
70
+ return { section: 'Changed', text: msg };
71
+ case 'docs':
72
+ case 'build':
73
+ case 'ci':
74
+ case 'test':
75
+ case 'style':
76
+ case 'chore':
77
+ case 'revert':
78
+ case 'deps':
79
+ case 'deps-dev':
80
+ return { section: 'Changed', text: msg };
81
+ default:
82
+ return { section: 'Changed', text: subject };
83
+ }
84
+ }
85
+
86
+ function renderEntry({ version, date, itemsBySection }) {
87
+ const sectionsOrder = ['Added', 'Fixed', 'Changed'];
88
+ const lines = [];
89
+
90
+ lines.push(`## [${version}] - ${date}`);
91
+ lines.push('');
92
+
93
+ let any = false;
94
+ for (const section of sectionsOrder) {
95
+ const items = itemsBySection.get(section) ?? [];
96
+ if (items.length === 0) continue;
97
+ any = true;
98
+ lines.push(`### ${section}`);
99
+ lines.push('');
100
+ for (const item of items) {
101
+ lines.push(`- ${item}`);
102
+ }
103
+ lines.push('');
104
+ }
105
+
106
+ if (!any) {
107
+ lines.push('### Changed');
108
+ lines.push('');
109
+ lines.push('- Maintenance release');
110
+ lines.push('');
111
+ }
112
+
113
+ return lines.join('\n').trimEnd();
114
+ }
115
+
116
+ function insertIntoChangelog(changelogContent, entryMarkdown) {
117
+ const firstHeadingIdx = changelogContent.indexOf('\n## [');
118
+ if (firstHeadingIdx === -1) {
119
+ return `${changelogContent.trimEnd()}\n\n${entryMarkdown}\n`;
120
+ }
121
+
122
+ const before = changelogContent.slice(0, firstHeadingIdx + 1); // keep leading newline
123
+ const after = changelogContent.slice(firstHeadingIdx + 1);
124
+ return `${before}${entryMarkdown}\n\n${after}`;
125
+ }
126
+
127
+ const { write, fromTag, date: dateArg } = parseArgs(process.argv.slice(2));
128
+
129
+ const repoRoot = path.resolve(process.cwd());
130
+ const packageJsonPath = path.join(repoRoot, 'package.json');
131
+ const changelogPath = path.join(repoRoot, 'CHANGELOG.md');
132
+
133
+ if (!fs.existsSync(packageJsonPath)) {
134
+ console.error(`::error::Missing package.json at ${packageJsonPath}`);
135
+ process.exit(1);
136
+ }
137
+
138
+ if (!fs.existsSync(changelogPath)) {
139
+ console.error(`::error::Missing CHANGELOG.md at ${changelogPath}`);
140
+ process.exit(1);
141
+ }
142
+
143
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
144
+ const version = String(pkg.version || '').trim();
145
+ if (!version) {
146
+ console.error('::error::package.json does not contain a valid "version" field');
147
+ process.exit(1);
148
+ }
149
+
150
+ const changelog = fs.readFileSync(changelogPath, 'utf8');
151
+ const expectedHeadingPrefix = `## [${version}] - `;
152
+ const alreadyExists = changelog.includes(expectedHeadingPrefix);
153
+
154
+ let startTag = fromTag;
155
+ if (!startTag) {
156
+ const stableTags = getStableTags().filter((t) => t !== `v${version}`);
157
+ startTag = stableTags.length > 0 ? stableTags[stableTags.length - 1] : undefined;
158
+ }
159
+
160
+ const range = startTag ? `${startTag}..HEAD` : undefined;
161
+ const subjects = getCommitSubjects(range)
162
+ .filter((s) => !isNoiseSubject(s));
163
+
164
+ const itemsBySection = new Map([
165
+ ['Added', []],
166
+ ['Fixed', []],
167
+ ['Changed', []],
168
+ ]);
169
+
170
+ for (const subject of subjects) {
171
+ const { section, text } = categorize(subject);
172
+ itemsBySection.get(section)?.push(text);
173
+ }
174
+
175
+ const entry = renderEntry({ version, date: dateArg ?? todayIsoDate(), itemsBySection });
176
+
177
+ process.stdout.write(entry + '\n');
178
+
179
+ if (!write) {
180
+ process.exit(0);
181
+ }
182
+
183
+ if (alreadyExists) {
184
+ console.error(`CHANGELOG already contains heading for v${version}; skipping write.`);
185
+ process.exit(0);
186
+ }
187
+
188
+ const updated = insertIntoChangelog(changelog, entry);
189
+ fs.writeFileSync(changelogPath, updated, 'utf8');
190
+ console.error(`✅ Inserted changelog entry for v${version} into CHANGELOG.md`);
@@ -0,0 +1,92 @@
1
+ <#
2
+ .SYNOPSIS
3
+ Register StyleCheck (non-runnable) and a worker project (runnable) with StyleCheck as sub-project.
4
+
5
+ .DESCRIPTION
6
+ Uses @winccoa-tools-pack/npm-winccoa-register-project locally or in CI.
7
+ Prefer this on Windows; the .sh script is for Git Bash / Linux / CI containers.
8
+
9
+ .EXAMPLE
10
+ ./scripts/register-stylecheck-projects.ps1 -ProjectPath ./src/Squirt -Version 3.21
11
+ #>
12
+ [CmdletBinding()]
13
+ param(
14
+ [Parameter(Mandatory = $true)]
15
+ [string] $ProjectPath,
16
+
17
+ [Parameter(Mandatory = $true)]
18
+ [Alias('v')]
19
+ [string] $Version,
20
+
21
+ [string] $Langs = 'en_US.utf8',
22
+
23
+ [string] $StyleCheckPath = '',
24
+
25
+ [string] $RegisterCli = '',
26
+
27
+ [switch] $DryRun
28
+ )
29
+
30
+ $ErrorActionPreference = 'Stop'
31
+ $PkgRoot = Split-Path -Parent $PSScriptRoot
32
+ if (-not $StyleCheckPath) {
33
+ $StyleCheckPath = Join-Path $PkgRoot 'winccoa\StyleCheck'
34
+ }
35
+
36
+ $ProjectPath = (Resolve-Path -LiteralPath $ProjectPath).Path
37
+ $StyleCheckPath = (Resolve-Path -LiteralPath $StyleCheckPath).Path
38
+
39
+ function Find-RegisterCli {
40
+ if ($RegisterCli -and (Test-Path -LiteralPath $RegisterCli)) {
41
+ return (Resolve-Path -LiteralPath $RegisterCli).Path
42
+ }
43
+ $candidates = @(
44
+ (Join-Path $PkgRoot 'node_modules\@winccoa-tools-pack\npm-winccoa-register-project\dist\cjs\cli.js'),
45
+ (Join-Path $PkgRoot '..\npm-winccoa-register-project\dist\cjs\cli.js'),
46
+ (Join-Path $PkgRoot '..\npm-winccoa-register-project\dist\src\cli.js')
47
+ )
48
+ foreach ($c in $candidates) {
49
+ if (Test-Path -LiteralPath $c) { return (Resolve-Path -LiteralPath $c).Path }
50
+ }
51
+ $cmd = Get-Command npm-winccoa-register -ErrorAction SilentlyContinue
52
+ if ($cmd) { return $cmd.Source }
53
+ throw 'Could not find npm-winccoa-register-project CLI. Install the package or pass -RegisterCli.'
54
+ }
55
+
56
+ function Invoke-Register {
57
+ param([string[]] $Args)
58
+ $cli = Find-RegisterCli
59
+ Write-Host "register: $cli $($Args -join ' ')"
60
+ if ($DryRun) { return }
61
+ if ($cli -like '*.js') {
62
+ & node $cli @Args
63
+ } else {
64
+ & $cli @Args
65
+ }
66
+ if ($LASTEXITCODE -ne 0) {
67
+ throw "Registration failed with exit code $LASTEXITCODE"
68
+ }
69
+ }
70
+
71
+ Write-Host "StyleCheck (non-runnable): $StyleCheckPath"
72
+ Write-Host "Worker project (runnable): $ProjectPath"
73
+ Write-Host "WinCC OA version: $Version"
74
+ Write-Host "Langs: $Langs"
75
+
76
+ Invoke-Register -Args @(
77
+ '--project-path', $StyleCheckPath,
78
+ '--runnable', 'false',
79
+ '--wincc-oa-version', $Version
80
+ )
81
+
82
+ Invoke-Register -Args @(
83
+ '--project-path', $ProjectPath,
84
+ '--runnable', 'true',
85
+ '--langs', $Langs,
86
+ '--wincc-oa-version', $Version,
87
+ '--sub-project', $StyleCheckPath
88
+ )
89
+
90
+ Write-Host "Done."
91
+ Write-Host "Worker config: $(Join-Path $ProjectPath 'config\config')"
92
+ Write-Host "Next: winccoa-ctrl-style check `"$ProjectPath`" -v $Version --no-register"
@@ -0,0 +1,193 @@
1
+ #!/usr/bin/env bash
2
+ # register-stylecheck-projects.sh
3
+ #
4
+ # Register StyleCheck (non-runnable) and a worker/source project (runnable)
5
+ # with StyleCheck as --sub-project, using npm-winccoa-register-project.
6
+ #
7
+ # Usable locally and in CI.
8
+ #
9
+ # Example:
10
+ # ./scripts/register-stylecheck-projects.sh \
11
+ # --project-path ./src/Squirt \
12
+ # --version 3.21 \
13
+ # --langs en_US.utf8
14
+ #
15
+ set -euo pipefail
16
+
17
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
18
+ PKG_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
19
+
20
+ PROJECT_PATH=""
21
+ VERSION=""
22
+ LANGS="en_US.utf8"
23
+ STYLE_CHECK_PATH="${PKG_ROOT}/winccoa/StyleCheck"
24
+ REGISTER_CLI=""
25
+ DRY_RUN=0
26
+
27
+ usage() {
28
+ cat <<EOF
29
+ Usage: $(basename "$0") --project-path <workerProject> [options]
30
+
31
+ Register:
32
+ 1) StyleCheck as non-runnable WinCC OA project
33
+ 2) Worker project as runnable with --sub-project StyleCheck
34
+
35
+ Options:
36
+ --project-path <path> Runnable worker/source project (required)
37
+ -v, --version <ver> WinCC OA version (e.g. 3.21)
38
+ --langs <csv> Languages (default: en_US.utf8)
39
+ --style-check-path <path> StyleCheck path (default: package winccoa/StyleCheck)
40
+ --register-cli <path> Path to npm-winccoa-register CLI js entry
41
+ --dry-run Print commands only
42
+ -h, --help Show help
43
+
44
+ Environment:
45
+ WINCCOA_VERSION Fallback for --version
46
+ WINCCOA_REGISTER_CLI Fallback for --register-cli
47
+
48
+ After success, run style check with worker config, e.g.:
49
+ WCCOActrl -config <worker>/config/config -n -log +stderr astyle.ctl <source>
50
+ # or:
51
+ winccoa-ctrl-style check <worker> -v <ver> --no-register
52
+ EOF
53
+ }
54
+
55
+ while [[ $# -gt 0 ]]; do
56
+ case "$1" in
57
+ --project-path)
58
+ PROJECT_PATH="${2:-}"; shift 2 ;;
59
+ -v|--version)
60
+ VERSION="${2:-}"; shift 2 ;;
61
+ --langs)
62
+ LANGS="${2:-}"; shift 2 ;;
63
+ --style-check-path)
64
+ STYLE_CHECK_PATH="${2:-}"; shift 2 ;;
65
+ --register-cli)
66
+ REGISTER_CLI="${2:-}"; shift 2 ;;
67
+ --dry-run)
68
+ DRY_RUN=1; shift ;;
69
+ -h|--help)
70
+ usage; exit 0 ;;
71
+ *)
72
+ echo "Unknown option: $1" >&2
73
+ usage >&2
74
+ exit 1 ;;
75
+ esac
76
+ done
77
+
78
+ VERSION="${VERSION:-${WINCCOA_VERSION:-}}"
79
+ REGISTER_CLI="${REGISTER_CLI:-${WINCCOA_REGISTER_CLI:-}}"
80
+
81
+ if [[ -z "${PROJECT_PATH}" ]]; then
82
+ echo "Error: --project-path is required" >&2
83
+ usage >&2
84
+ exit 1
85
+ fi
86
+
87
+ if [[ -z "${VERSION}" ]]; then
88
+ echo "Error: --version (or WINCCOA_VERSION) is required" >&2
89
+ exit 1
90
+ fi
91
+
92
+ # Resolve absolute paths when possible
93
+ if command -v realpath >/dev/null 2>&1; then
94
+ PROJECT_PATH="$(realpath "${PROJECT_PATH}")"
95
+ STYLE_CHECK_PATH="$(realpath "${STYLE_CHECK_PATH}")"
96
+ else
97
+ PROJECT_PATH="$(cd "${PROJECT_PATH}" && pwd)"
98
+ STYLE_CHECK_PATH="$(cd "${STYLE_CHECK_PATH}" && pwd)"
99
+ fi
100
+
101
+ if [[ ! -d "${PROJECT_PATH}" ]]; then
102
+ echo "Error: project path does not exist: ${PROJECT_PATH}" >&2
103
+ exit 1
104
+ fi
105
+ if [[ ! -d "${STYLE_CHECK_PATH}" ]]; then
106
+ echo "Error: StyleCheck path does not exist: ${STYLE_CHECK_PATH}" >&2
107
+ exit 1
108
+ fi
109
+
110
+ find_register_cli() {
111
+ if [[ -n "${REGISTER_CLI}" && -f "${REGISTER_CLI}" ]]; then
112
+ echo "${REGISTER_CLI}"
113
+ return 0
114
+ fi
115
+
116
+ local candidates=(
117
+ "${PKG_ROOT}/node_modules/@winccoa-tools-pack/npm-winccoa-register-project/dist/cjs/cli.js"
118
+ "${PKG_ROOT}/../npm-winccoa-register-project/dist/cjs/cli.js"
119
+ "${PKG_ROOT}/../npm-winccoa-register-project/dist/src/cli.js"
120
+ )
121
+ local c
122
+ for c in "${candidates[@]}"; do
123
+ if [[ -f "${c}" ]]; then
124
+ echo "${c}"
125
+ return 0
126
+ fi
127
+ done
128
+
129
+ if command -v npm-winccoa-register >/dev/null 2>&1; then
130
+ echo "npm-winccoa-register"
131
+ return 0
132
+ fi
133
+
134
+ return 1
135
+ }
136
+
137
+ run_cmd() {
138
+ if [[ "${DRY_RUN}" -eq 1 ]]; then
139
+ printf '+'
140
+ printf ' %q' "$@"
141
+ printf '\n'
142
+ return 0
143
+ fi
144
+ "$@"
145
+ }
146
+
147
+ REGISTER_BIN="$(find_register_cli)" || {
148
+ echo "Error: could not find npm-winccoa-register-project CLI." >&2
149
+ echo "Install @winccoa-tools-pack/npm-winccoa-register-project or pass --register-cli." >&2
150
+ exit 1
151
+ }
152
+
153
+ echo "StyleCheck (non-runnable): ${STYLE_CHECK_PATH}"
154
+ echo "Worker project (runnable): ${PROJECT_PATH}"
155
+ echo "WinCC OA version: ${VERSION}"
156
+ echo "Langs: ${LANGS}"
157
+ echo "Register CLI: ${REGISTER_BIN}"
158
+
159
+ # 1) StyleCheck non-runnable
160
+ if [[ "${REGISTER_BIN}" == *.js ]]; then
161
+ run_cmd node "${REGISTER_BIN}" \
162
+ --project-path "${STYLE_CHECK_PATH}" \
163
+ --runnable false \
164
+ --wincc-oa-version "${VERSION}"
165
+ else
166
+ run_cmd "${REGISTER_BIN}" \
167
+ --project-path "${STYLE_CHECK_PATH}" \
168
+ --runnable false \
169
+ --wincc-oa-version "${VERSION}"
170
+ fi
171
+
172
+ # 2) Worker runnable + StyleCheck sub-project
173
+ # Note: register-project only writes config when missing. For a forced rewrite
174
+ # use: winccoa-ctrl-style register <projectPath> -v <ver>
175
+ if [[ "${REGISTER_BIN}" == *.js ]]; then
176
+ run_cmd node "${REGISTER_BIN}" \
177
+ --project-path "${PROJECT_PATH}" \
178
+ --runnable true \
179
+ --langs "${LANGS}" \
180
+ --wincc-oa-version "${VERSION}" \
181
+ --sub-project "${STYLE_CHECK_PATH}"
182
+ else
183
+ run_cmd "${REGISTER_BIN}" \
184
+ --project-path "${PROJECT_PATH}" \
185
+ --runnable true \
186
+ --langs "${LANGS}" \
187
+ --wincc-oa-version "${VERSION}" \
188
+ --sub-project "${STYLE_CHECK_PATH}"
189
+ fi
190
+
191
+ echo "Done."
192
+ echo "Worker config: ${PROJECT_PATH}/config/config"
193
+ echo "Next: winccoa-ctrl-style check \"${PROJECT_PATH}\" -v ${VERSION} --no-register"
@@ -0,0 +1,74 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ function collectTestFiles(rootDir: string): string[] {
6
+ const out: string[] = [];
7
+ const stack: string[] = [rootDir];
8
+
9
+ while (stack.length > 0) {
10
+ const dir = stack.pop()!;
11
+ let entries: fs.Dirent[];
12
+ try {
13
+ entries = fs.readdirSync(dir, { withFileTypes: true });
14
+ } catch {
15
+ continue;
16
+ }
17
+
18
+ for (const entry of entries) {
19
+ const fullPath = path.join(dir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ stack.push(fullPath);
22
+ continue;
23
+ }
24
+
25
+ if (entry.isFile() && entry.name.endsWith('.test.ts')) {
26
+ out.push(fullPath);
27
+ }
28
+ }
29
+ }
30
+
31
+ return out.sort();
32
+ }
33
+
34
+ // Collect CLI args but ignore option-like args (starting with '-')
35
+ const rawArgs = process.argv.slice(2);
36
+ const targets = rawArgs.filter((a) => !a.startsWith('-'));
37
+ if (targets.length === 0) {
38
+ console.error('Usage: node --import tsx scripts/run-node-tests.ts <file|dir> [file|dir...]');
39
+ process.exit(2);
40
+ }
41
+
42
+ const cwd = process.cwd();
43
+ const files = targets.flatMap((t) => {
44
+ const resolved = path.resolve(cwd, t);
45
+ try {
46
+ const stat = fs.statSync(resolved);
47
+ if (stat.isFile() && resolved.endsWith('.test.ts')) {
48
+ return [resolved];
49
+ }
50
+ if (stat.isDirectory()) {
51
+ return collectTestFiles(resolved);
52
+ }
53
+ } catch {
54
+ // ignore missing targets
55
+ }
56
+ return [] as string[];
57
+ });
58
+
59
+ if (files.length === 0) {
60
+ console.error(`No '*.test.ts' files found under: ${targets.join(', ')}`);
61
+ process.exit(1);
62
+ }
63
+
64
+ // Run tests one by one to better isolate failures
65
+ // Integration tests may leave resources hanging that interfere with subsequent tests
66
+ let failed = false;
67
+ for (const file of files) {
68
+ const args = ['--import', 'tsx', '--test', '--test-force-exit', file];
69
+ const result = spawnSync(process.execPath, args, { stdio: 'inherit' });
70
+ if (result.status !== 0) {
71
+ failed = true;
72
+ }
73
+ }
74
+ process.exit(failed ? 1 : 0);
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env sh
2
+ # Wait for WinCC OA in container to be ready.
3
+ # Usage: wait-for-winccoa.sh <container-name> <max-retries>
4
+
5
+ container="$1"
6
+ max_retries="${2:-60}"
7
+ delay=2
8
+
9
+ if [ -z "$container" ]; then
10
+ echo "Usage: $0 <container-name> [max-retries]"
11
+ exit 2
12
+ fi
13
+
14
+ echo "Waiting for WinCC OA in container '$container' (max $max_retries attempts)..."
15
+ i=0
16
+ while [ $i -lt $max_retries ]; do
17
+ i=$((i+1))
18
+ docker exec "$container" sh -c "if [ -x /opt/WinCC_OA/bin/winccoa ]; then echo ready; exit 0; else exit 1; fi" >/dev/null 2>&1 && {
19
+ echo "Ready after $i attempts"
20
+ exit 0
21
+ }
22
+ sleep $delay
23
+ done
24
+
25
+ echo "Timed out waiting for WinCC OA in container '$container'"
26
+ exit 1