@enfocussw/switch-scripting-context 25.11.0-beta.10 → 25.11.0-beta.11

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/CHANGELOG.md CHANGED
@@ -5,6 +5,72 @@ All notable changes to this package are documented here. Format follows
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [25.11.0-beta.11] - 2026-09-08
9
+
10
+ ### Added
11
+ - `api-entry-points.md` — documented that Switch's entry-point scanner is regex-based (not a real
12
+ parser) and can silently drop `function` declarations from certain source shapes: string literals
13
+ ending in a backslash, regex literals (especially ones containing an unescaped `/` inside a
14
+ character class), and division not in a plain `word / word` shape. Applies to every entry point,
15
+ not just the ones checked at load time; `calculateScriptExpression` is the one exception, since
16
+ it's dispatched directly rather than through this scanner. Verified against real
17
+ `SwitchScriptTool --pack` output for each failure mode.
18
+ - `api-tooling.md` — new "Verify entry points before packing" section with a Python/Node.js script
19
+ to check a built `main.js` for the expected entry points before relying on `--pack`, which doesn't
20
+ validate this itself.
21
+ - `api-script-structure.md` — cross-linked the entry-point scanner constraints from the "Agent
22
+ editing policy" callout.
23
+ - `api-app-guidelines.md` — new pre-publish checklist for scripts submitted to the Enfocus Appstore,
24
+ covering property naming/tooltip/editor/default requirements, entry point and `sendTo*()`
25
+ consistency with declared connections, logging quality, temp file/path handling, and app-only
26
+ packaging rules (no embedded Oracle JRE, universal signed macOS Mach-O binaries in extra files,
27
+ immutable extra files, translation completeness). Registered in the routing table, both `init.ts`
28
+ doc-file arrays, and the README's included-docs list.
29
+
30
+ ### Changed
31
+ - `switch-scripting.md` — broadened the `api-entry-points.md` routing-table trigger to any edit to
32
+ `main.ts`/`main.js`, not just adding a new entry point.
33
+ - `api-property-editors.md` — documented that the `nofiles`/`allfiles`/`allotherfiles` and
34
+ `nofolders`/`allfolders`/`allotherfolders` literal editors are specifically for connection
35
+ include/exclude filter mask properties, with the exact `Editor`/`Default`/`Subtype` chain verified
36
+ against Switch's own built-in connection filter declarations; added the matching rows to Common
37
+ practices. Noted that `next`/`current` have no confirmed script-facing use case. Noted that `none`
38
+ is the sanctioned way to allow an empty value under `Validation="Standard"`.
39
+ - `api-script-declaration.md` — cross-linked the `Validation` row to the `none` literal editor for
40
+ allowing empty values under `Standard` validation.
41
+ - `api-app-guidelines.md`, `api-script-declaration.md`, `api-property-editors.md`,
42
+ `api-entry-points.md`, `api-job-patterns.md`, `api-logging.md`, `api-script-structure.md` —
43
+ reclassified the Appstore submission rules added previously: most turned out to be universal
44
+ correctness rules (functional requirements or bugs if violated) rather than app-specific policy,
45
+ so their "App guideline" framing was removed and they're now stated as plain rules that apply to
46
+ every script. Only a handful remain "mandatory for apps, recommended for scripts" (property
47
+ naming/tooltip/default, non-module-editor requirement, log volume, platform-independent paths).
48
+ "No embedded Oracle JRE" moved out of the app-only section entirely, since it applies to any
49
+ script bundling extra files. `api-app-guidelines.md` is now organized into three explicit tiers
50
+ (Universal / Apps required-scripts recommended / Apps only) instead of one flat list.
51
+ - `api-script-declaration.md`, `api-property-editors.md`, `api-entry-points.md`, `api-job-patterns.md`,
52
+ `api-job.md`, `api-logging.md`, `api-script-structure.md` — wove the individual Appstore submission
53
+ rules (above) directly into the relevant existing sections (property attributes, editor tables,
54
+ entry point signatures, `sendTo*()`/`ConnectionType` rules, logging conventions, packaging), each
55
+ cross-linked to and from the new checklist, so they're followed from the start rather than caught
56
+ only at a pre-publish review.
57
+ - `api-script-declaration.md` — fixed the `Validation="Custom"` row, which named a nonexistent
58
+ `isPropertyValid` entry point; the actual entry point (per `api-entry-points.md`) is
59
+ `validateProperties`/`validateConnectionProperties`.
60
+ - `api-job-patterns.md` — clarified that the automatic executor-refresh cleanup only applies to
61
+ temp files created via `flowElement.createPathWithName()` (the recommended default); temp files
62
+ created any other way must be cleaned up explicitly by the script. Also notes that the `tmp` npm
63
+ package is not recommended, and if used anyway, its `setGracefulCleanup()` is not always reliable
64
+ in the Switch execution environment and `discardDescriptor: true` should be set.
65
+ - `api-flow-element.md`, `api-connection.md`, `api-script-declaration.md` — cross-linked and made
66
+ explicit that `getPropertyStringValue()`/`connection.getPropertyStringValue()` throws
67
+ `"Invalid tag: <tag>"` for a `Dependency` dependent property currently hidden by its master's
68
+ value, and that `hasProperty()`/`connection.hasProperty()` should be used to guard against this
69
+ (e.g. before fetching properties dependent on a drop-down/enum master). Also notes that the shown
70
+ set can vary per job if the master's value is dynamic, and that a hidden dependent's value cannot
71
+ be read at all — a script needing more than one dependency group's data at once must use a
72
+ different property structure, not `Dependency`-based hiding.
73
+
8
74
  ## [25.11.0-beta.10] - 2026-09-03
9
75
 
10
76
  ### Added
package/README.md CHANGED
@@ -149,3 +149,4 @@ The `switch-docs/` folder contains:
149
149
  - `switch-api/api-property-editors.md` — property editor types and string return values
150
150
  - `switch-api/api-job-patterns.md` — file access semantics, routing rules, child jobs, executor limits
151
151
  - `switch-api/api-execution-environment.md` — process model, state persistence across jobs, error handling, npm/native module constraints
152
+ - `switch-api/api-app-guidelines.md` — pre-publish checklist for Appstore apps: property naming/tooltips/editors, entry point and sendTo consistency, logging quality, packaging
package/dist/init.js CHANGED
@@ -95,6 +95,7 @@ function generateCursorMdc(docsDir) {
95
95
  'api-execution-environment.md',
96
96
  'api-logging.md',
97
97
  'api-logs-and-dataroot.md',
98
+ 'api-app-guidelines.md',
98
99
  ];
99
100
  const fileList = apiFiles.map(f => `- \`${docsDir}/switch-api/${f}\``).join('\n');
100
101
  return `---
@@ -152,6 +153,7 @@ const API_FILES = [
152
153
  'api-execution-environment.md',
153
154
  'api-logging.md',
154
155
  'api-logs-and-dataroot.md',
156
+ 'api-app-guidelines.md',
155
157
  ];
156
158
  function inlineApiFileList(docsDir) {
157
159
  return API_FILES.map(f => `- \`${docsDir}/switch-api/${f}\``).join('\n');
