@vsirotin/ts-stop 1.11.1 → 1.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/esm/sfsm/FaMerger.js +24 -0
- package/lib/esm/sfsm/index.js +1 -0
- package/lib/sfsm/FaMerger.d.ts +16 -0
- package/lib/sfsm/FaMerger.d.ts.map +1 -0
- package/lib/sfsm/FaMerger.js +28 -0
- package/lib/sfsm/FaMerger.js.map +1 -0
- package/lib/sfsm/index.d.ts +1 -0
- package/lib/sfsm/index.d.ts.map +1 -1
- package/lib/sfsm/index.js +3 -1
- package/lib/sfsm/index.js.map +1 -1
- package/package.json +11 -1
- package/scripts/merge-fas-from-dir.js +129 -0
- package/scripts/merge-fas.js +99 -0
- package/scripts/publish-local.sh +15 -0
- package/scripts/reduce-fa.js +56 -0
- package/scripts/update-fa.js +109 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { reduceFA } from './FaReducer';
|
|
2
|
+
/**
|
|
3
|
+
* Merges multiple FA definitions into one compact multi-FA object.
|
|
4
|
+
*
|
|
5
|
+
* Each input is reduced first (same semantics as reduceFA). If an input is
|
|
6
|
+
* already compact, reduceFA returns it unchanged.
|
|
7
|
+
*
|
|
8
|
+
* On duplicate FA keys, the later input overwrites the previous one and a
|
|
9
|
+
* warning entry is emitted.
|
|
10
|
+
*/
|
|
11
|
+
export function mergeFAs(definitions) {
|
|
12
|
+
const merged = {};
|
|
13
|
+
const warnings = [];
|
|
14
|
+
for (let fileIndex = 0; fileIndex < definitions.length; fileIndex++) {
|
|
15
|
+
const reduced = reduceFA(definitions[fileIndex]);
|
|
16
|
+
for (const [faName, transitions] of Object.entries(reduced)) {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(merged, faName)) {
|
|
18
|
+
warnings.push(`Duplicate FA key '${faName}' encountered at input #${fileIndex + 1}. Last definition wins.`);
|
|
19
|
+
}
|
|
20
|
+
merged[faName] = transitions;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return { merged, warnings };
|
|
24
|
+
}
|
package/lib/esm/sfsm/index.js
CHANGED
|
@@ -2,5 +2,6 @@ export { ExternalWorldHub } from './ExternalWorldHub';
|
|
|
2
2
|
export { FaResolver } from './FaResolver';
|
|
3
3
|
export { Sfsm } from './Sfsm';
|
|
4
4
|
export { reduceFA } from './FaReducer';
|
|
5
|
+
export { mergeFAs } from './FaMerger';
|
|
5
6
|
export { updateCompactFA, updateFullFA } from './FaUpdater';
|
|
6
7
|
export { loadFAFromFile, loadFAFromURL } from './FaLoader';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { FaDefinition } from './types';
|
|
2
|
+
export interface MergeFaResult {
|
|
3
|
+
merged: FaDefinition;
|
|
4
|
+
warnings: string[];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Merges multiple FA definitions into one compact multi-FA object.
|
|
8
|
+
*
|
|
9
|
+
* Each input is reduced first (same semantics as reduceFA). If an input is
|
|
10
|
+
* already compact, reduceFA returns it unchanged.
|
|
11
|
+
*
|
|
12
|
+
* On duplicate FA keys, the later input overwrites the previous one and a
|
|
13
|
+
* warning entry is emitted.
|
|
14
|
+
*/
|
|
15
|
+
export declare function mergeFAs(definitions: FaDefinition[]): MergeFaResult;
|
|
16
|
+
//# sourceMappingURL=FaMerger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaMerger.d.ts","sourceRoot":"","sources":["../../src/sfsm/FaMerger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC,MAAM,WAAW,aAAa;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAC;CACtB;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,WAAW,EAAE,YAAY,EAAE,GAAG,aAAa,CAgBnE"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeFAs = mergeFAs;
|
|
4
|
+
const FaReducer_1 = require("./FaReducer");
|
|
5
|
+
/**
|
|
6
|
+
* Merges multiple FA definitions into one compact multi-FA object.
|
|
7
|
+
*
|
|
8
|
+
* Each input is reduced first (same semantics as reduceFA). If an input is
|
|
9
|
+
* already compact, reduceFA returns it unchanged.
|
|
10
|
+
*
|
|
11
|
+
* On duplicate FA keys, the later input overwrites the previous one and a
|
|
12
|
+
* warning entry is emitted.
|
|
13
|
+
*/
|
|
14
|
+
function mergeFAs(definitions) {
|
|
15
|
+
const merged = {};
|
|
16
|
+
const warnings = [];
|
|
17
|
+
for (let fileIndex = 0; fileIndex < definitions.length; fileIndex++) {
|
|
18
|
+
const reduced = (0, FaReducer_1.reduceFA)(definitions[fileIndex]);
|
|
19
|
+
for (const [faName, transitions] of Object.entries(reduced)) {
|
|
20
|
+
if (Object.prototype.hasOwnProperty.call(merged, faName)) {
|
|
21
|
+
warnings.push(`Duplicate FA key '${faName}' encountered at input #${fileIndex + 1}. Last definition wins.`);
|
|
22
|
+
}
|
|
23
|
+
merged[faName] = transitions;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return { merged, warnings };
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=FaMerger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"FaMerger.js","sourceRoot":"","sources":["../../src/sfsm/FaMerger.ts"],"names":[],"mappings":";;AAiBA,4BAgBC;AAhCD,2CAAuC;AAOvC;;;;;;;;GAQG;AACH,SAAgB,QAAQ,CAAC,WAA2B;IAChD,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,MAAM,QAAQ,GAAa,EAAE,CAAC;IAE9B,KAAK,IAAI,SAAS,GAAG,CAAC,EAAE,SAAS,GAAG,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,CAAC;QAClE,MAAM,OAAO,GAAG,IAAA,oBAAQ,EAAC,WAAW,CAAC,SAAS,CAAC,CAAC,CAAC;QAEjD,KAAK,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAC1D,IAAI,MAAM,CAAC,SAAS,CAAC,cAAc,CAAC,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;gBACvD,QAAQ,CAAC,IAAI,CAAC,qBAAqB,MAAM,2BAA2B,SAAS,GAAG,CAAC,yBAAyB,CAAC,CAAC;YAChH,CAAC;YACD,MAAM,CAAC,MAAM,CAAC,GAAG,WAAW,CAAC;QACjC,CAAC;IACL,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;AAChC,CAAC"}
|
package/lib/sfsm/index.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { FaDefinition, FaNode, FaUpdate, Transition, SfsmOptions, LogEntry, Miss
|
|
|
4
4
|
export { FaResolver, ResolvedFa } from './FaResolver';
|
|
5
5
|
export { Sfsm } from './Sfsm';
|
|
6
6
|
export { reduceFA } from './FaReducer';
|
|
7
|
+
export { mergeFAs, MergeFaResult } from './FaMerger';
|
|
7
8
|
export { updateCompactFA, updateFullFA } from './FaUpdater';
|
|
8
9
|
export { loadFAFromFile, loadFAFromURL } from './FaLoader';
|
|
9
10
|
//# sourceMappingURL=index.d.ts.map
|
package/lib/sfsm/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sfsm/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AACxI,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/sfsm/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAChF,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,UAAU,EAAE,WAAW,EAAE,QAAQ,EAAE,iBAAiB,EAAE,uBAAuB,EAAE,MAAM,SAAS,CAAC;AACxI,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAC9B,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
package/lib/sfsm/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.loadFAFromURL = exports.loadFAFromFile = exports.updateFullFA = exports.updateCompactFA = exports.reduceFA = exports.Sfsm = exports.FaResolver = exports.ExternalWorldHub = void 0;
|
|
3
|
+
exports.loadFAFromURL = exports.loadFAFromFile = exports.updateFullFA = exports.updateCompactFA = exports.mergeFAs = exports.reduceFA = exports.Sfsm = exports.FaResolver = exports.ExternalWorldHub = void 0;
|
|
4
4
|
var ExternalWorldHub_1 = require("./ExternalWorldHub");
|
|
5
5
|
Object.defineProperty(exports, "ExternalWorldHub", { enumerable: true, get: function () { return ExternalWorldHub_1.ExternalWorldHub; } });
|
|
6
6
|
var FaResolver_1 = require("./FaResolver");
|
|
@@ -9,6 +9,8 @@ var Sfsm_1 = require("./Sfsm");
|
|
|
9
9
|
Object.defineProperty(exports, "Sfsm", { enumerable: true, get: function () { return Sfsm_1.Sfsm; } });
|
|
10
10
|
var FaReducer_1 = require("./FaReducer");
|
|
11
11
|
Object.defineProperty(exports, "reduceFA", { enumerable: true, get: function () { return FaReducer_1.reduceFA; } });
|
|
12
|
+
var FaMerger_1 = require("./FaMerger");
|
|
13
|
+
Object.defineProperty(exports, "mergeFAs", { enumerable: true, get: function () { return FaMerger_1.mergeFAs; } });
|
|
12
14
|
var FaUpdater_1 = require("./FaUpdater");
|
|
13
15
|
Object.defineProperty(exports, "updateCompactFA", { enumerable: true, get: function () { return FaUpdater_1.updateCompactFA; } });
|
|
14
16
|
Object.defineProperty(exports, "updateFullFA", { enumerable: true, get: function () { return FaUpdater_1.updateFullFA; } });
|
package/lib/sfsm/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sfsm/index.ts"],"names":[],"mappings":";;;AACA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AAEzB,2CAAsD;AAA7C,wGAAA,UAAU,OAAA;AACnB,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA;AACjB,yCAA4D;AAAnD,4GAAA,eAAe,OAAA;AAAE,yGAAA,YAAY,OAAA;AACtC,uCAA2D;AAAlD,0GAAA,cAAc,OAAA;AAAE,yGAAA,aAAa,OAAA"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/sfsm/index.ts"],"names":[],"mappings":";;;AACA,uDAAsD;AAA7C,oHAAA,gBAAgB,OAAA;AAEzB,2CAAsD;AAA7C,wGAAA,UAAU,OAAA;AACnB,+BAA8B;AAArB,4FAAA,IAAI,OAAA;AACb,yCAAuC;AAA9B,qGAAA,QAAQ,OAAA;AACjB,uCAAqD;AAA5C,oGAAA,QAAQ,OAAA;AACjB,yCAA4D;AAAnD,4GAAA,eAAe,OAAA;AAAE,yGAAA,YAAY,OAAA;AACtC,uCAA2D;AAAlD,0GAAA,cAAc,OAAA;AAAE,yGAAA,aAAa,OAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vsirotin/ts-stop",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.13.0",
|
|
4
4
|
"description": "State Oriented Programming library for TypeScript",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"types": "lib/index.d.ts",
|
|
@@ -20,10 +20,18 @@
|
|
|
20
20
|
},
|
|
21
21
|
"files": [
|
|
22
22
|
"lib",
|
|
23
|
+
"scripts",
|
|
23
24
|
"README.md",
|
|
24
25
|
"LICENSE",
|
|
25
26
|
"LICENSE-COMMERCIAL.md"
|
|
26
27
|
],
|
|
28
|
+
"bin": {
|
|
29
|
+
"reduce-fa": "./scripts/reduce-fa.js",
|
|
30
|
+
"merge-fas": "./scripts/merge-fas.js",
|
|
31
|
+
"merge-fas-from-dir": "./scripts/merge-fas-from-dir.js",
|
|
32
|
+
"update-full-fa": "./scripts/update-fa.js",
|
|
33
|
+
"update-compact-fa": "./scripts/update-fa.js"
|
|
34
|
+
},
|
|
27
35
|
"scripts": {
|
|
28
36
|
"build": "npm run build:cjs && npm run build:esm",
|
|
29
37
|
"build:cjs": "tsc",
|
|
@@ -34,6 +42,8 @@
|
|
|
34
42
|
"publish:local": "./scripts/publish-local.sh",
|
|
35
43
|
"pack": "npm run build && npm pack",
|
|
36
44
|
"reduce-fa": "node scripts/reduce-fa.js",
|
|
45
|
+
"merge-fas": "node scripts/merge-fas.js",
|
|
46
|
+
"merge-fas-from-dir": "node scripts/merge-fas-from-dir.js",
|
|
37
47
|
"update-full-fa": "node scripts/update-fa.js --format=full",
|
|
38
48
|
"update-compact-fa": "node scripts/update-fa.js --format=compact"
|
|
39
49
|
},
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* merge-fas-from-dir.js — CLI tool to recursively merge all FA JSON files from a directory.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/merge-fas-from-dir.js --input-dir=<path/to/dir> --result=<path/to/result.json>
|
|
9
|
+
*
|
|
10
|
+
* Behavior:
|
|
11
|
+
* - Recursively finds all .json files in the input directory (including subdirectories).
|
|
12
|
+
* - Sorts files alphabetically by full path for consistent merge order.
|
|
13
|
+
* - Each input file is reduced with reduceFA semantics.
|
|
14
|
+
* - Already compact files are used unchanged.
|
|
15
|
+
* - Duplicate FA keys are overwritten by the latest file and reported as warnings.
|
|
16
|
+
* - Output is a single merged compact FA definition.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const { mergeFAs } = require('../lib/sfsm');
|
|
22
|
+
|
|
23
|
+
const args = process.argv.slice(2);
|
|
24
|
+
const options = {};
|
|
25
|
+
|
|
26
|
+
for (const arg of args) {
|
|
27
|
+
const match = arg.match(/^--([^=]+)=(.+)$/);
|
|
28
|
+
if (match) {
|
|
29
|
+
options[match[1]] = match[2];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!options['input-dir']) {
|
|
34
|
+
console.error('Error: --input-dir=<path> is required.');
|
|
35
|
+
console.error('Usage: node scripts/merge-fas-from-dir.js --input-dir=<path/to/dir> --result=<path/to/result.json>');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!options.result) {
|
|
40
|
+
console.error('Error: --result=<path> is required.');
|
|
41
|
+
console.error('Usage: node scripts/merge-fas-from-dir.js --input-dir=<path/to/dir> --result=<path/to/result.json>');
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const inputDir = path.resolve(process.cwd(), options['input-dir']);
|
|
46
|
+
const resultFile = path.resolve(process.cwd(), options.result);
|
|
47
|
+
|
|
48
|
+
// Verify input directory exists
|
|
49
|
+
if (!fs.existsSync(inputDir) || !fs.statSync(inputDir).isDirectory()) {
|
|
50
|
+
console.error(`Error: Input directory not found or not a directory: ${inputDir}`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Recursively find all .json files in a directory.
|
|
56
|
+
*/
|
|
57
|
+
function findJsonFiles(dir, baseDir = dir) {
|
|
58
|
+
const files = [];
|
|
59
|
+
const entries = fs.readdirSync(dir);
|
|
60
|
+
|
|
61
|
+
for (const entry of entries) {
|
|
62
|
+
const fullPath = path.join(dir, entry);
|
|
63
|
+
const stat = fs.statSync(fullPath);
|
|
64
|
+
|
|
65
|
+
if (stat.isDirectory()) {
|
|
66
|
+
files.push(...findJsonFiles(fullPath, baseDir));
|
|
67
|
+
} else if (entry.endsWith('.json')) {
|
|
68
|
+
files.push(fullPath);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return files;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Check if a definition is in compact format.
|
|
77
|
+
*/
|
|
78
|
+
function isCompactDefinition(definition) {
|
|
79
|
+
const entries = Object.entries(definition);
|
|
80
|
+
return entries.length > 0 && entries.every(([, value]) => Array.isArray(value));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Find all JSON files
|
|
84
|
+
const jsonFiles = findJsonFiles(inputDir).sort();
|
|
85
|
+
|
|
86
|
+
if (jsonFiles.length === 0) {
|
|
87
|
+
console.error(`Error: No JSON files found in input directory: ${inputDir}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
console.log(`Found ${jsonFiles.length} JSON file(s) in ${inputDir}`);
|
|
92
|
+
|
|
93
|
+
const definitions = [];
|
|
94
|
+
|
|
95
|
+
for (let i = 0; i < jsonFiles.length; i++) {
|
|
96
|
+
const file = jsonFiles[i];
|
|
97
|
+
const relPath = path.relative(inputDir, file);
|
|
98
|
+
let definition;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
definition = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.error(`Error: Failed to parse JSON from ${file}: ${err.message}`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (isCompactDefinition(definition)) {
|
|
108
|
+
console.log(`Info: Input #${i + 1} is already compact and will be used as-is: ${relPath}`);
|
|
109
|
+
} else {
|
|
110
|
+
console.log(`Info: Input #${i + 1} is extended and will be reduced: ${relPath}`);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
definitions.push(definition);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const { merged, warnings } = mergeFAs(definitions);
|
|
117
|
+
|
|
118
|
+
for (const warning of warnings) {
|
|
119
|
+
console.warn(`Warning: ${warning}`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const json = JSON.stringify(merged, null, 4).replace(
|
|
123
|
+
/\[\s*\n\s*"([^"]+)",\s*\n\s*"([^"]+)",\s*\n\s*"([^"]+)"(?:,\s*\n\s*"([^"]+)")?\s*\n\s*\]/g,
|
|
124
|
+
(_, a, b, c, d) => d ? `["${a}", "${b}", "${c}", "${d}"]` : `["${a}", "${b}", "${c}"]`
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
fs.mkdirSync(path.dirname(resultFile), { recursive: true });
|
|
128
|
+
fs.writeFileSync(resultFile, `${json}\n`, 'utf-8');
|
|
129
|
+
console.log(`Merged compact FA written to: ${resultFile}`);
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* merge-fas.js — CLI tool to reduce and merge multiple FA JSON files.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npm run merge-fas -- --result=<path/to/result.json> <file1[,file2,...]> [file3 ...]
|
|
9
|
+
*
|
|
10
|
+
* Behavior:
|
|
11
|
+
* - Each input file is reduced with reduceFA semantics.
|
|
12
|
+
* - Already compact files are used unchanged.
|
|
13
|
+
* - Duplicate FA keys are overwritten by the latest file and reported as warnings.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { mergeFAs } = require('../lib/sfsm');
|
|
19
|
+
|
|
20
|
+
const args = process.argv.slice(2);
|
|
21
|
+
const options = {};
|
|
22
|
+
const rawFiles = [];
|
|
23
|
+
|
|
24
|
+
for (const arg of args) {
|
|
25
|
+
const match = arg.match(/^--([^=]+)=(.+)$/);
|
|
26
|
+
if (match) {
|
|
27
|
+
options[match[1]] = match[2];
|
|
28
|
+
} else {
|
|
29
|
+
rawFiles.push(arg);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (!options.result) {
|
|
34
|
+
console.error('Error: --result=<path> is required.');
|
|
35
|
+
console.error('Usage: npm run merge-fas -- --result=<path/to/result.json> <file1[,file2,...]> [file3 ...]');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const inputFiles = rawFiles
|
|
40
|
+
.flatMap((chunk) => chunk.split(','))
|
|
41
|
+
.map((s) => s.trim())
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
|
|
44
|
+
if (inputFiles.length === 0) {
|
|
45
|
+
console.error('Error: At least one input file is required.');
|
|
46
|
+
console.error('Usage: npm run merge-fas -- --result=<path/to/result.json> <file1[,file2,...]> [file3 ...]');
|
|
47
|
+
process.exit(1);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const resolvedInputFiles = inputFiles.map((f) => path.resolve(process.cwd(), f));
|
|
51
|
+
const resultFile = path.resolve(process.cwd(), options.result);
|
|
52
|
+
|
|
53
|
+
for (const file of resolvedInputFiles) {
|
|
54
|
+
if (!fs.existsSync(file)) {
|
|
55
|
+
console.error(`Error: Input file not found: ${file}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isCompactDefinition(definition) {
|
|
61
|
+
const entries = Object.entries(definition);
|
|
62
|
+
return entries.length > 0 && entries.every(([, value]) => Array.isArray(value));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const definitions = [];
|
|
66
|
+
|
|
67
|
+
for (let i = 0; i < resolvedInputFiles.length; i++) {
|
|
68
|
+
const file = resolvedInputFiles[i];
|
|
69
|
+
let definition;
|
|
70
|
+
try {
|
|
71
|
+
definition = JSON.parse(fs.readFileSync(file, 'utf-8'));
|
|
72
|
+
} catch (err) {
|
|
73
|
+
console.error(`Error: Failed to parse JSON from ${file}: ${err.message}`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (isCompactDefinition(definition)) {
|
|
78
|
+
console.log(`Info: Input #${i + 1} is already compact and will be used as-is: ${file}`);
|
|
79
|
+
} else {
|
|
80
|
+
console.log(`Info: Input #${i + 1} is extended and will be reduced: ${file}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
definitions.push(definition);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { merged, warnings } = mergeFAs(definitions);
|
|
87
|
+
|
|
88
|
+
for (const warning of warnings) {
|
|
89
|
+
console.warn(`Warning: ${warning}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const json = JSON.stringify(merged, null, 4).replace(
|
|
93
|
+
/\[\s*\n\s*"([^"]+)",\s*\n\s*"([^"]+)",\s*\n\s*"([^"]+)"(?:,\s*\n\s*"([^"]+)")?\s*\n\s*\]/g,
|
|
94
|
+
(_, a, b, c, d) => d ? `["${a}", "${b}", "${c}", "${d}"]` : `["${a}", "${b}", "${c}"]`
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
fs.mkdirSync(path.dirname(resultFile), { recursive: true });
|
|
98
|
+
fs.writeFileSync(resultFile, `${json}\n`, 'utf-8');
|
|
99
|
+
console.log(`Merged compact FA written to: ${resultFile}`);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
set -e
|
|
3
|
+
|
|
4
|
+
echo "📦 Publishing ts-stop locally..."
|
|
5
|
+
|
|
6
|
+
# Build
|
|
7
|
+
npm run build
|
|
8
|
+
|
|
9
|
+
# Copy to ts-stop node_modules so the test suite can import @vsirotin/ts-stop
|
|
10
|
+
echo "📋 Installing to ts-stop node_modules..."
|
|
11
|
+
rm -rf node_modules/@vsirotin/ts-stop
|
|
12
|
+
mkdir -p node_modules/@vsirotin/ts-stop
|
|
13
|
+
cp -r lib package.json README.md LICENSE node_modules/@vsirotin/ts-stop/
|
|
14
|
+
|
|
15
|
+
echo "✅ Local publishing completed!"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* reduce-fa.js — CLI tool to convert an extended FA JSON file to compact format.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npm run reduce-fa -- <path/to/extended-fa.json>
|
|
9
|
+
*
|
|
10
|
+
* Output:
|
|
11
|
+
* Writes the compact definition to <input>-compact.json in the same directory.
|
|
12
|
+
*
|
|
13
|
+
* Requires the library to be built first: npm run build
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { reduceFA } = require('../lib/sfsm');
|
|
19
|
+
|
|
20
|
+
const inputArg = process.argv[2];
|
|
21
|
+
|
|
22
|
+
if (!inputArg) {
|
|
23
|
+
console.error('Error: No input file specified.');
|
|
24
|
+
console.error('Usage: npm run reduce-fa -- <path/to/extended-fa.json>');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const inputFile = path.resolve(process.cwd(), inputArg);
|
|
29
|
+
|
|
30
|
+
if (!fs.existsSync(inputFile)) {
|
|
31
|
+
console.error(`Error: File not found: ${inputFile}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let definition;
|
|
36
|
+
try {
|
|
37
|
+
definition = JSON.parse(fs.readFileSync(inputFile, 'utf-8'));
|
|
38
|
+
} catch (err) {
|
|
39
|
+
console.error(`Error: Failed to parse JSON from ${inputFile}: ${err.message}`);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const compact = reduceFA(definition);
|
|
44
|
+
|
|
45
|
+
const ext = path.extname(inputFile);
|
|
46
|
+
const base = path.basename(inputFile, ext);
|
|
47
|
+
const dir = path.dirname(inputFile);
|
|
48
|
+
const outputFile = path.join(dir, `${base}-compact${ext}`);
|
|
49
|
+
|
|
50
|
+
// Serialize with transitions inline: ["state", "signal", "newState", "cmd?"]
|
|
51
|
+
const json = JSON.stringify(compact, null, 4).replace(
|
|
52
|
+
/\[\s*\n\s*"([^"]+)",\s*\n\s*"([^"]+)",\s*\n\s*"([^"]+)"(?:,\s*\n\s*"([^"]+)")?\s*\n\s*\]/g,
|
|
53
|
+
(_, a, b, c, d) => d ? `["${a}", "${b}", "${c}", "${d}"]` : `["${a}", "${b}", "${c}"]`
|
|
54
|
+
);
|
|
55
|
+
fs.writeFileSync(outputFile, json, 'utf-8');
|
|
56
|
+
console.log(`Reduced FA written to: ${outputFile}`);
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* update-fa.js — CLI tool to apply an FA update file to a source FA JSON file.
|
|
6
|
+
*
|
|
7
|
+
* Invoked via npm scripts:
|
|
8
|
+
* npm run update-full-fa -- --source=<path> --update=<path> [--result=<path>]
|
|
9
|
+
* npm run update-compact-fa -- --source=<path> --update=<path> [--result=<path>]
|
|
10
|
+
*
|
|
11
|
+
* If --result is omitted, the output file is named <source-basename>-updated.json.
|
|
12
|
+
*
|
|
13
|
+
* Requires the library to be built first: npm run build
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const { updateFullFA, updateCompactFA } = require('../lib/sfsm');
|
|
19
|
+
|
|
20
|
+
// ---------------------------------------------------------------------------
|
|
21
|
+
// Parse args
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
|
|
24
|
+
const args = {};
|
|
25
|
+
for (const arg of process.argv.slice(2)) {
|
|
26
|
+
const match = arg.match(/^--([^=]+)=(.+)$/);
|
|
27
|
+
if (match) args[match[1]] = match[2];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const format = args['format'];
|
|
31
|
+
if (!format || !['full', 'compact'].includes(format)) {
|
|
32
|
+
console.error('Error: --format=full|compact is required (set by the npm script).');
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!args['source']) {
|
|
37
|
+
console.error('Error: --source=<path> is required.');
|
|
38
|
+
console.error(`Usage: npm run update-${format}-fa -- --source=<path> --update=<path> [--result=<path>]`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!args['update']) {
|
|
43
|
+
console.error('Error: --update=<path> is required.');
|
|
44
|
+
console.error(`Usage: npm run update-${format}-fa -- --source=<path> --update=<path> [--result=<path>]`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
// Resolve paths
|
|
50
|
+
// ---------------------------------------------------------------------------
|
|
51
|
+
|
|
52
|
+
const sourceFile = path.resolve(process.cwd(), args['source']);
|
|
53
|
+
const updateFile = path.resolve(process.cwd(), args['update']);
|
|
54
|
+
|
|
55
|
+
let resultFile;
|
|
56
|
+
if (args['result']) {
|
|
57
|
+
resultFile = path.resolve(process.cwd(), args['result']);
|
|
58
|
+
} else {
|
|
59
|
+
const ext = path.extname(sourceFile);
|
|
60
|
+
const base = path.basename(sourceFile, ext);
|
|
61
|
+
const dir = path.dirname(sourceFile);
|
|
62
|
+
resultFile = path.join(dir, `${base}-updated${ext}`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ---------------------------------------------------------------------------
|
|
66
|
+
// Load & validate
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
|
|
69
|
+
if (!fs.existsSync(sourceFile)) {
|
|
70
|
+
console.error(`Error: Source file not found: ${sourceFile}`);
|
|
71
|
+
process.exit(1);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!fs.existsSync(updateFile)) {
|
|
75
|
+
console.error(`Error: Update file not found: ${updateFile}`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let source, update;
|
|
80
|
+
try {
|
|
81
|
+
source = JSON.parse(fs.readFileSync(sourceFile, 'utf-8'));
|
|
82
|
+
} catch (err) {
|
|
83
|
+
console.error(`Error: Failed to parse source JSON: ${err.message}`);
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
update = JSON.parse(fs.readFileSync(updateFile, 'utf-8'));
|
|
89
|
+
} catch (err) {
|
|
90
|
+
console.error(`Error: Failed to parse update JSON: ${err.message}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
// Apply update
|
|
96
|
+
// ---------------------------------------------------------------------------
|
|
97
|
+
|
|
98
|
+
let result;
|
|
99
|
+
try {
|
|
100
|
+
result = format === 'full'
|
|
101
|
+
? updateFullFA(source, update)
|
|
102
|
+
: updateCompactFA(source, update);
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.error(`Error: Update failed: ${err.message}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
fs.writeFileSync(resultFile, JSON.stringify(result, null, 4), 'utf-8');
|
|
109
|
+
console.log(`Updated FA written to: ${resultFile}`);
|