meadow-integration 1.0.38 → 1.0.40
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/.github/workflows/publish-image.yml +85 -0
- package/BUILDING-AND-PUBLISHING.md +357 -0
- package/Dockerfile +1 -0
- package/example-applications/mapping-demo/web/index.html +6 -6
- package/example-applications/mapping-demo/web/mapping-demo-editor.js +6 -6
- package/example-applications/mapping-demo/web/mapping-demo-editor.min.js +1 -1
- package/package.json +19 -9
- package/schema/default.json +915 -0
- package/source/cli/Default-Meadow-Integration-Configuration.json +1 -1
- package/source/cli/commands/Meadow-Integration-Command-DataClone.js +102 -6
- package/source/views/PictView-MeadowMappingEditor.js +5 -5
- package/source/views/flow-cards/FlowCard-SolverExpression.js +2 -2
- package/source/views/flow-cards/FlowCard-TemplateExpression.js +2 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const libCLICommandLineCommand = require('pict-service-commandlineutility').ServiceCommandLineCommand;
|
|
2
2
|
|
|
3
|
+
const libFs = require('fs');
|
|
3
4
|
const libPath = require('path');
|
|
4
5
|
|
|
5
6
|
const libMeadowConnectionManager = require('../../services/clone/Meadow-Service-ConnectionManager.js');
|
|
@@ -7,6 +8,35 @@ const libMeadowCloneRestClient = require('../../services/clone/Meadow-Service-Re
|
|
|
7
8
|
const libMeadowSync = require('../../services/clone/Meadow-Service-Sync.js');
|
|
8
9
|
const libSessionManagerSetup = require('../../Meadow-Integration-SessionManagerSetup.js');
|
|
9
10
|
|
|
11
|
+
// Resolve an env var with the standard `_FILE` suffix fallback for
|
|
12
|
+
// secrets — so docker / k8s secret mounts work without bespoke wiring.
|
|
13
|
+
// Returns undefined when neither the var nor its `_FILE` companion is
|
|
14
|
+
// set; existing JSON-config + CLI-override layers then take effect
|
|
15
|
+
// unchanged.
|
|
16
|
+
function _envOrFile(pVarName)
|
|
17
|
+
{
|
|
18
|
+
let tmpValue = process.env[pVarName];
|
|
19
|
+
if (tmpValue !== undefined && tmpValue !== '')
|
|
20
|
+
{
|
|
21
|
+
return tmpValue;
|
|
22
|
+
}
|
|
23
|
+
let tmpFilePath = process.env[pVarName + '_FILE'];
|
|
24
|
+
if (tmpFilePath)
|
|
25
|
+
{
|
|
26
|
+
try
|
|
27
|
+
{
|
|
28
|
+
return libFs.readFileSync(tmpFilePath, 'utf8').replace(/\s+$/, '');
|
|
29
|
+
}
|
|
30
|
+
catch (pErr)
|
|
31
|
+
{
|
|
32
|
+
// Soft fail — fall through to undefined so the config-file
|
|
33
|
+
// layer (or CLI flag) still has a chance.
|
|
34
|
+
console.warn(`Meadow-Integration: ${pVarName}_FILE=${tmpFilePath} unreadable: ${pErr.message}`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
10
40
|
class DataClone extends libCLICommandLineCommand
|
|
11
41
|
{
|
|
12
42
|
constructor(pFable, pOptions, pServiceHash)
|
|
@@ -23,13 +53,13 @@ class DataClone extends libCLICommandLineCommand
|
|
|
23
53
|
this.options.CommandOptions.push({ Name: '-p, --api_password [api_password]', Description: 'The API password to authenticate with.' });
|
|
24
54
|
|
|
25
55
|
this.options.CommandOptions.push({ Name: '-d, --db_provider [db_provider]', Description: 'The database provider (MySQL or MSSQL). Default is MySQL.', DefaultValue: 'MySQL' });
|
|
26
|
-
this.options.CommandOptions.push({ Name: '
|
|
27
|
-
this.options.CommandOptions.push({ Name: '
|
|
28
|
-
this.options.CommandOptions.push({ Name: '
|
|
29
|
-
this.options.CommandOptions.push({ Name: '
|
|
30
|
-
this.options.CommandOptions.push({ Name: '
|
|
56
|
+
this.options.CommandOptions.push({ Name: '--db_host [db_host]', Description: 'The database host address.' });
|
|
57
|
+
this.options.CommandOptions.push({ Name: '--db_port [db_port]', Description: 'The database port.' });
|
|
58
|
+
this.options.CommandOptions.push({ Name: '--db_username [db_username]', Description: 'The database username.' });
|
|
59
|
+
this.options.CommandOptions.push({ Name: '--db_password [db_password]', Description: 'The database password.' });
|
|
60
|
+
this.options.CommandOptions.push({ Name: '--db_name [db_name]', Description: 'The database name.' });
|
|
31
61
|
|
|
32
|
-
this.options.CommandOptions.push({ Name: '
|
|
62
|
+
this.options.CommandOptions.push({ Name: '--schema_path [schema_path]', Description: 'Path to the Meadow extended schema JSON file.' });
|
|
33
63
|
|
|
34
64
|
this.options.CommandOptions.push({ Name: '-s, --sync_mode [sync_mode]', Description: 'The sync mode: "Initial" or "Ongoing". Default is "Initial".', DefaultValue: 'Initial' });
|
|
35
65
|
|
|
@@ -42,6 +72,11 @@ class DataClone extends libCLICommandLineCommand
|
|
|
42
72
|
{
|
|
43
73
|
const tmpConfig = JSON.parse(JSON.stringify(this.fable.ProgramConfiguration));
|
|
44
74
|
|
|
75
|
+
// Layer 1: env vars (overlay on top of the config-file values).
|
|
76
|
+
// Layer 2 (CLI flags) below still wins — keeps the existing
|
|
77
|
+
// precedence intact for standalone CLI users.
|
|
78
|
+
this._applyEnvOverrides(tmpConfig);
|
|
79
|
+
|
|
45
80
|
// Apply command-line overrides for Source (API)
|
|
46
81
|
if (!tmpConfig.Source)
|
|
47
82
|
{
|
|
@@ -106,6 +141,21 @@ class DataClone extends libCLICommandLineCommand
|
|
|
106
141
|
tmpConfig.SchemaPath = this.CommandOptions.schema_path;
|
|
107
142
|
}
|
|
108
143
|
|
|
144
|
+
// Final fallback: a bundled sample schema ships with the
|
|
145
|
+
// package at <pkg>/schema/default.json (BookStore reference
|
|
146
|
+
// model). Lets containerized launches succeed out of the box
|
|
147
|
+
// even when no SchemaPath is configured, so operators get a
|
|
148
|
+
// "yes it ran" smoke test before wiring their real schema.
|
|
149
|
+
if (!tmpConfig.SchemaPath)
|
|
150
|
+
{
|
|
151
|
+
let tmpBundled = libPath.resolve(__dirname, '..', '..', '..', 'schema', 'default.json');
|
|
152
|
+
if (libFs.existsSync(tmpBundled))
|
|
153
|
+
{
|
|
154
|
+
tmpConfig.SchemaPath = tmpBundled;
|
|
155
|
+
this.log.info(`No SchemaPath configured; falling back to bundled default at ${tmpBundled}`);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
109
159
|
// Sync config
|
|
110
160
|
if (!tmpConfig.Sync)
|
|
111
161
|
{
|
|
@@ -115,6 +165,52 @@ class DataClone extends libCLICommandLineCommand
|
|
|
115
165
|
return tmpConfig;
|
|
116
166
|
}
|
|
117
167
|
|
|
168
|
+
// Overlay MEADOW_INTEGRATION_* env vars onto the config object in
|
|
169
|
+
// place. Mutates pConfig; called between the config-file load and
|
|
170
|
+
// the CLI-override pass so the layering stays predictable
|
|
171
|
+
// (CLI > env > file > defaults). Honors the `_FILE` suffix on
|
|
172
|
+
// secret-bearing keys.
|
|
173
|
+
_applyEnvOverrides(pConfig)
|
|
174
|
+
{
|
|
175
|
+
// Source (API) ----------------------------------------------
|
|
176
|
+
let tmpApiServer = _envOrFile('MEADOW_INTEGRATION_API_SERVER');
|
|
177
|
+
let tmpApiUser = _envOrFile('MEADOW_INTEGRATION_API_USERNAME');
|
|
178
|
+
let tmpApiPass = _envOrFile('MEADOW_INTEGRATION_API_PASSWORD');
|
|
179
|
+
if (tmpApiServer || tmpApiUser || tmpApiPass)
|
|
180
|
+
{
|
|
181
|
+
if (!pConfig.Source) { pConfig.Source = {}; }
|
|
182
|
+
if (tmpApiServer) { pConfig.Source.ServerURL = tmpApiServer; }
|
|
183
|
+
if (tmpApiUser) { pConfig.Source.UserID = tmpApiUser; }
|
|
184
|
+
if (tmpApiPass) { pConfig.Source.Password = tmpApiPass; }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Destination (Database) ------------------------------------
|
|
188
|
+
let tmpDbProvider = _envOrFile('MEADOW_INTEGRATION_DB_PROVIDER');
|
|
189
|
+
let tmpDbHost = _envOrFile('MEADOW_INTEGRATION_DB_HOST');
|
|
190
|
+
let tmpDbPort = _envOrFile('MEADOW_INTEGRATION_DB_PORT');
|
|
191
|
+
let tmpDbUser = _envOrFile('MEADOW_INTEGRATION_DB_USERNAME');
|
|
192
|
+
let tmpDbPass = _envOrFile('MEADOW_INTEGRATION_DB_PASSWORD');
|
|
193
|
+
let tmpDbName = _envOrFile('MEADOW_INTEGRATION_DB_NAME');
|
|
194
|
+
let tmpHasDb = tmpDbProvider || tmpDbHost || tmpDbPort || tmpDbUser || tmpDbPass || tmpDbName;
|
|
195
|
+
if (tmpHasDb)
|
|
196
|
+
{
|
|
197
|
+
if (!pConfig.Destination) { pConfig.Destination = {}; }
|
|
198
|
+
if (tmpDbProvider) { pConfig.Destination.Provider = tmpDbProvider; }
|
|
199
|
+
let tmpProviderKey = pConfig.Destination.Provider || 'MySQL';
|
|
200
|
+
if (!pConfig.Destination[tmpProviderKey]) { pConfig.Destination[tmpProviderKey] = {}; }
|
|
201
|
+
let tmpProviderCfg = pConfig.Destination[tmpProviderKey];
|
|
202
|
+
if (tmpDbHost) { tmpProviderCfg.server = tmpDbHost; }
|
|
203
|
+
if (tmpDbPort) { tmpProviderCfg.port = parseInt(tmpDbPort, 10); }
|
|
204
|
+
if (tmpDbUser) { tmpProviderCfg.user = tmpDbUser; }
|
|
205
|
+
if (tmpDbPass) { tmpProviderCfg.password = tmpDbPass; }
|
|
206
|
+
if (tmpDbName) { tmpProviderCfg.database = tmpDbName; }
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Schema path -----------------------------------------------
|
|
210
|
+
let tmpSchemaPath = _envOrFile('MEADOW_INTEGRATION_SCHEMA_PATH');
|
|
211
|
+
if (tmpSchemaPath) { pConfig.SchemaPath = tmpSchemaPath; }
|
|
212
|
+
}
|
|
213
|
+
|
|
118
214
|
onRunAsync(fCallback)
|
|
119
215
|
{
|
|
120
216
|
const tmpConfig = this._resolveConfig();
|
|
@@ -106,7 +106,7 @@ const _ViewConfiguration =
|
|
|
106
106
|
}
|
|
107
107
|
.meadow-mapping-btn-primary {
|
|
108
108
|
background: var(--facto-brand, #18a5a0);
|
|
109
|
-
color: #fff;
|
|
109
|
+
color: var(--theme-color-background-panel, #fff);
|
|
110
110
|
border-color: var(--facto-brand, #18a5a0);
|
|
111
111
|
}
|
|
112
112
|
.meadow-mapping-btn-primary:hover {
|
|
@@ -121,9 +121,9 @@ const _ViewConfiguration =
|
|
|
121
121
|
background: var(--facto-border-subtle, #e8ddc8);
|
|
122
122
|
}
|
|
123
123
|
.meadow-mapping-btn-danger {
|
|
124
|
-
background: #e74c3c;
|
|
125
|
-
color: #fff;
|
|
126
|
-
border-color: #e74c3c;
|
|
124
|
+
background: var(--theme-color-status-error, #e74c3c);
|
|
125
|
+
color: var(--theme-color-background-panel, #fff);
|
|
126
|
+
border-color: var(--theme-color-status-error, #e74c3c);
|
|
127
127
|
}
|
|
128
128
|
.meadow-mapping-btn-danger:hover {
|
|
129
129
|
opacity: 0.88;
|
|
@@ -147,7 +147,7 @@ const _ViewConfiguration =
|
|
|
147
147
|
}
|
|
148
148
|
.meadow-schema-mode-tab.active {
|
|
149
149
|
background: var(--facto-brand, #18a5a0);
|
|
150
|
-
color: #fff;
|
|
150
|
+
color: var(--theme-color-background-panel, #fff);
|
|
151
151
|
border-color: var(--facto-brand, #18a5a0);
|
|
152
152
|
}
|
|
153
153
|
.meadow-section-title {
|
|
@@ -43,7 +43,7 @@ class FlowCardSolverExpression extends libPictFlowCard
|
|
|
43
43
|
BodyContent:
|
|
44
44
|
{
|
|
45
45
|
ContentType: 'html',
|
|
46
|
-
Template: '<div style="font-size:10px; padding:2px 4px; color
|
|
46
|
+
Template: '<div style="font-size:10px; padding:2px 4px; color:var(--theme-color-border-default, #ccc); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; max-width:200px;">{~D:Record.Data.SolverExpression~}</div>'
|
|
47
47
|
},
|
|
48
48
|
PropertiesPanel:
|
|
49
49
|
{
|
|
@@ -53,7 +53,7 @@ class FlowCardSolverExpression extends libPictFlowCard
|
|
|
53
53
|
Title: 'Solver Expression',
|
|
54
54
|
Configuration:
|
|
55
55
|
{
|
|
56
|
-
Template: '<div style="padding:8px;"><label style="display:block; margin-bottom:4px; font-weight:bold; font-size:12px;">Solver Expression</label><textarea id="FlowCard-SOL-{~D:Record.Hash~}" style="width:100%; height:80px; font-family:monospace; font-size:12px; resize:vertical; background:#1a1a2e; color
|
|
56
|
+
Template: '<div style="padding:8px;"><label style="display:block; margin-bottom:4px; font-weight:bold; font-size:12px;">Solver Expression</label><textarea id="FlowCard-SOL-{~D:Record.Hash~}" style="width:100%; height:80px; font-family:monospace; font-size:12px; resize:vertical; background:#1a1a2e; color:var(--theme-color-border-default, #e0e0e0); border:1px solid var(--theme-color-text-secondary, #444); border-radius:4px; padding:6px;" onchange="(function(el){var n=pict.views[\'MeadowMapping-Flow\'];if(n&&n._FlowData){for(var i=0;i<n._FlowData.Nodes.length;i++){if(n._FlowData.Nodes[i].Hash===\'{~D:Record.Hash~}\'){n._FlowData.Nodes[i].Data.SolverExpression=el.value;if(typeof n.renderFlow===\'function\')n.renderFlow();break;}}}})(this)">{~D:Record.Data.SolverExpression~}</textarea><div style="font-size:10px; color:var(--theme-color-text-muted, #888); margin-top:4px;">Example: IF(IncomingRecord.Type == \'Premium\', \'GOLD\', \'SILVER\')</div></div>'
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
},
|
|
@@ -42,7 +42,7 @@ class FlowCardTemplateExpression extends libPictFlowCard
|
|
|
42
42
|
BodyContent:
|
|
43
43
|
{
|
|
44
44
|
ContentType: 'html',
|
|
45
|
-
Template: '<div style="font-size:10px; padding:2px 4px; color
|
|
45
|
+
Template: '<div style="font-size:10px; padding:2px 4px; color:var(--theme-color-border-default, #ccc); overflow:hidden; white-space:nowrap; text-overflow:ellipsis; max-width:200px;">{~D:Record.Data.TemplateExpression~}</div>'
|
|
46
46
|
},
|
|
47
47
|
PropertiesPanel:
|
|
48
48
|
{
|
|
@@ -52,7 +52,7 @@ class FlowCardTemplateExpression extends libPictFlowCard
|
|
|
52
52
|
Title: 'Template Expression',
|
|
53
53
|
Configuration:
|
|
54
54
|
{
|
|
55
|
-
Template: '<div style="padding:8px;"><label style="display:block; margin-bottom:4px; font-weight:bold; font-size:12px;">Template Expression</label><textarea id="FlowCard-TPL-{~D:Record.Hash~}" style="width:100%; height:80px; font-family:monospace; font-size:12px; resize:vertical; background:#1a1a2e; color
|
|
55
|
+
Template: '<div style="padding:8px;"><label style="display:block; margin-bottom:4px; font-weight:bold; font-size:12px;">Template Expression</label><textarea id="FlowCard-TPL-{~D:Record.Hash~}" style="width:100%; height:80px; font-family:monospace; font-size:12px; resize:vertical; background:#1a1a2e; color:var(--theme-color-border-default, #e0e0e0); border:1px solid var(--theme-color-text-secondary, #444); border-radius:4px; padding:6px;" onchange="(function(el){var n=pict.views[\'MeadowMapping-Flow\'];if(n&&n._FlowData){for(var i=0;i<n._FlowData.Nodes.length;i++){if(n._FlowData.Nodes[i].Hash===\'{~D:Record.Hash~}\'){n._FlowData.Nodes[i].Data.TemplateExpression=el.value;if(typeof n.renderFlow===\'function\')n.renderFlow();break;}}}})(this)">{~D:Record.Data.TemplateExpression~}</textarea><div style="font-size:10px; color:var(--theme-color-text-muted, #888); margin-top:4px;">Use {~D:Record.FieldName~} syntax. Example: {~D:Record.Name~} in {~D:Record.City~}</div></div>'
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
},
|