@@ -0,0 +1,122 @@
1
+ # App Publishing Guidelines
2
+
3
+ A pre-publish checklist for a script intended for the Enfocus Appstore (`ScriptPackageType="App"`
4
+ — see [api-script-structure.md § Script vs App](api-script-structure.md#script-vs-app)). Each item
5
+ here is enforced in more detail at the linked section — this page exists to be read top-to-bottom
6
+ right before submission, not as the primary source for any individual rule.
7
+
8
+ Items fall into three tiers, marked on each one:
9
+
10
+ - **Universal** — a correctness rule for every script, app or not. Violating it is a bug, not a
11
+ style choice.
12
+ - **Apps required / scripts recommended** — mandatory for Appstore review; worth following in a
13
+ plain `Script` too, but not enforced there.
14
+ - **Apps only** — doesn't apply to a plain `Script` package at all.
15
+
16
+ ---
17
+
18
+ ## Properties
19
+
20
+ - [ ] **Apps required / scripts recommended.** Every property name (`LocalizedTagName`) is 30
21
+ characters or fewer and uses sentence-style capitalization ("Customer name", not "Customer
22
+ Name") — see [api-script-declaration.md](api-script-declaration.md#custom-property-elements-elementfields).
23
+ - [ ] **Apps required / scripts recommended.** Every property has a `Tooltip`, unless it's fully
24
+ self-explanatory with no extra constraints (e.g. "Number of copies" with no min/max) — see
25
+ [api-script-declaration.md](api-script-declaration.md#custom-property-elements-elementfields).
26
+ - [ ] **Universal.** Dynamic editors (`sltextwithvar`/`mltextwithvar`, `conditionwithvar`,
27
+ `scriptexp`) are enabled wherever the value could plausibly vary per job, and omitted where the
28
+ value is inherently static (e.g. a dataset name) — see
29
+ [api-property-editors.md § Common practices](api-property-editors.md#common-practices).
30
+ - [ ] **Universal.** Keyword-style default values ("Default", "None", "Automatic", …) use the
31
+ matching literal editor (`default`, `none`, `automatic`, …), not a typed string default — see
32
+ [api-property-editors.md § Literal editors](api-property-editors.md#literal-editors).
33
+ - [ ] **Universal.** Every property using `askplugin`/`askplugin2` ("Select from library"/"Select
34
+ many from library") has a matching `getLibraryForProperty`/`getLibraryForConnectionProperty`
35
+ entry point — see [api-entry-points.md § Property UI callbacks](api-entry-points.md#property-ui-callbacks).
36
+ - [ ] **Apps required / scripts recommended.** Every property's editor chain includes at least one
37
+ editor that doesn't require an add-on Switch module — e.g. never offer `scriptexp` (Scripting
38
+ Module) as the only editor — see
39
+ [api-property-editors.md § Common practices](api-property-editors.md#common-practices).
40
+ - [ ] **Universal.** Every property using the `external`/`external2`/… editor has either a
41
+ non-empty dependent `Application` property or a `findExternalEditorPath` entry point — see
42
+ [api-property-editors.md § Notes](api-property-editors.md#notes).
43
+ - [ ] **Apps required / scripts recommended.** Every property has a default value unless one
44
+ genuinely isn't possible, in which case the `Tooltip` gives an example value instead — see
45
+ [api-script-declaration.md](api-script-declaration.md#custom-property-elements-elementfields).
46
+ - [ ] **Universal.** Every property/connection field declaring `Validation="Custom"` or `"Standard
47
+ and custom"` has a matching `validateProperties`/`validateConnectionProperties` entry point —
48
+ see [api-entry-points.md § Property UI callbacks](api-entry-points.md#property-ui-callbacks).
49
+
50
+ ## Entry points
51
+
52
+ - [ ] **Universal.** `jobArrived` is present when `IncomingConnections="Yes"`; `timerFired` is
53
+ present when `IncomingConnections="No"` (and may additionally be present alongside `jobArrived`)
54
+ — see [api-entry-points.md § Job processing](api-entry-points.md#job-processing).
55
+ - [ ] **Universal.** No empty `jobArrived` or `timerFired` — an entry point that does nothing is
56
+ removed entirely, not left in place — see
57
+ [api-entry-points.md § Job processing](api-entry-points.md#job-processing).
58
+
59
+ ## Sending jobs
60
+
61
+ All **universal** — see
62
+ [api-job-patterns.md § Sending jobs](api-job-patterns.md#sending-jobs) and
63
+ [api-script-declaration.md § ConnectionFields](api-script-declaration.md#connectionfields) for full
64
+ detail.
65
+
66
+ - [ ] If `OutgoingConnections` is not `No`, some `sendTo*()` method is called at least once for
67
+ any job actually routed (a purely timer/event-driven script that never touches a job is a
68
+ legitimate exception).
69
+ - [ ] If `OutgoingConnections="Unlimited"`, `job.sendToSingle()` is never used.
70
+ - [ ] `ConnectionType="Move"` never uses `job.sendToData()`/`job.sendToLog()`.
71
+ - [ ] `ConnectionType="TrafficLight"` uses only `job.sendToData()`/`job.sendToLog()`; `sendToData`
72
+ is used when a Data Success/Warning/Error connection is enabled, `sendToLog` when the
73
+ corresponding Log connection is enabled.
74
+ - [ ] An unused Success/Warning/Error connection is omitted from `ConnectionFields` entirely, not
75
+ left declared but unrouted.
76
+ - [ ] A job with no output calls `job.sendToNull()`.
77
+ - [ ] `job.fail()`/`flowElement.failProcess()` is used for genuine processing errors; an error
78
+ `TrafficLight` connection isn't used as a substitute for `fail()`.
79
+
80
+ ## Logging
81
+
82
+ - [ ] **Universal.** An `Error` is logged when a custom
83
+ `validateProperties`/`validateConnectionProperties` check returns `valid: false` for a tag, and
84
+ when `getLibraryForProperty`/`getLibraryForConnectionProperty` would otherwise return an empty
85
+ list — see [api-logging.md § Message quality](api-logging.md#message-quality).
86
+ - [ ] **Universal.** Log messages are free of grammatical errors and typos — see
87
+ [api-logging.md § Message quality](api-logging.md#message-quality).
88
+ - [ ] **Universal.** Log messages use `%1`/`%2`/… placeholders with `messageParams`, not string
89
+ concatenation or interpolation of dynamic values — required for correct translation. See
90
+ [api-logging.md § Placeholders instead of concatenation](api-logging.md#placeholders-instead-of-concatenation),
91
+ which also covers the separate, narrower literal-`%` escaping gotcha.
92
+ - [ ] **Apps required / scripts recommended.** Logging isn't excessive — no `Info`/`Debug` calls
93
+ that don't help a flow operator or support engineer understand what happened — see
94
+ [api-logging.md § Log volume](api-logging.md#log-volume).
95
+
96
+ ## Temp files and paths
97
+
98
+ - [ ] **Universal.** All temporary files and folders are removed by the script before it finishes,
99
+ rather than relying on Switch's executor-refresh cleanup — see
100
+ [api-job-patterns.md § Temp file cleanup](api-job-patterns.md#temp-file-cleanup).
101
+ - [ ] **Universal.** No Oracle JRE is embedded as an extra file, in a script or an app — see
102
+ [api-script-structure.md § Script folder files](api-script-structure.md#script-folder-files).
103
+ - [ ] **Apps required / scripts recommended.** File system path strings are built with Node's
104
+ `path` module, not hardcoded separators or string concatenation — see
105
+ [api-job-patterns.md § Platform-independent paths](api-job-patterns.md#platform-independent-paths).
106
+
107
+ ---
108
+
109
+ ## App-only
110
+
111
+ The following apply only to `ScriptPackageType="App"`, not to a plain `Script` package at all. See
112
+ [api-script-structure.md § App Store submission guidelines](api-script-structure.md#app-store-submission-guidelines)
113
+ for full detail.
114
+
115
+ - [ ] The app is preferably available in more languages than just English.
116
+ - [ ] Where translations are used, they're complete (every shipped language has every string), and
117
+ logging goes through the translation mechanism rather than hardcoded English.
118
+ - [ ] Any macOS Mach-O binaries (executables or dynamic libraries) in extra files are built for
119
+ both `x86_64` and `arm64`.
120
+ - [ ] Any macOS Mach-O binaries in extra files are signed.
121
+ - [ ] No file that might be modified or regenerated at runtime is placed inside the app's own
122
+ package folder — that would fail the app's signature validation and cause Switch to remove it.
@@ -26,7 +26,7 @@ Connection properties are declared in the script's XML declaration (`ConnectionF
26
26
  ```ts
27
27
  connection.getPropertyStringValue(tag: string): Promise<string | string[]>
28
28
  ```
29
- Returns the connection property value as a string.
29
+ Returns the connection property value as a string. Throws `"Invalid tag: <tag>"` if the tag is unknown **or if it's a `Dependency` dependent currently hidden by its master's value** — see [api-script-declaration.md § Dependency](api-script-declaration.md#custom-property-elements-elementfields). Before fetching a dependent property, confirm its condition holds (from the already-read master value, or via `hasProperty(tag)` below) rather than fetching it unconditionally.
30
30
 
31
31
  ```ts
32
32
  connection.getPropertyType(tag: string): PropertyType
@@ -41,7 +41,7 @@ Returns the English display name of the property (for use in log messages).
41
41
  ```ts
42
42
  connection.hasProperty(tag: string): boolean
43
43
  ```
44
- Returns `true` if the property exists and is visible for the current configuration.
44
+ Returns `true` if the property exists and is visible for the current configuration. Use this to guard `getPropertyStringValue(tag)` calls on `Dependency` dependents — see the note above.
45
45
 
46
46
  ## File count
47
47
 
@@ -2,6 +2,54 @@
2
2
 
3
3
  Entry points are top-level `async` functions with specific names. They are **not exported**. Switch calls them automatically based on flow events.
4
4
 
5
+ ## Entry-point scanner constraints
6
+
7
+ Switch discovers a script's entry points by regex, not by parsing. Before matching
8
+ `function\s+(name)\s*\(` it strips comments and literals with a regex that doesn't understand JS
9
+ escaping. Certain source shapes make that strip regex delete large spans of real code — and every
10
+ `function` declaration inside the deleted span becomes invisible to Switch, whether or not that
11
+ function is itself well-formed.
12
+
13
+ This applies to **every** entry point on this page, not only the four checked below — a swallowed
14
+ `validateProperties` or `getLibraryForProperty` fails silently (the callback just never runs, no
15
+ error anywhere), which is worse than the loud failure the four below produce. The one exception is
16
+ `calculateScriptExpression`: it's invoked directly at runtime rather than through this scanner, so
17
+ it isn't at risk.
18
+
19
+ If none of `jobArrived`, `timerFired`, `flowStopTriggered`, `httpRequestTriggeredAsync` survives,
20
+ Switch fails with `Cannot open script '<name>': no entry points found` — `httpRequestTriggeredSync`
21
+ alone does not satisfy this check. Both the packed `.sscript` (the entry-point list is baked in at
22
+ pack time) and the unpacked script folder (`main.js` is rescanned by this same scanner every time
23
+ Switch loads it, e.g. running or debugging from Designer) are affected. Packing does not validate
24
+ or warn about any of this — a script that packs cleanly can still fail to load.
25
+
26
+ **Rules for writing entry points in `main.ts`/`main.js`:**
27
+
28
+ 1. Never write a string literal whose content ends with a backslash: `"\\"`, `'\\'`, `` `\\` ``,
29
+ `"C:\\Temp\\"`. This is the highest-frequency trigger — the strip regex hunts for the next
30
+ matching quote character anywhere later in the file to close the literal (a backslash right
31
+ before the real closing quote makes it skip past that quote), and deletes everything in between.
32
+ Use `"\x5c"`, `"\u005C"`, `String.fromCharCode(92)`, or restructure (e.g.
33
+ `JSON.stringify(v).slice(1, -1)` for backslash/quote escaping) instead. A backslash elsewhere in
34
+ the literal is fine: `"\\n is newline"`, `"a\\b"`.
35
+ 2. Avoid regex literals — including ones that look properly closed, like `/[^/]+/` (a character
36
+ class containing an unescaped `/`). The strip regex doesn't understand character classes, so it
37
+ closes the "literal" early at the inner `/` and leaves a dangling `/` that swallows forward to
38
+ the next `/` anywhere later in the file. Use `new RegExp("...")` instead.
39
+ 3. Avoid division that isn't in the plain `word / word` shape. `a / b` is recognised and stripped
40
+ safely; `) / 2`, `] / 2`, and `/=` are not, and the leftover `/` swallows forward exactly like an
41
+ unclosed regex literal. Rewrite as `Math.floor(x / y)` with plain identifiers, or hoist to a
42
+ named variable first.
43
+ 4. Declare every entry point with the literal `function` keyword at top level:
44
+ `function jobArrived(...)` or `async function jobArrived(...)`. Arrow functions,
45
+ `exports.jobArrived = function (...)`, and class methods are invisible to the scanner.
46
+ 5. Entry-point names are matched case-sensitively. `JobArrived` is silently dropped during
47
+ extraction — if another valid entry point is also present the script packs and loads without
48
+ error, the misnamed function just never runs.
49
+
50
+ See [api-tooling.md § Verify entry points before packing](api-tooling.md#verify-entry-points-before-packing)
51
+ for a script to catch this before it reaches Switch.
52
+
5
53
  ## Flow lifecycle
6
54
 
7
55
  ```ts
@@ -21,6 +69,10 @@ async function timerFired(s: Switch, flowElement: FlowElement): Promise<void>
21
69
  ```
22
70
  Called on a recurring timer. First fires after `flowStartTriggered`. Set the interval via `flowElement.setTimerInterval(seconds)`. Cannot coexist with `flowStartTriggered` as the sole entry point.
23
71
 
72
+ Required when `IncomingConnections="No"`; optional (alongside `jobArrived`) when
73
+ `IncomingConnections="Yes"`. Never leave it present but empty — remove it entirely if it does
74
+ nothing.
75
+
24
76
  ## Job processing
25
77
 
26
78
  ```ts
@@ -28,6 +80,12 @@ async function jobArrived(s: Switch, flowElement: FlowElement, job: Job): Promis
28
80
  ```
29
81
  Called when a job arrives. Must eventually call a `job.sendTo*()` or `job.fail()`. Can run concurrently if configured in the script XML declaration.
30
82
 
83
+ `jobArrived` and `timerFired` presence must match the declared connections — present when
84
+ `IncomingConnections="Yes"` (`timerFired` may additionally be present); when
85
+ `IncomingConnections="No"`, `timerFired` must be present instead. See
86
+ [api-script-declaration.md](api-script-declaration.md#built-in-elementfields). Never leave an empty
87
+ `jobArrived` or `timerFired` in the script — remove the function entirely if it does nothing.
88
+
31
89
  ```ts
32
90
  async function abort(s: Switch, flowElement: FlowElement, job: Job, abortData: any): Promise<void>
33
91
  ```
@@ -59,24 +117,34 @@ Not available for debugging.
59
117
  ```ts
60
118
  async function getLibraryForProperty(s: Switch, flowElement: FlowElement, tag: string): Promise<string[]>
61
119
  ```
62
- Returns a dynamic list of values for a property's library dropdown.
120
+ Returns a dynamic list of values for a property's library dropdown. Mandatory if any property uses
121
+ the `askplugin`/`askplugin2` editor (see
122
+ [api-property-editors.md](api-property-editors.md#modal-editors)). Log an error via `flowElement.log()`
123
+ if the list would otherwise come back empty, so the user can see why the dropdown has nothing in it.
63
124
 
64
125
  ```ts
65
126
  async function getLibraryForConnectionProperty(s: Switch, flowElement: FlowElement, c: Connection, tag: string): Promise<string[]>
66
127
  ```
67
- Returns a dynamic list of values for a connection property's library dropdown.
128
+ Returns a dynamic list of values for a connection property's library dropdown. Same guidance as
129
+ `getLibraryForProperty` above, for connection fields using `askplugin`/`askplugin2`.
68
130
 
69
131
  ```ts
70
132
  async function validateProperties(s: Switch, flowElement: FlowElement, tags: string[]): Promise<{ tag: string, valid: boolean }[]>
71
133
  ```
72
- Validates one or more property values. Return one result object per tag.
134
+ Validates one or more property values. Return one result object per tag. Mandatory if any property
135
+ declares `Validation="Custom"` or `"Standard and custom"` (see
136
+ [api-script-declaration.md](api-script-declaration.md#custom-property-elements-elementfields)). Log
137
+ an error via `flowElement.log()` for any tag returned with `valid: false`, so the user sees why.
73
138
 
74
139
  ```ts
75
140
  async function validateConnectionProperties(s: Switch, flowElement: FlowElement, c: Connection, tags: string[]): Promise<{ tag: string, valid: boolean }[]>
76
141
  ```
77
- Validates one or more connection property values.
142
+ Validates one or more connection property values. Same guidance as `validateProperties` above, for
143
+ connection fields declaring `Validation="Custom"`/`"Standard and custom"`.
78
144
 
79
145
  ```ts
80
146
  async function findExternalEditorPath(s: Switch, flowElement: FlowElement, tag: string): Promise<string>
81
147
  ```
82
- Resolves the path to an external editor for a property.
148
+ Resolves the path to an external editor for a property. Required whenever the
149
+ `external`/`external2`/… editor is used, unless the dependent `Application` property is non-empty —
150
+ see [api-property-editors.md § Notes](api-property-editors.md#notes).
@@ -21,7 +21,7 @@ Properties are configured by the user in the Switch canvas and declared in the s
21
21
  ```ts
22
22
  flowElement.getPropertyStringValue(tag: string): Promise<string | string[]>
23
23
  ```
24
- Returns the property value as a string (or array for multi-value properties). For `OAuthToken` properties, returns a valid refreshed token. Throws `"Invalid tag: <tag>"` if the tag is unknown or a hidden dependent property. See [api-property-editors.md](api-property-editors.md) for editor types and their return value formats.
24
+ Returns the property value as a string (or array for multi-value properties). For `OAuthToken` properties, returns a valid refreshed token. Throws `"Invalid tag: <tag>"` if the tag is unknown **or if it's a `Dependency` dependent currently hidden by its master's value** — see [api-script-declaration.md § Dependency](api-script-declaration.md#custom-property-elements-elementfields). Before fetching a dependent property, confirm its condition holds (from the already-read master value, or via `hasProperty(tag)` below) rather than fetching it unconditionally. See [api-property-editors.md](api-property-editors.md) for editor types and their return value formats.
25
25
 
26
26
  > **Dynamic values (Switch variables and script expressions) are only resolved when `getPropertyStringValue()` is called from `jobArrived`.** In any other entry point (e.g. `timerFired`) the raw unresolved string is returned.
27
27
  >
@@ -40,7 +40,7 @@ Returns the English display name of the property (for use in log messages).
40
40
  ```ts
41
41
  flowElement.hasProperty(tag: string): boolean
42
42
  ```
43
- Returns `true` if the property exists and is visible for the current configuration.
43
+ Returns `true` if the property exists and is visible for the current configuration. Use this to guard `getPropertyStringValue(tag)` calls on `Dependency` dependents — see the note above.
44
44
 
45
45
  ## Connections
46
46
 
@@ -75,7 +75,7 @@ Logs a fatal error and puts the element into the "problem process" state. `messa
75
75
  ```ts
76
76
  flowElement.createJob(path: string): Promise<Job>
77
77
  ```
78
- Creates a new job from an existing file/folder path. Valid only in `jobArrived` and `timerFired`. The new job has default properties (no parent). The caller is responsible for cleaning up the source file after routing. See [api-job-patterns.md](api-job-patterns.md) for temp file cleanup rules and routing constraints.
78
+ Creates a new job from an existing file/folder path. Valid only in `jobArrived` and `timerFired`. The new job has default properties (no parent). The caller is responsible for cleaning up the source file after routing. See [api-job-patterns.md](api-job-patterns.md#temp-file-cleanup) for temp file cleanup rules and [api-job-patterns.md](api-job-patterns.md#sending-jobs) for routing constraints.
79
79
 
80
80
  ```ts
81
81
  flowElement.getJobs(ids: string[]): Promise<Job[]>
@@ -94,7 +94,7 @@ Subscribes to a channel to receive jobs from it. Only one subscriber per channel
94
94
  ```ts
95
95
  flowElement.createPathWithName(name: string, createFolder: boolean): Promise<any>
96
96
  ```
97
- Creates a temporary path (optionally as a folder — `createFolder` is required, not optional). Returns an empty string only if creating the folder throws (e.g. a permissions error); it does not check whether the path already exists first.
97
+ Creates a temporary path (optionally as a folder — `createFolder` is required, not optional). Returns an empty string only if creating the folder throws (e.g. a permissions error); it does not check whether the path already exists first. This is the recommended way to create temp files — see [api-job-patterns.md](api-job-patterns.md#temp-file-cleanup) for cleanup rules, including why this method should be preferred over ad hoc temp-file approaches like the `tmp` npm package.
98
98
 
99
99
  ```ts
100
100
  flowElement.getFileCount(nested?: boolean): Promise<number>
@@ -25,7 +25,24 @@ Switch does **not** automatically clean up files you create or pass to job-creat
25
25
  - Files/folders passed to `job.createChild(path)` — delete after routing.
26
26
  - Files/folders passed to `job.createDataset(name, filePath, model)` — delete after routing.
27
27
 
28
- > Files accumulate between executor refreshes if not cleaned up. The executor is refreshed after 5 min idle, 5,000 tasks, 150 MB memory, or 1,024 open file handles at which point accumulated files are removed automatically, but do not rely on this.
28
+ Use `flowElement.createPathWithName()` to create temp files by default its output lives in the executor's temp area, which Switch removes automatically on executor refresh. Even so, delete explicitly after routing rather than relying on that refresh (see below).
29
+
30
+ If a temp file is created some other way (e.g. `fs`/`path` writing outside the temp area, or a library that writes its own scratch files), the same rule applies: the script must delete it explicitly. Don't rely on Switch's executor-refresh cleanup for files outside the temp area — it only removes what it put there, so anything else creates a permanent leak.
31
+
32
+ The [`tmp`](https://www.npmjs.com/package/tmp) npm package is common in scripts written by app creators, but it is **not recommended** here — prefer `flowElement.createPathWithName()` instead. If `tmp` is used anyway: its `setGracefulCleanup()` is not always reliable in the Switch script execution environment, so don't depend on it for cleanup — delete the file explicitly after routing regardless. Also pass `discardDescriptor: true` when creating a temp path (e.g. `tmp.fileSync({ discardDescriptor: true })`), or the open file descriptor can leak.
33
+
34
+ > Files accumulate between executor refreshes if not cleaned up. The executor is refreshed after 5 min idle, 5,000 tasks, 150 MB memory, or 1,024 open file handles — at which point accumulated files **created via `createPathWithName()`** are removed automatically, but do not rely on this.
35
+
36
+ ---
37
+
38
+ ## Platform-independent paths
39
+
40
+ Switch Server runs on both Windows and macOS. **App guideline (mandatory for apps, recommended for
41
+ scripts):** a script package should work unmodified on either — paths returned by `job.get()`,
42
+ `flowElement.createPathWithName()`, and similar methods use the host OS's native separator, so avoid
43
+ hardcoding `/` or `\` when building or splitting a path, and don't assume one form when comparing or
44
+ matching paths. Use Node's `path` module (`path.join`, `path.resolve`, `path.sep`, `path.basename`,
45
+ etc.) instead of string concatenation.
29
46
 
30
47
  ---
31
48
 
@@ -49,6 +66,27 @@ await child.sendTo(conn2);
49
66
  await fs.promises.rm(tempPath);
50
67
  ```
51
68
 
69
+ **Consistency with declared connections** (applies to every script, not just apps):
70
+ - If `OutgoingConnections` is not `No`, the script must call some `sendTo*()` method at least once
71
+ for any job it actually routes. A purely timer/event-driven script that never touches a job
72
+ (polling an API, rotating logs, etc.) is a legitimate exception.
73
+ - If `OutgoingConnections="Unlimited"` (more than one outgoing connection allowed), never use
74
+ `job.sendToSingle()` — it only makes sense when exactly one outgoing connection exists.
75
+ - If no output is produced for a job, call `job.sendToNull()` rather than leaving it unrouted.
76
+ - `ConnectionType="Move"` (see [api-script-declaration.md](api-script-declaration.md#connectionfields)):
77
+ never use `job.sendToData()`/`job.sendToLog()` — those are for `TrafficLight` connections only. Use
78
+ `job.sendTo()`/`job.sendToSingle()`.
79
+ - `ConnectionType="TrafficLight"`: use only `job.sendToData()`/`job.sendToLog()` — never
80
+ `job.sendTo()`/`job.sendToSingle()`. Use `sendToData` when a Data Success/Warning/Error connection
81
+ is enabled in the declaration, and `sendToLog` when the corresponding Log connection is enabled.
82
+ Conversely, don't declare a Success/Warning/Error element the script never routes to — omit it from
83
+ `ConnectionFields` entirely (see the TrafficLight example in
84
+ [api-script-declaration.md](api-script-declaration.md#connectionfields)).
85
+ - Prefer `job.fail()`/`flowElement.failProcess()` for genuine processing errors (see
86
+ [api-logging.md](api-logging.md)) over silently routing a failed job to a generic error connection —
87
+ reserve an error `TrafficLight` connection for cases the flow author should be able to reroute
88
+ downstream, not as a substitute for `fail()`.
89
+
52
90
  ---
53
91
 
54
92
  ## Dataset writes must precede child job creation
@@ -36,7 +36,7 @@ Returns the local filesystem path to the job. Use `AccessLevel.ReadOnly` to read
36
36
 
37
37
  ## Routing
38
38
 
39
- Every job must be routed exactly once.
39
+ Every job must be routed exactly once. See [api-job-patterns.md § Sending jobs](api-job-patterns.md#sending-jobs) for which `sendTo*()` method is appropriate for a given `ConnectionType`/`OutgoingConnections` declaration.
40
40
 
41
41
  ```ts
42
42
  job.sendToNull(): Promise<void>
@@ -92,7 +92,7 @@ Log a message including job context. To log a literal `%`, pass it as a param: `
92
92
  ```ts
93
93
  job.createChild(path: string): Promise<Job>
94
94
  ```
95
- Creates a new job inheriting the processing history, metadata, and private data of the parent. Takes effect immediately: the child's datasets are copied from the parent's *current* server-side state, so any pending `createDataset()` write not yet flushed by a `sendTo*()` call is not reflected in it. See [api-job-patterns.md](api-job-patterns.md#dataset-writes-must-precede-child-job-creation) — creating a child before writing a dataset is a common source of bugs. The caller is responsible for cleaning up the source file after routing.
95
+ Creates a new job inheriting the processing history, metadata, and private data of the parent. Takes effect immediately: the child's datasets are copied from the parent's *current* server-side state, so any pending `createDataset()` write not yet flushed by a `sendTo*()` call is not reflected in it. See [api-job-patterns.md](api-job-patterns.md#dataset-writes-must-precede-child-job-creation) — creating a child before writing a dataset is a common source of bugs. The caller is responsible for cleaning up the source file after routing (see [api-job-patterns.md](api-job-patterns.md#temp-file-cleanup)).
96
96
 
97
97
  ## Private data
98
98
 
@@ -128,7 +128,7 @@ List all datasets attached to the job. **Immediate**: reads server state at once
128
128
  ```ts
129
129
  job.createDataset(name: string, filePath: string, model: DatasetModel): Promise<void>
130
130
  ```
131
- Attach a new dataset. **Deferred**: registers in-memory only; the file is not uploaded until the next `sendTo*()` call. Caller must clean up the source file.
131
+ Attach a new dataset. **Deferred**: registers in-memory only; the file is not uploaded until the next `sendTo*()` call. Caller must clean up the source file after routing — see [api-job-patterns.md](api-job-patterns.md#temp-file-cleanup).
132
132
 
133
133
  ```ts
134
134
  job.getDataset(name: string, accessLevel: AccessLevel): Promise<string>
@@ -51,3 +51,36 @@ job.log(LogLevel.Info, '%1', [urlThatMightContainPercent]);
51
51
  `job.fail(message, messageParams?: (string | number | boolean)[])` takes an **array**.
52
52
  `flowElement.failProcess(message, messageParam?: string | number | boolean)` takes a **single
53
53
  value**, not an array. Passing an array to `failProcess` is a common mistake.
54
+
55
+ ## Placeholders instead of concatenation
56
+
57
+ Build log messages with `%1`, `%2`, … placeholders and pass the dynamic values as
58
+ `messageParams`/`messageParam`, rather than concatenating or interpolating them into the message
59
+ string (beyond the literal-`%` escaping case above). A concatenated message can't be translated
60
+ correctly — word order and pluralization vary by language, so a translator needs the message
61
+ template and the substituted values kept separate. This matters even for a script that only ships in
62
+ English today if it might ever be localized later.
63
+
64
+ ```ts
65
+ // Avoid — can't be translated correctly, and hits the literal-% gotcha if fileName contains one
66
+ job.log(LogLevel.Info, `Processed file ${fileName} in ${seconds}s`);
67
+
68
+ // Prefer
69
+ job.log(LogLevel.Info, 'Processed file %1 in %2s', [fileName, seconds]);
70
+ ```
71
+
72
+ ## Message quality
73
+
74
+ - Log messages are user-facing text — proofread them. A message with grammatical errors or typos
75
+ looks unfinished and undermines trust.
76
+ - Log an `Error` when a custom `validateProperties`/`validateConnectionProperties` check returns
77
+ `valid: false` for a tag, and when `getLibraryForProperty`/`getLibraryForConnectionProperty` would
78
+ otherwise return an empty list — see [api-entry-points.md § Property UI callbacks](api-entry-points.md#property-ui-callbacks).
79
+ Silently returning `false`/`[]` with no log line leaves the user without any explanation.
80
+
81
+ ## Log volume
82
+
83
+ **App guideline (mandatory for apps, recommended for scripts):** don't over-log. Beyond the
84
+ [Logging in loops](#logging-in-loops) guidance, avoid `Info`/`Debug` calls that don't help a flow
85
+ operator or support engineer understand what happened — every extra message adds noise (and
86
+ translation burden, for an app) without adding signal.
@@ -48,8 +48,8 @@ open a dialog when the user clicks the editor button. All have `Type="string"` u
48
48
  | `regexp` | Regular expression string as entered |
49
49
  | `filetype` | Filename pattern(s) for the selected file type |
50
50
  | `types` (`Type="filefilter"`) | `string[]` — one pattern string per selected file type |
51
- | `askplugin` | String value selected from the library dialog (calls `getLibraryForProperty`) |
52
- | `askplugin2` | `string[]` — one string per selected library item (calls `getLibraryForMultipleProperty`) |
51
+ | `askplugin` | String value selected from the library dialog (calls `getLibraryForProperty`) — **requires the script to implement `getLibraryForProperty`** (`getLibraryForConnectionProperty` for a connection field), see [api-entry-points.md](api-entry-points.md#property-ui-callbacks) |
52
+ | `askplugin2` | `string[]` — one string per selected library item (calls `getLibraryForMultipleProperty`) — **requires a matching library-callback entry point**, same as `askplugin` above. ⚠ `getLibraryForMultipleProperty` isn't documented anywhere in [api-entry-points.md](api-entry-points.md#property-ui-callbacks), which only lists `getLibraryForProperty`/`getLibraryForConnectionProperty` — this may be the same entry point handling both single- and multi-select, or a genuinely separate, undocumented one; needs verifying against source before relying on the name here. |
53
53
  | `description` | Multi-line text as a single string (may include newlines) |
54
54
  | `scriptexp` | Result of script expression evaluated in job context, as a string |
55
55
  | `sltextwithvar` | Single-line text with variables substituted, as a string |
@@ -75,6 +75,14 @@ constant as one of the choices; all literal tokens have `Type="string"`.
75
75
  Always call `getPropertyType()` first to check whether the value is a literal before comparing the
76
76
  string.
77
77
 
78
+ Use the matching literal editor for a keyword-style value like "Default", "None", or "Automatic"
79
+ instead of making it a typed string default — e.g. `default` instead of a `Default`
80
+ attribute set to the literal text `"Default"`. Typing the keyword as an ordinary string default lets
81
+ a user accidentally change it to arbitrary text and loses the `PropertyType.Literal` signal the
82
+ script relies on. `none` is specifically the sanctioned way to let a property hold an empty value
83
+ under `Validation="Standard"` — combine it into the `Editor` chain instead of relaxing to
84
+ `Validation="None"` just to permit emptiness.
85
+
78
86
  | `Editor` token | `getPropertyStringValue()` returns |
79
87
  |---|---|
80
88
  | `default` | `"Default"` |
@@ -89,6 +97,19 @@ string.
89
97
  | `next` | `"Next"` |
90
98
  | `current` | `"Current"` |
91
99
 
100
+ `nofiles`/`allfiles`/`allotherfiles` and `nofolders`/`allfolders`/`allotherfolders` are not
101
+ general-purpose — every real use of them is a connection **include/exclude filter mask** (an
102
+ `IncludeMask`/`ExcludeMask`, or `IncludeFolderMask`/`ExcludeFolderMask`, on a `ConnectionType="Filter"`
103
+ connection). Switch Designer labels them "No/All/All other jobs" and "No/All folders" in that
104
+ context, not the XML token names above. See [Common practices](#common-practices) below for the
105
+ exact chain.
106
+
107
+ `next` and `current` exist in the editor grammar but have no confirmed script-facing use case — no
108
+ built-in Switch flow element declares a property using them, and Enfocus's own property-editor
109
+ documentation omits both (along with `allotherfolders`, apparently by oversight, since it's used the
110
+ same way as `allotherfiles`). Don't reach for `next`/`current` without confirming a concrete need
111
+ first.
112
+
92
113
  ---
93
114
 
94
115
  ## Extra XML attributes for certain editors
@@ -115,6 +136,17 @@ flow is exported.
115
136
  The following are recommended conventions for pairing property kinds with editor chains, not
116
137
  restrictions enforced by Switch — a script writer can combine editors differently if asked to.
117
138
 
139
+ Enable the dynamic editors (`sltextwithvar`/`mltextwithvar` and `conditionwithvar`, `scriptexp`)
140
+ wherever the value could plausibly vary per job — the rows below already reflect this. Conversely,
141
+ don't add them to a property whose value is inherently static (e.g. a dataset name, or anything a
142
+ `Dependency` master needs to read at flow-configuration time) — offering a variable/script option
143
+ there just invites a value the script can't sensibly use.
144
+
145
+ **App guideline (mandatory for apps, recommended for scripts):** make sure at least one editor in
146
+ the chain works without an add-on Switch module: `scriptexp` requires the Scripting Module, so never
147
+ offer it as the property's only editor — pair it with a module-free option (e.g. `inline` or
148
+ `sltextwithvar`) so the property still works for a user without that module.
149
+
118
150
  | Property kind | Recommended `Editor` chain | Why |
119
151
  |---|---|---|
120
152
  | Secret (password, token, API key) | `password` only | Masks the value and avoids leaking it via logs or variable inspection if a var/expression editor were combined. |
@@ -126,6 +158,8 @@ restrictions enforced by Switch — a script writer can combine editors differen
126
158
  | Enum (`Type="enum:..."`) | `inline` only, always | No editor guarantees a var/expression result matches one of the declared items. |
127
159
  | File/folder path that must exist for the app to work | `choosefile`/`choosefolder` only | Switch validates the picked path exists at flow start. |
128
160
  | File/folder path that legitimately varies per job | `choosefile`/`choosefolder` + `sltextwithvar` + `scriptexp` | Dynamic values only resolve when read during `jobArrived` — see the "Workaround for deferred processing" note in [api-flow-element.md](api-flow-element.md#properties) if the path is needed later in the flow. |
161
+ | Connection include-mask filter (which jobs pass through) | `allfiles;allotherfiles;types;filepatterns;regexp;scriptexp`, `Default="All Files"`, `Subtype="allfiles"` | Matches Switch's own built-in connection filter properties exactly — `Default`/`Subtype` must hold the chosen literal's exact rendered string. Folder version: `allfolders;allotherfolders;folderpatterns;regexp;scriptexp` with `Default="All Folders"`, `Subtype="allfolders"`. |
162
+ | Connection exclude-mask filter (which jobs are blocked) | `nofiles;types;filepatterns;regexp;scriptexp`, `Default="No Files"`, `Subtype="nofiles"` | Same reasoning as the include mask, starting from `nofiles` instead since there's no "exclude all/all other" case. Folder version: `nofolders;folderpatterns;regexp;scriptexp` with `Default="No Folders"`, `Subtype="nofolders"`. |
129
163
 
130
164
  `regexp`, `filetype`/`types`, `askplugin`/`askplugin2`, `oauth`, and `external` can technically be
131
165
  combined with `sltextwithvar`/`scriptexp` too, but this is uncommon in practice and not covered by a
@@ -142,3 +176,7 @@ dedicated row above.
142
176
  `getPropertyStringValue()`.
143
177
  - **External editor**: requires a separate companion application. The script receives a file path to
144
178
  the property set file. Entry point `findExternalEditorPath` resolves the editor executable path.
179
+ One of these two must be true — either a dependent property named `Application` (see
180
+ [Dependency](api-script-declaration.md#custom-property-elements-elementfields)) is non-empty, or
181
+ the script implements `findExternalEditorPath` — see
182
+ [api-entry-points.md](api-entry-points.md#property-ui-callbacks).
@@ -57,8 +57,8 @@ value is non-string (see the table); an empty string field can omit `Type`.
57
57
  | `Version` | number | Format `[1-9][0-9]*(\.(0\|[1-9][0-9]*))*`, e.g. `1`, `2.1`. Bump on every declaration change. |
58
58
  | `Keywords` | string | Space/comma/semicolon-separated. Elements pane search. |
59
59
  | `Tooltip` | string | Elements pane tooltip. |
60
- | `IncomingConnections` | `Yes` \| `No` | Add `RequireAtLeastOne="Yes"` when `Yes`. |
61
- | `OutgoingConnections` | `No` \| `One` \| `Unlimited` | Add `RequireAtLeastOne="Yes"` when `One`/`Unlimited`. Always carries `DetailedInfo=""` (or a prose description of the connections, for generated docs). |
60
+ | `IncomingConnections` | `Yes` \| `No` | Add `RequireAtLeastOne="Yes"` when `Yes`. When `Yes`, the script must implement `jobArrived` (optionally `timerFired` too); when `No`, it must implement `timerFired` instead — see [api-entry-points.md](api-entry-points.md#job-processing). |
61
+ | `OutgoingConnections` | `No` \| `One` \| `Unlimited` | Add `RequireAtLeastOne="Yes"` when `One`/`Unlimited`. Always carries `DetailedInfo=""` (or a prose description of the connections, for generated docs). When not `No`, the script must call a `sendTo*()` method at least once for any job it actually routes (a purely timer/event-driven script that never touches a job — polling an API, rotating logs, etc. — is a legitimate exception); when `Unlimited`, never use `job.sendToSingle()` — see [api-job-patterns.md § Sending jobs](api-job-patterns.md#sending-jobs). |
62
62
  | `ConnectionType` | `Move` \| `Filter` \| `TrafficLight` | Governs which `ConnectionFields` children are legal — see [ConnectionFields](#connectionfields). |
63
63
  | `FunctionsNodeJSScript` | `;`-separated names | Entry point function names found in the script. Omit the element entirely if there is no Node.js program. |
64
64
  | `ExecutionMode` | `Concurrent` \| `Serialized` | |
@@ -110,11 +110,11 @@ Key attributes:
110
110
  | `Type` | Value type discriminator — see [Type reference](#type-reference). |
111
111
  | `Editor` | `;`-joined chain of editor tokens (inline editor first, then modal editors) — see [api-property-editors.md](api-property-editors.md). |
112
112
  | `Subtype` | Which editor in the `Editor` chain is currently active for the stored value. For a single-editor property this equals that editor's token (e.g. `"inline"`); for a bare modal editor with no inline component, leave it `""`. This attribute is read by Switch's validator at runtime, not just bookkeeping — keep it consistent with `Editor`. |
113
- | `LocalizedTagName` | Display name shown in the Properties pane. |
114
- | `Tooltip` | Tooltip shown in the Properties pane. |
113
+ | `LocalizedTagName` | Display name shown in the Properties pane. **App guideline (mandatory for apps, recommended for scripts):** 30 characters max, sentence-style capitalization (`"Customer name"`, not `"Customer Name"`). |
114
+ | `Tooltip` | Tooltip shown in the Properties pane. **App guideline (mandatory for apps, recommended for scripts):** give every property a tooltip. The only exception is a property that's fully self-explanatory from its name alone with no extra constraints (e.g. "Number of copies" with no min/max) — if there's anything a user would need to know (units, valid range, format, an example value), put it in the tooltip. |
115
115
  | `DetailedInfo` | Long-form description for generated docs. Always include the attribute (empty string `""` is fine) — omitting it makes the property non-editable in Switch Designer. |
116
- | `Validation` | `None` (skip validation) \| `Standard` (built-in validator) \| `Custom` (needs an `isPropertyValid` entry point) \| `Standard and custom` (both, custom only runs if standard passes). |
117
- | `Default` | Default value string. Omit only if the value is emitted as CDATA instead. |
116
+ | `Validation` | `None` (skip validation) \| `Standard` (built-in validator) \| `Custom` (needs a `validateProperties` entry point) \| `Standard and custom` (both, custom only runs if standard passes). If any property uses `Custom` or `Standard and custom`, the script must implement `validateProperties` (or `validateConnectionProperties` for a connection field) — see [api-entry-points.md](api-entry-points.md#property-ui-callbacks). To let a property hold an empty value under `Standard` validation, add the `none` literal editor to the `Editor` chain (see [api-property-editors.md § Literal editors](api-property-editors.md#literal-editors)) instead of relaxing to `Validation="None"` just to permit emptiness. |
117
+ | `Default` | Default value string. Omit only if the value is emitted as CDATA instead. **App guideline (mandatory for apps, recommended for scripts):** always provide a default unless there's genuinely no sensible one; if a default truly isn't possible, give an example value in the `Tooltip` instead. |
118
118
  | `Dependency` | Tag of the master property this one depends on. Written flat (not nested) — the dependent element is a sibling of its master, not a child. |
119
119
  | `DependencyCondition` | One of: `Not-empty`, `Equals`, `Not-equals`, `Contains`, `Does not contain`, `Matches`, `Does not match`, `Starts with`, `Does not start with`. |
120
120
  | `Dependencyvalue` | Value(s) to compare against — `;`-separated for multiple. Note the lowercase `v`. |
@@ -131,6 +131,24 @@ Key attributes:
131
131
  Validation="Standard" Default=""></OutputFolder>
132
132
  ```
133
133
 
134
+ **Runtime pitfall:** when a dependent property is hidden (its master's current value doesn't satisfy
135
+ `DependencyCondition`/`Dependencyvalue`), Switch treats the tag as if it doesn't exist. Calling
136
+ `flowElement.getPropertyStringValue(tag)` on it throws `"Invalid tag: <tag>"` — see
137
+ [api-flow-element.md](api-flow-element.md#properties). Only fetch a dependent property once you know
138
+ its condition holds, either by checking the already-read master value yourself, or by calling
139
+ `flowElement.hasProperty(tag)` first (it returns `false` for a hidden dependent). This matters most
140
+ for an enum/drop-down master with several dependents keyed off different values — fetch only the
141
+ ones actually shown for the current selection, not all of them.
142
+
143
+ A script must never assume the set of fetchable dependents is fixed once at flow configuration
144
+ time: if the master's own value is dynamic (a Switch variable or script expression, resolved per
145
+ job in `jobArrived`), which dependents are shown can differ from job to job in the same flow
146
+ execution. Re-check the condition (or call `hasProperty()`) on every invocation rather than caching
147
+ an earlier "shown" determination. There is no way to read a currently-hidden dependent's value for
148
+ a given job — if a script genuinely needs data from more than one dependency group at once (e.g.
149
+ values entered while the master previously pointed elsewhere), this pattern cannot provide it, and
150
+ a different property structure (not `Dependency`-based hiding) is needed instead.
151
+
134
152
  Attribute order in the file doesn't matter functionally — SwitchScripter's own output happens to be
135
153
  alphabetical (QDom sorts attributes on write), but any order parses correctly.
136
154
 
@@ -172,6 +190,11 @@ most important thing to get right when hand-editing this section:
172
190
 
173
191
  Custom (author-defined) connection properties may be added regardless of `ConnectionType`.
174
192
 
193
+ The declared connections and the script's `sendTo*()` calls must agree with each other — see
194
+ [api-job-patterns.md § Sending jobs](api-job-patterns.md#sending-jobs) for the exact rules per
195
+ `ConnectionType`. In particular, omit a `Success`/`Warning`/`Error` element entirely (as shown
196
+ above) if the script never routes to it, rather than leaving an unused connection declared.
197
+
175
198
  **Caution — package-swap connection preservation:** Switch Designer decides whether existing flow
176
199
  connections survive a script package replace by comparing the serialised `ConnectionFields` and
177
200
  `IncomingConnections` text **byte-for-byte** between old and new declarations, and additionally
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Switch script project lives in a **script folder** during development and is distributed as a `.sscript` package (ZIP).
4
4
 
5
- > **Agent editing policy**: Edit TypeScript/JavaScript source files (`main.ts`, `main.js`) and the XML declaration (`<ScriptID>.xml`) freely — see [api-script-declaration.md](api-script-declaration.md) for the declaration's rules. Leave `manifest.xml` to the user unless explicitly asked.
5
+ > **Agent editing policy**: Edit TypeScript/JavaScript source files (`main.ts`, `main.js`) and the XML declaration (`<ScriptID>.xml`) freely — see [api-script-declaration.md](api-script-declaration.md) for the declaration's rules, and [api-entry-points.md § Entry-point scanner constraints](api-entry-points.md#entry-point-scanner-constraints) before writing any string/regex literal or division, since certain shapes make Switch's entry-point scanner silently drop functions. Leave `manifest.xml` to the user unless explicitly asked.
6
6
 
7
7
  ## Script folder files
8
8
 
@@ -17,7 +17,7 @@ A Switch script project lives in a **script folder** during development and is d
17
17
  | `tsconfig.json` | Yes (TypeScript) | TypeScript transpilation options. |
18
18
  | `<IconFileName>.png` | No | 32×32 px, RGB, not interlaced. Extension must be `.png`. |
19
19
  | `node_modules/` | No | Local npm packages. |
20
- | `Resources/` | No | Extra resource files bundled when packing with SwitchScriptTool. |
20
+ | `Resources/` | No | Extra resource files bundled when packing with SwitchScriptTool. Never bundle an Oracle Java Runtime Environment here — licensing terms don't permit redistributing it, in a script or an app. |
21
21
  | `<LanguageCode>.ts` | No | Translation files (Switch App SDK). |
22
22
  | `.vscode/launch.json` | No | Debug config (created by SwitchScriptTool). |
23
23
  | `.vscode/switch.code-snippets` | No | Entry point snippets (TypeScript only). |
@@ -72,6 +72,31 @@ A Switch script project lives in a **script folder** during development and is d
72
72
  | Distribution | Freely | Enfocus Appstore only |
73
73
  | Position in pane | — | Configured via `PositionInElementPane` in XML |
74
74
 
75
+ ## App Store submission guidelines
76
+
77
+ The following apply **only** to `ScriptPackageType="App"` (Appstore submission) — not to a plain
78
+ `Script` package — beyond the general property/entry-point/logging conventions covered elsewhere in
79
+ these docs:
80
+
81
+ - **Prefer more than one language.** An app is preferably localized beyond English — see
82
+ [Translation files](#script-folder-files) (`<LanguageCode>.ts`) and
83
+ `SwitchScriptTool --generate-translations` in [api-tooling.md](api-tooling.md).
84
+ - **Translations must be complete, and logging must go through the translation mechanism.** A
85
+ partially-translated app (some strings translated, others not) is not acceptable — every
86
+ user-facing message needs an entry in each shipped language file, and log calls must go through
87
+ that mechanism (see [api-switch.md](api-switch.md) for `Switch.translate`) rather than
88
+ hardcoding English text that bypasses it.
89
+ - **macOS Mach-O binaries in extra files (`Resources/`) must be universal.** Any bundled macOS
90
+ application, framework bundle, executable, or dynamic library must include both `x86_64` and
91
+ `arm64` architectures.
92
+ - **macOS Mach-O binaries in extra files must be signed.** An unsigned executable or dynamic
93
+ library bundled as an extra file will fail Appstore review.
94
+ - **Extra files must not change at runtime.** Don't place files the script might modify or
95
+ regenerate at runtime inside the app's own package folder — Switch validates the app package's
96
+ signature, and content that changes after signing fails that validation, causing Switch to remove
97
+ the app. Write runtime-mutable data (temp files, caches, generated output) outside the app
98
+ folder — see [api-job-patterns.md § Temp file cleanup](api-job-patterns.md#temp-file-cleanup).
99
+
75
100
  ## Execution modes
76
101
 
77
102
  Set in the XML declaration via SwitchScripter. Applies to all instances of the script.
@@ -40,6 +40,83 @@ location for the current OS if the bare command isn't found.
40
40
 
41
41
  **Unpack** prints package metadata (`Type`, `Protection`, `Status`) before extracting. For a TypeScript-sourced package it extracts only `main.ts` — the compiled `main.js`/`main.js.map` are not written back out, so a pack → unpack round trip returns a clean, editable script folder rather than a compiled snapshot.
42
42
 
43
+ ## Verify entry points before packing
44
+
45
+ Switch's entry-point scanner is regex-based, not a real parser, and can silently drop entry-point
46
+ functions from certain string/regex/division shapes — see
47
+ [api-entry-points.md § Entry-point scanner constraints](api-entry-points.md#entry-point-scanner-constraints).
48
+ `--pack` does not validate or warn about this: a script that packs cleanly can still fail to load,
49
+ or load with a property callback that silently never runs. Compiling and passing tests doesn't
50
+ catch it either, since the source is syntactically valid JS/TS — only extraction against the built
51
+ `main.js` does.
52
+
53
+ After any edit to `main.ts`/`main.js`, run the extracted entry points against the **built**
54
+ `main.js` and confirm the expected names come back. Python:
55
+
56
+ ```python
57
+ #!/usr/bin/env python3
58
+ import re, sys
59
+
60
+ STRIP_PATTERN = re.compile(
61
+ r'(?:/\*.*?\*/)'
62
+ r'|(?:(["\'`])\1)'
63
+ r'|(?://.*?$)'
64
+ r'|(?:([/"\'`]).*?[^\\]\2)'
65
+ r'|(?:/(?:\\.|[^/\n])*/)'
66
+ r'|(?:\(\s*\w+\s*/\s*\w+\s*\))'
67
+ r'|(?:\w+\s*/\s*\w+)',
68
+ re.DOTALL | re.MULTILINE,
69
+ )
70
+ FUNC_PATTERN = re.compile(r'function[\s\n\r]+(\$?\w+)[\s\n\r]*\(', re.MULTILINE)
71
+ NODE_ENTRY_POINTS = {
72
+ 'timerFired', 'jobArrived', 'getLibraryForProperty', 'getLibraryForConnectionProperty',
73
+ 'findExternalEditorPath', 'validateProperties', 'validateConnectionProperties',
74
+ 'httpRequestTriggeredSync', 'httpRequestTriggeredAsync', 'flowStopTriggered',
75
+ 'flowStartTriggered', 'abort',
76
+ }
77
+ REQUIRED_ANY = {'jobArrived', 'timerFired', 'flowStopTriggered', 'httpRequestTriggeredAsync'}
78
+
79
+ code = open(sys.argv[1], encoding='utf-8').read()
80
+ stripped = STRIP_PATTERN.sub('', code)
81
+ found = [m.group(1) for m in FUNC_PATTERN.finditer(stripped) if m.group(1) in NODE_ENTRY_POINTS]
82
+ print('Entry points found:', found or '(none)')
83
+ if not (REQUIRED_ANY & set(found)):
84
+ sys.exit('FAIL: none of ' + str(sorted(REQUIRED_ANY)) + ' survived extraction.')
85
+ ```
86
+
87
+ Or Node.js:
88
+
89
+ ```js
90
+ #!/usr/bin/env node
91
+ const fs = require('fs');
92
+
93
+ const STRIP_PATTERN = /(?:\/\*[\s\S]*?\*\/)|(?:(["'`])\1)|(?:\/\/.*?$)|(?:([/"'`])[\s\S]*?[^\\]\2)|(?:\/(?:\\.|[^/\n])*\/)|(?:\(\s*\w+\s*\/\s*\w+\s*\))|(?:\w+\s*\/\s*\w+)/gm;
94
+ const FUNC_PATTERN = /function[\s\n\r]+(\$?\w+)[\s\n\r]*\(/gm;
95
+ const NODE_ENTRY_POINTS = new Set([
96
+ 'timerFired', 'jobArrived', 'getLibraryForProperty', 'getLibraryForConnectionProperty',
97
+ 'findExternalEditorPath', 'validateProperties', 'validateConnectionProperties',
98
+ 'httpRequestTriggeredSync', 'httpRequestTriggeredAsync', 'flowStopTriggered',
99
+ 'flowStartTriggered', 'abort',
100
+ ]);
101
+ const REQUIRED_ANY = ['jobArrived', 'timerFired', 'flowStopTriggered', 'httpRequestTriggeredAsync'];
102
+
103
+ const stripped = fs.readFileSync(process.argv[2], 'utf8').replace(STRIP_PATTERN, '');
104
+ const found = [];
105
+ let m;
106
+ while ((m = FUNC_PATTERN.exec(stripped)) !== null) {
107
+ if (NODE_ENTRY_POINTS.has(m[1])) found.push(m[1]);
108
+ }
109
+ console.log('Entry points found:', found.length ? found : '(none)');
110
+ if (!found.some((f) => REQUIRED_ANY.includes(f))) {
111
+ console.error('FAIL: none of', REQUIRED_ANY, 'survived extraction.');
112
+ process.exit(1);
113
+ }
114
+ ```
115
+
116
+ If the required entry point is missing, dump `stripped`/the stripped output and diff it against
117
+ `main.js` — the first swallowed region starts at the string, regex, or division literal identified
118
+ in the rules above.
119
+
43
120
  ## Deployment
44
121
 
45
122
  Before packing, remove dev dependencies to reduce package size — this matters because `--pack` bundles `node_modules` as-is, and the scaffolded `package.json` puts type-only packages (`@types/node`, `@types/switch-scripting`, `undici-types`) under `devDependencies`:
@@ -11,7 +11,7 @@ Detailed API docs are in `docs/switch-api/`:
11
11
 
12
12
  | File | Contents | Load when |
13
13
  |---|---|---|
14
- | `switch-api/api-entry-points.md` | All entry point signatures, constraints, and when each is called | Scaffolding a new script or adding an entry point |
14
+ | `switch-api/api-entry-points.md` | All entry point signatures, constraints, and when each is called | Scaffolding a script, adding/editing an entry point, or making any edit to main.ts/main.js |
15
15
  | `switch-api/api-switch.md` | `Switch` (`s`) — global data, webhooks, abort, server utilities | Using global data, webhooks, abort, or server settings |
16
16
  | `switch-api/api-flow-element.md` | `FlowElement` — properties, connections, job creation, logging | Reading properties, creating jobs, logging, connections |
17
17
  | `switch-api/api-job.md` | `Job` — routing, file access, child jobs, private data, datasets | Processing/routing jobs, file access, private data, datasets |
@@ -29,6 +29,7 @@ Detailed API docs are in `docs/switch-api/`:
29
29
  | `switch-api/api-execution-environment.md` | Process/concurrency model, state persistence across job invocations, unhandled-rejection behavior, npm/native module constraints | Relying on module-level state across jobs, handling errors/rejections, or using third-party npm packages |
30
30
  | `switch-api/api-logging.md` | Log level semantics, logging practice, `console.log` limitation, common gotchas | Adding or reviewing `job.log()`/`flowElement.log()`/`job.fail()`/`failProcess()` calls |
31
31
  | `switch-api/api-logs-and-dataroot.md` | Locating the Application Data Root, querying `ServerLogs.db3` directly | Diagnosing or validating a script from its actual log output rather than by reading the code |
32
+ | `switch-api/api-app-guidelines.md` | Pre-publish checklist for Appstore apps: property naming/tooltips/editors, entry point and sendTo consistency, logging quality, packaging | Preparing a script for publication as an app, or reviewing one against Appstore submission criteria |
32
33
 
33
34
  ## Key rules
34
35
  - Always consult the API reference files above before writing or modifying script code.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enfocussw/switch-scripting-context",
3
- "version": "25.11.0-beta.10",
3
+ "version": "25.11.0-beta.11",
4
4
  "description": "AI coding assistant context for Enfocus Switch scripting (Node.js/TypeScript)",
5
5
  "keywords": [
6
6
  "switch",
@@ -40,7 +40,7 @@
40
40
  "license": "ISC",
41
41
  "repository": {
42
42
  "type": "git",
43
- "url": "git+https://github.com/samw_pacol/enf-switch-scripting-context.git"
43
+ "url": "git+https://github.com/esko-bv/enf-switch-scripting-context.git"
44
44
  },
45
45
  "publishConfig": {
46
46
  "registry": "https://registry.npmjs.org/",
@@ -50,9 +50,9 @@
50
50
  "@types/node": "^20.0.0",
51
51
  "typescript": "^5.0.0"
52
52
  },
53
- "homepage": "https://github.com/samw_pacol/enf-switch-scripting-context#readme",
53
+ "homepage": "https://github.com/esko-bv/enf-switch-scripting-context#readme",
54
54
  "bugs": {
55
- "url": "https://github.com/samw_pacol/enf-switch-scripting-context/issues"
55
+ "url": "https://github.com/esko-bv/enf-switch-scripting-context/issues"
56
56
  },
57
57
  "engines": {
58
58
  "node": ">=18"