@enfocussw/switch-scripting-context 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (33) hide show
  1. package/CHANGELOG.md +504 -0
  2. package/README.md +192 -0
  3. package/bin/cli.js +8 -0
  4. package/dist/init.d.ts +78 -0
  5. package/dist/init.js +894 -0
  6. package/docs/switch-api/api-versions.md +127 -0
  7. package/docs/switch-api/connection.md +82 -0
  8. package/docs/switch-api/document-classes.md +189 -0
  9. package/docs/switch-api/entry-points.md +185 -0
  10. package/docs/switch-api/enums.md +181 -0
  11. package/docs/switch-api/execution-environment.md +143 -0
  12. package/docs/switch-api/flow-element.md +143 -0
  13. package/docs/switch-api/http.md +96 -0
  14. package/docs/switch-api/job-patterns.md +238 -0
  15. package/docs/switch-api/job.md +187 -0
  16. package/docs/switch-api/logging.md +117 -0
  17. package/docs/switch-api/switch.md +210 -0
  18. package/docs/switch-appstore/app-guidelines.md +281 -0
  19. package/docs/switch-appstore/app-manual.md +84 -0
  20. package/docs/switch-appstore/app-store-listing.md +69 -0
  21. package/docs/switch-appstore/app-store-submission.md +83 -0
  22. package/docs/switch-project/debugging.md +61 -0
  23. package/docs/switch-project/logs-and-dataroot.md +80 -0
  24. package/docs/switch-project/node-versions.md +87 -0
  25. package/docs/switch-project/project-planning.md +149 -0
  26. package/docs/switch-project/property-documentation.md +75 -0
  27. package/docs/switch-project/property-editors.md +249 -0
  28. package/docs/switch-project/script-declaration.md +407 -0
  29. package/docs/switch-project/script-structure.md +157 -0
  30. package/docs/switch-project/tooling.md +165 -0
  31. package/docs/switch-project/vscode.md +90 -0
  32. package/docs/switch-scripting.md +70 -0
  33. package/package.json +65 -0
@@ -0,0 +1,210 @@
1
+ ---
2
+ id: switch
3
+ category: switch-api
4
+ order: 3
5
+ summary: "`Switch` (`s`): global data, webhooks, abort, server utilities"
6
+ triggers:
7
+ - "Using global data, webhooks, abort, or server settings"
8
+ ---
9
+
10
+ # Switch Class
11
+
12
+ The `Switch` instance `s` is passed to every entry point. It provides global data storage, webhook subscription, abort handling, and server utilities.
13
+
14
+ <!-- docs-index:contents begin -->
15
+ ## Contents
16
+
17
+ - Global data
18
+ - Locking
19
+ - Webhooks
20
+ - Abort
21
+ - Server utilities
22
+ - Translation extraction rules
23
+ <!-- docs-index:contents end -->
24
+
25
+ ## Global data
26
+
27
+ Global data is key/value storage shared across entry point invocations, scoped by `Scope`.
28
+
29
+ | Scope | Shared across |
30
+ |---|---|
31
+ | `Scope.FlowElement` | This script element instance only |
32
+ | `Scope.FlowElements` | All instances of the same script in the same flow (Switch 22.0+) |
33
+ | `Scope.Element` | All instances of the same script across all flows |
34
+ | `Scope.Flow` | All elements in the same flow (Switch 22.0+) |
35
+ | `Scope.Global` | Any element in any flow — use sparingly; prefix tags with your company name |
36
+
37
+ Dates stored via `setGlobalData` are returned as strings by `getGlobalData` — parse them in your script. Before Switch 21.0, store string values only (see [api-versions.md](api-versions.md#declaration-changes-to-existing-methods)).
38
+
39
+ ```ts
40
+ s.getGlobalData(scope: Scope, tag: string, lock?: boolean): Promise<any>
41
+ s.getGlobalData(scope: Scope, tags: string[], lock?: boolean): Promise<{ tag: string, value: any }[]>
42
+ ```
43
+ Read one or multiple values. Returns `''` (empty string) for a tag that doesn't exist, not `undefined`. Optional `lock` (default `false`) locks the requested tags for this entry point invocation. Other invocations' reads, writes and locks on those tags wait until it is released. One invocation can hold only one lock at a time. See [Locking](#locking) for what releases a lock and for the one-lock rule.
44
+
45
+ More than 100 global data calls (`getGlobalData`/`setGlobalData` combined) in a single entry point invocation logs a warning — this is an advisory client-side counter, not an enforced server-side limit, but a sign the script should batch reads/writes. The warning text starts "The script exceeded the maximum of 100 global data operations from within the same entry point execution. This may affect Switch performance."
46
+
47
+ ```ts
48
+ s.setGlobalData(scope: Scope, tag: string, value: any): Promise<void>
49
+ s.setGlobalData(scope: Scope, globalData: { tag: string, value: any }[]): Promise<void>
50
+ ```
51
+ Write one or multiple values. Releases this invocation's lock on every tag it writes.
52
+
53
+ ```ts
54
+ s.removeGlobalData(scope: Scope, tag: string): Promise<void>
55
+ s.removeGlobalData(scope: Scope, tags: string[]): Promise<void>
56
+ ```
57
+ Delete one or multiple global data entries. Releases this invocation's lock on every tag it removes.
58
+
59
+ > **Global data is never removed automatically.** Entries persist in Switch's database until explicitly deleted with `removeGlobalData`. Scripts that skip cleanup accumulate entries indefinitely and can fill the database. Always remove global data as soon as it is no longer needed.
60
+
61
+ ### Locking
62
+
63
+ A lock belongs to one entry point invocation. `getGlobalData(scope, tags, true)` takes it. It ends
64
+ when that invocation writes the tag with `setGlobalData`, removes it with `removeGlobalData`, or
65
+ when the entry point ends, including when it throws or hits its abort timeout.
66
+
67
+ While one invocation holds a lock on a tag, other invocations behave like this:
68
+
69
+ - A plain `getGlobalData` of the tag waits until the lock is released, then returns the value
70
+ written at release. It does not throw and does not return the old value.
71
+ - A locked `getGlobalData` of the tag waits, then takes the lock. It does not throw.
72
+ - `setGlobalData` of the tag waits, then writes, overwriting what the holder wrote at release.
73
+
74
+ The holding invocation itself can read its locked tags normally.
75
+
76
+ The server checks again every 200 ms and has no timeout of its own. If the waiting invocation hits
77
+ its abort timeout, Switch aborts it as usual: the wait ends, and any tags it already holds from an
78
+ array lock are released.
79
+
80
+ **Gotcha: one lock per invocation.** While an invocation holds or is waiting for a lock, every
81
+ further `getGlobalData(..., true)` in that invocation throws `Token is already locked` within a few
82
+ milliseconds. That includes a tag nobody holds and the tag it already holds. After it releases the
83
+ lock, it can lock again. The rule applies across scopes too, and a `setGlobalData` that
84
+ writes only some of the locked tags leaves the others locked, so the next lock still throws.
85
+
86
+ `Token is already locked` therefore always comes from the invocation's own earlier lock; contention
87
+ with another invocation waits instead of throwing. Retrying on this error can't succeed. Fix the
88
+ code path that takes the second lock.
89
+
90
+ **Locking several tags.** The array form is the only way to hold more than one tag at once:
91
+
92
+ - `getGlobalData(scope, [a, b], true)` locks every listed tag.
93
+ - `setGlobalData(scope, [{ tag: a, value: ... }, { tag: b, value: ... }])` releases every listed
94
+ tag.
95
+
96
+ **Caveat: an array lock is not all-or-nothing.** If one of the tags is locked elsewhere, the call
97
+ keeps the tags it already has while it waits for the rest, and other invocations wait on those too.
98
+ The tags are taken in alphabetical order whatever order they are listed in, so two
99
+ array locks over the same tags can't deadlock each other.
100
+
101
+ **Caveat: deadlock risk.** A waiting array lock keeps what it has, and plain
102
+ reads and writes wait for locks. If invocation X locks `[a, b]`, gets `a` and waits for `b`, while
103
+ invocation Y holds `b` and then reads or writes `a`, each waits for the other until one of them
104
+ hits its abort timeout. To avoid it, don't read or write any tag other than the ones you locked
105
+ while you hold a lock.
106
+
107
+ Rules for a read-modify-write of shared tags:
108
+
109
+ 1. Lock every tag you will change in one `getGlobalData` call.
110
+ 2. Change the values and write them straight away, in one `setGlobalData` call covering every
111
+ locked tag.
112
+ 3. Release on every exit path. If the update fails, write the original values back (or remove the
113
+ tags) in a `catch` or `finally`, rather than leaving other invocations waiting until the entry
114
+ point ends.
115
+ 4. Don't hold a lock across job sends, file I/O, or network calls. Every other invocation's plain
116
+ read of the tag waits for as long as you hold it.
117
+
118
+ See [job-patterns.md § Shared state in global data](job-patterns.md#shared-state-in-global-data)
119
+ for the job-registration pattern written this way.
120
+
121
+ ## Webhooks
122
+
123
+ Both methods need Switch 21.1+.
124
+
125
+ ```ts
126
+ s.httpRequestSubscribe(method: HttpRequest.Method, path: string, args: any[]): Promise<void>
127
+ ```
128
+ Subscribe to incoming HTTP requests. Call inside `flowStartTriggered`. The absolute URL is `https://<host>:51088/scripting/${path}`. `path` must start with `/` (e.g. `/my-path`) or the call throws `"Wrong format of the subscription path."`. The `args` array is forwarded to the `httpRequestTriggeredSync` / `httpRequestTriggeredAsync` entry points.
129
+
130
+ ```ts
131
+ s.httpRequestUnsubscribe(method: HttpRequest.Method, path: string): Promise<void>
132
+ ```
133
+ Unsubscribe a previously registered webhook.
134
+
135
+ ## Abort
136
+
137
+ ```ts
138
+ // Switch 22.1+
139
+ s.setAbortData(abortData: any): void
140
+ ```
141
+ Store a value that will be passed to the `abort` entry point when the configured abort timeout expires.
142
+
143
+ ## Server utilities
144
+
145
+ ```ts
146
+ // Switch 24.0+
147
+ s.getPreferenceSetting(settingKey: string): Promise<any>
148
+ ```
149
+ Retrieve a Switch preference setting. `settingKey` format: `"settingGroup"` or `"settingGroup/settingName"`. Returns `''` if the setting/group doesn't exist. Any field whose name contains `password`, `pwd`, or `secret` is redacted in the result. The returned value is JSON-stringified, not a raw object.
150
+
151
+ ```ts
152
+ // Switch 24.0+
153
+ s.getServerVersion(): number
154
+ ```
155
+ Returns the Switch server version as `majorVersion + updateNumber/100` (e.g. Switch 25.11 → `25.11`), not a literal version string — don't parse it as one. The method doesn't exist before Switch 24.0, so it can't detect older versions; see [api-versions.md § Checking at runtime](api-versions.md#checking-at-runtime).
156
+
157
+ ```ts
158
+ // Switch 22.0+
159
+ Switch.tr(str: string): string
160
+ ```
161
+ Static method. Marks a string literal for translation (used by SwitchScriptTool). Returns the string unchanged at runtime.
162
+
163
+ ### Translation extraction rules
164
+
165
+ `SwitchScriptTool --generate-translations` extracts strings by **static analysis of the source
166
+ text**, not by running it. It only ever sees a `Switch.tr(...)` call whose argument is a string
167
+ literal, spelled exactly like that. Anything it can't resolve statically is silently skipped — no
168
+ error, no warning, just a missing entry in the `.ts` file. Always check the generated `.ts` for the
169
+ strings you expected.
170
+
171
+ What extraction handles:
172
+
173
+ ```ts
174
+ Switch.tr('single'); // single, double and backtick quotes all work
175
+ Switch.tr(`multi
176
+ line`); // multi-line literals work
177
+ Switch.tr('one ' + 'two '); // concatenation of literals works
178
+ // The tr() call doesn't have to sit at the point of use:
179
+ const s = Switch.tr('Predefined string');
180
+ await job.log(LogLevel.Info, s); // extracted, and translated at runtime
181
+ ```
182
+
183
+ What it does **not** handle:
184
+
185
+ ```ts
186
+ const t = Switch.tr; // aliasing the function: never extracted
187
+ await job.log(LogLevel.Info, t('nope'));
188
+
189
+ const token = getToken();
190
+ Switch.tr('prefix ' + token); // concatenation with a non-literal
191
+ Switch.tr(`prefix ${token}`); // template interpolation
192
+ Switch.tr('Job ID = ' + job.getId());
193
+ ```
194
+
195
+ For dynamic values, mark the template and pass the values as `messageParams` — the template gets
196
+ translated, the substituted values don't:
197
+
198
+ ```ts
199
+ await job.log(LogLevel.Info, Switch.tr('Job ID = %1'), [job.getId()]);
200
+ ```
201
+
202
+ Arguments can themselves be translated by wrapping them separately:
203
+
204
+ ```ts
205
+ const mode = readOnly ? Switch.tr('Read-only') : Switch.tr('Regular');
206
+ await job.log(LogLevel.Info, Switch.tr('Job ID = %1, Mode = %2'), [job.getId(), mode]);
207
+ ```
208
+
209
+ See [logging.md § Placeholders instead of concatenation](logging.md#placeholders-instead-of-concatenation)
210
+ for the same rule stated from the logging side.
@@ -0,0 +1,281 @@
1
+ ---
2
+ id: app-guidelines
3
+ category: switch-appstore
4
+ order: 20
5
+ summary: "Pre-publish checklist for Appstore apps: property naming/tooltips/editors, entry point and sendTo consistency, logging quality, packaging, and the app-only submission rules (localization, signed universal macOS binaries)"
6
+ triggers:
7
+ - "Preparing a script for publication as an app, or reviewing one against Appstore submission criteria"
8
+ ---
9
+
10
+ # App Publishing Guidelines
11
+
12
+ A pre-publish checklist for a script intended for the Enfocus Appstore (`ScriptPackageType="App"`
13
+ — see [script-structure.md § Script vs App](../switch-project/script-structure.md#script-vs-app)). Each item
14
+ here is enforced in more detail at the linked section — this page exists to be read top-to-bottom
15
+ right before submission, not as the primary source for any individual rule. The one exception is
16
+ [App-only](#app-only) at the bottom, which is documented in full here.
17
+
18
+ Items fall into three tiers, marked on each one:
19
+
20
+ - **Universal** — a correctness rule for every script, app or not. Violating it is a bug, not a
21
+ style choice.
22
+ - **Apps required / scripts recommended** — mandatory for Appstore review; worth following in a
23
+ plain `Script` too, but not enforced there.
24
+ - **Apps only** — doesn't apply to a plain `Script` package at all.
25
+
26
+ ---
27
+
28
+ <!-- docs-index:contents begin -->
29
+ ## Contents
30
+
31
+ - Properties
32
+ - Entry points
33
+ - Sending jobs
34
+ - Logging
35
+ - Temp files and paths
36
+ - App-only
37
+ - Identity and versioning
38
+ - Top-level declaration properties
39
+ - Password protection
40
+ - Localization
41
+ - Icon
42
+ - Extra files
43
+ - Source and review hygiene
44
+ - What review does and doesn't cover
45
+ <!-- docs-index:contents end -->
46
+
47
+ ## Properties
48
+
49
+ - [ ] **Apps required / scripts recommended.** Every property name (`LocalizedTagName`) is 30
50
+ characters or fewer and uses sentence-style capitalization ("Customer name", not "Customer
51
+ Name") — see [script-declaration.md](../switch-project/script-declaration.md#custom-property-elements-elementfields).
52
+ - [ ] **Apps required / scripts recommended.** Every property has a `Tooltip`, unless it's fully
53
+ self-explanatory with no extra constraints (e.g. "Number of copies" with no min/max) — see
54
+ [script-declaration.md](../switch-project/script-declaration.md#custom-property-elements-elementfields) for
55
+ the attribute and [property-documentation.md](../switch-project/property-documentation.md) for what to write in it.
56
+ - [ ] **Universal.** Dynamic editors (`sltextwithvar`/`mltextwithvar`, `conditionwithvar`,
57
+ `scriptexp`) are enabled wherever the value could plausibly vary per job, and omitted where the
58
+ value is inherently static (e.g. a dataset name) — see
59
+ [property-editors.md § Common practices](../switch-project/property-editors.md#common-practices).
60
+ - [ ] **Universal.** Keyword-style default values ("Default", "None", "Automatic", …) use the
61
+ matching literal editor (`default`, `none`, `automatic`, …), not a typed string default — see
62
+ [property-editors.md § Literal editors](../switch-project/property-editors.md#literal-editors).
63
+ - [ ] **Universal.** Every property using `askplugin`/`askplugin2` ("Select from library"/"Select
64
+ many from library") has a matching `getLibraryForProperty`/`getLibraryForConnectionProperty`
65
+ entry point — see [entry-points.md § Property UI callbacks](../switch-api/entry-points.md#property-ui-callbacks).
66
+ - [ ] **Apps required / scripts recommended.** Every property's editor chain includes at least one
67
+ editor that doesn't require an add-on Switch module — e.g. never offer `scriptexp` (Scripting
68
+ Module) as the only editor — see
69
+ [property-editors.md § Common practices](../switch-project/property-editors.md#common-practices).
70
+ - [ ] **Universal.** Every property using the `external`/`external2`/… editor has either a
71
+ non-empty dependent `Application` property or a `findExternalEditorPath` entry point — see
72
+ [property-editors.md § Notes](../switch-project/property-editors.md#notes).
73
+ - [ ] **Apps required / scripts recommended.** Every property has a default value unless one
74
+ genuinely isn't possible, in which case the `Tooltip` gives an example value instead — see
75
+ [script-declaration.md](../switch-project/script-declaration.md#custom-property-elements-elementfields).
76
+ - [ ] **Universal.** Every property/connection field declaring `Validation="Custom"` or `"Standard
77
+ and custom"` has a matching `validateProperties`/`validateConnectionProperties` entry point —
78
+ see [entry-points.md § Property UI callbacks](../switch-api/entry-points.md#property-ui-callbacks).
79
+
80
+ ## Entry points
81
+
82
+ - [ ] **Universal.** `jobArrived` is present when `IncomingConnections="Yes"`; `timerFired` is
83
+ present when `IncomingConnections="No"` (and may additionally be present alongside `jobArrived`)
84
+ — see [entry-points.md § Job processing](../switch-api/entry-points.md#job-processing).
85
+ - [ ] **Universal.** No empty `jobArrived` or `timerFired` — an entry point that does nothing is
86
+ removed entirely, not left in place — see
87
+ [entry-points.md § Job processing](../switch-api/entry-points.md#job-processing).
88
+
89
+ ## Sending jobs
90
+
91
+ All **universal** — see
92
+ [job-patterns.md § Sending jobs](../switch-api/job-patterns.md#sending-jobs) and
93
+ [script-declaration.md § ConnectionFields](../switch-project/script-declaration.md#connectionfields) for full
94
+ detail.
95
+
96
+ - [ ] If `OutgoingConnections` is not `No`, some `sendTo*()` method is called at least once for
97
+ any job actually routed (a purely timer/event-driven script that never touches a job is a
98
+ legitimate exception).
99
+ - [ ] If `OutgoingConnections="Unlimited"`, `job.sendToSingle()` is never used.
100
+ - [ ] `ConnectionType="Move"` never uses `job.sendToData()`/`job.sendToLog()`.
101
+ - [ ] `ConnectionType="TrafficLight"` uses only `job.sendToData()`/`job.sendToLog()`; `sendToData`
102
+ is used when a Data Success/Warning/Error connection is enabled, `sendToLog` when the
103
+ corresponding Log connection is enabled.
104
+ - [ ] An unused Success/Warning/Error connection is omitted from `ConnectionFields` entirely, not
105
+ left declared but unrouted.
106
+ - [ ] A job with no output calls `job.sendToNull()`.
107
+ - [ ] `job.fail()`/`flowElement.failProcess()` is used for genuine processing errors; an error
108
+ `TrafficLight` connection isn't used as a substitute for `fail()`.
109
+
110
+ ## Logging
111
+
112
+ - [ ] **Universal.** An `Error` is logged when a custom
113
+ `validateProperties`/`validateConnectionProperties` check returns `valid: false` for a tag, and
114
+ when `getLibraryForProperty`/`getLibraryForConnectionProperty` would otherwise return an empty
115
+ list — see [logging.md § Message quality](../switch-api/logging.md#message-quality).
116
+ - [ ] **Universal.** Log messages are free of grammatical errors and typos — see
117
+ [logging.md § Message quality](../switch-api/logging.md#message-quality).
118
+ - [ ] **Universal.** Log messages use `%1`/`%2`/… placeholders with `messageParams`, not string
119
+ concatenation or interpolation of dynamic values — required for correct translation. See
120
+ [logging.md § Placeholders instead of concatenation](../switch-api/logging.md#placeholders-instead-of-concatenation),
121
+ which also covers the separate, narrower literal-`%` escaping gotcha.
122
+ - [ ] **Apps required / scripts recommended.** Logging isn't excessive — no `Info`/`Debug` calls
123
+ that don't help a flow operator or support engineer understand what happened — see
124
+ [logging.md § Log volume](../switch-api/logging.md#log-volume).
125
+
126
+ ## Temp files and paths
127
+
128
+ - [ ] **Universal.** All temporary files and folders are removed by the script before it finishes,
129
+ rather than relying on Switch's executor-refresh cleanup — see
130
+ [job-patterns.md § Temp file cleanup](../switch-api/job-patterns.md#temp-file-cleanup).
131
+ - [ ] **Universal.** No Oracle JRE is embedded as an extra file, in a script or an app — see
132
+ [script-structure.md § Script folder files](../switch-project/script-structure.md#script-folder-files).
133
+ - [ ] **Apps required / scripts recommended.** File system path strings are built with Node's
134
+ `path` module, not hardcoded separators or string concatenation — see
135
+ [job-patterns.md § Platform-independent paths](../switch-api/job-patterns.md#platform-independent-paths).
136
+
137
+ ---
138
+
139
+ ## App-only
140
+
141
+ The following apply only to `ScriptPackageType="App"`, not to a plain `Script` package at all.
142
+ Unlike the sections above, these are documented in full here rather than deferring elsewhere.
143
+
144
+ ### Identity and versioning
145
+
146
+ - [ ] **`Name` (Script ID) matches `[A-Za-z0-9_-]+`** — letters, digits, hyphens and underscores
147
+ only, no spaces. It must also be globally unique across the Appstore; Enfocus checks for clashes
148
+ at review and may require a change. Pick something unlikely to collide.
149
+ - [ ] **`Name` never changes across versions.** It is what identifies the app in an existing
150
+ customer flow, so changing it breaks the upgrade path. `DisplayName` *can* change freely between
151
+ versions without breaking upgrades — so keep any third-party application version number out of
152
+ `Name`, and preferably out of `DisplayName` too, or every application release forces a rename.
153
+ - [ ] **`Version` is only incremented once the previous version was actually published.** If a
154
+ submission is rejected, resubmit under the *same* version number rather than bumping. Minor
155
+ versions are allowed to one level (`1.1`, `1.10`, `1.34`); leading zeros are not (`1.01` is
156
+ invalid).
157
+ - [ ] **Every app in an app bundle carries the same version**, including the master app entry.
158
+ - [ ] **The pack ID always starts with `com.enfocus.`**, generated from the script ID, regardless
159
+ of the company name and domain set in SwitchScripter preferences. To keep it stable when
160
+ updating, open the existing `.enfpack` (not the `.sscript`) before re-packing — SwitchScripter
161
+ remembers the pack ID from the pack it opened.
162
+
163
+ ### Top-level declaration properties
164
+
165
+ - [ ] **App-level `Tooltip` is filled in** — this is the tooltip on the icon in the Elements pane,
166
+ separate from the per-property tooltips above.
167
+ - [ ] **`Keywords` is populated and matches the keywords on the app's Appstore detail page.** Words
168
+ already in `DisplayName` are implicitly searchable and don't need repeating. Matching is
169
+ case-insensitive.
170
+ - [ ] **`ExecutionMode="Concurrent"` wherever the script allows it.** `Serialized` is the default
171
+ and the slower choice; concurrent is preferred for throughput. If you go concurrent, the script
172
+ must synchronise its own access to any shared resource — see
173
+ [execution-environment.md § Execution modes](../switch-api/execution-environment.md#execution-modes).
174
+ - [ ] **`ExecutionGroup` is shared across every app that drives the same non-concurrent third-party
175
+ application**, so Switch can stop them using it simultaneously. If your app is in that situation,
176
+ ask Enfocus which group name to use rather than inventing one.
177
+ - [ ] **`PerformanceTuning="Yes"` only with a concrete reason.** It exposes `IdleAfterJob` (and
178
+ `NumberOfSlots`) to the user; don't surface those knobs by default.
179
+ - [ ] **At most three categories, and `SubcategoryInElementPane` is filled in.** New categories need
180
+ Enfocus Product Management approval, and an app listed in more than three needs additional
181
+ approval. Note that an unsigned app always loads under the **Custom** subcategory regardless of
182
+ what it declares — the declared subcategory only takes effect once Enfocus has signed the app, and
183
+ review may change it.
184
+ - [ ] **`DetailedInfo` is written wherever it adds something a `Tooltip` can't carry** — not
185
+ mechanically on every property. It is optional: Switch Designer can export a *flow's* HTML
186
+ documentation from every element in it, so an empty `DetailedInfo` produces a blank entry there
187
+ if a flow builder ever generates one, which is a reason to fill it in where the property warrants
188
+ it, not a reason to pad out every trivial one. See
189
+ [property-documentation.md](../switch-project/property-documentation.md) for what to write in it, and when
190
+ it's fine to leave blank. The [app manual](app-manual.md#flow-elements-properties) is the surface
191
+ users are far more likely to read, and it needs real per-property content regardless of what
192
+ `DetailedInfo` holds.
193
+ - [ ] **`Description`, `Compatibility`, `SupportInfo` and `AppDiscovery` are filled in.** These are
194
+ required to create the `.enfpack` at all, and `SupportInfo` must point at *your* support channel,
195
+ never Enfocus Support — see [app-store-listing.md](app-store-listing.md) for what to write
196
+ in each.
197
+ - [ ] **The app manual document is prepared**, following Enfocus's `documentation-app_name.docx`
198
+ template, and uploaded during Appstore review — see [app-manual.md](app-manual.md).
199
+ - [ ] **The declared incoming/outgoing connection topology matches what the code actually does** —
200
+ see [Entry points](#entry-points) and [Sending jobs](#sending-jobs) above.
201
+ - [ ] **Dependent properties have real dependency conditions and values, never left empty**, and the
202
+ script does not read a dependent property's value when its master isn't set to the value that
203
+ makes it relevant.
204
+
205
+ ### Password protection
206
+
207
+ - [ ] **The app is password protected with a non-trivial password** (8+ characters, mixed case,
208
+ digits, symbols). Enfocus checks the password strength at review.
209
+ - [ ] **Don't lose the password.** For a Node.js app, Switch runs an obfuscated copy of the code
210
+ produced at save time and never needs the original source, so the protection is genuinely
211
+ one-way: Enfocus cannot recover a Node.js script's source if the author forgets the password.
212
+ (For legacy JavaScript/VBScript a bypass mechanism exists internally at Enfocus, so that
213
+ protection is weaker — another reason to be on Node.js.)
214
+ - [ ] Note that a published Node.js app ships only the obfuscated code, re-protected with a random
215
+ password Enfocus generates during review — not the password you set.
216
+
217
+ ### Localization
218
+
219
+ - [ ] **Prefer more than one language.** An app is preferably localized beyond English — see
220
+ [Translation files](../switch-project/script-structure.md#script-folder-files) (`<LanguageCode>.ts`) and
221
+ `SwitchScriptTool --generate-translations` in [tooling.md](../switch-project/tooling.md).
222
+ - [ ] **The app's own translations are limited to the six Switch UI languages** (English, French,
223
+ German, Italian, Chinese, Japanese); other languages are not accepted for the app itself. The
224
+ separate Appstore *documentation* may be supplied in additional languages, since it isn't
225
+ generated through the SDK.
226
+ - [ ] **Translations must be complete, and logging must go through the translation mechanism.** A
227
+ partially-translated app (some strings translated, others not) is not acceptable — every
228
+ user-facing message needs an entry in each shipped language file, and log calls must go through
229
+ that mechanism rather than hardcoding English text that bypasses it. Note that
230
+ `Switch.tr()` extraction is static and silently skips anything that isn't a plain string literal
231
+ — see [switch.md § Translation extraction rules](../switch-api/switch.md#translation-extraction-rules).
232
+ Check the generated `.ts` files rather than assuming.
233
+
234
+ ### Icon
235
+
236
+ - [ ] **32×32 PNG, RGB, transparent background**, attached as the app icon. This is what appears in
237
+ the Elements pane and in flows.
238
+ - [ ] **A separate icon (at least 200×200, square) is uploaded to the Appstore submission form**
239
+ for the Appstore listing — it is not part of the pack. See
240
+ [app-store-submission.md § Icons](app-store-submission.md#icons) for both assets' exact
241
+ specs and how to derive/verify them from source art.
242
+
243
+ ### Extra files
244
+
245
+ - [ ] **macOS Mach-O binaries in extra files (`Resources/`) must be universal.** Any bundled macOS
246
+ application, framework bundle, executable, or dynamic library must include both `x86_64` and
247
+ `arm64` architectures. Relying on Rosetta 2 costs performance and isn't a long-term option.
248
+ - [ ] **macOS Mach-O binaries in extra files must be signed and notarized.** An unsigned or
249
+ incorrectly signed executable or dynamic library will fail Appstore review. At minimum, sign with
250
+ hardened runtime enabled and a secure timestamp; verify with `codesign -dv <file>` and check the
251
+ output contains a `Timestamp` line and the `runtime` flag on the `CodeDirectory` line.
252
+ - [ ] **Extra files must not change at runtime.** Don't place files the script might modify or
253
+ regenerate at runtime — license files included — inside the app's own package folder. Switch
254
+ validates the app package's signature, and content that changes after signing fails that
255
+ validation, causing Switch to remove the app. Write runtime-mutable data (temp files, caches,
256
+ generated output) outside the app folder — see
257
+ [job-patterns.md § Temp file cleanup](../switch-api/job-patterns.md#temp-file-cleanup).
258
+ - [ ] Find bundled files at runtime with `flowElement.getPluginResourcesPath()` — see
259
+ [flow-element.md](../switch-api/flow-element.md).
260
+
261
+ ### Source and review hygiene
262
+
263
+ - [ ] **Comments in the source are in English**, so Enfocus reviewers can read them.
264
+ - [ ] **No malicious or unrelated payload** — nothing that harms the user's machine or installs
265
+ additional software.
266
+ - [ ] **No Oracle JRE embedded**, in a script or an app, for licensing reasons — see
267
+ [script-structure.md § Script folder files](../switch-project/script-structure.md#script-folder-files).
268
+ - [ ] **Automating a third-party application doesn't breach that application's licence.** Some
269
+ vendors' terms explicitly exclude automation; check before building.
270
+
271
+ ### What review does and doesn't cover
272
+
273
+ Enfocus verifies that the app loads correctly and becomes available as a flow element, and checks
274
+ the items above. It does **not** test that the app works — functional correctness, error handling,
275
+ code structure and naming are the app creator's responsibility, though good practice in all of them
276
+ is assumed. Items marked as rejection-triggers in the SDK's own checklist are the identity,
277
+ password, icon, signing and architecture ones above.
278
+
279
+ Budget roughly **10 business days** for a review. An unsigned app only loads in Switch on the
280
+ machine that created it, which is what makes local testing possible before submission; once signed
281
+ it loads anywhere. Apps require Switch 13.1 or later.
@@ -0,0 +1,84 @@
1
+ ---
2
+ id: app-manual
3
+ category: switch-appstore
4
+ order: 23
5
+ summary: "Writing guidance for the separate app manual document uploaded during Appstore review (`documentation-app_name.docx` template)"
6
+ triggers:
7
+ - "Preparing the app manual document for Appstore submission"
8
+ ---
9
+
10
+ # Writing the App Manual
11
+
12
+ Guidance for the separate Word/PDF document an app creator uploads during Appstore review,
13
+ following Enfocus's `documentation-app_name.docx` template. Enfocus shows it as a downloadable
14
+ document on the app's Appstore detail page — distinct from the
15
+ [listing fields](app-store-listing.md) shown inline on the listing itself, and from
16
+ [Tooltip/DetailedInfo](../switch-project/property-documentation.md), seen only after installing. Follow the
17
+ template's section order below; don't invent your own structure.
18
+
19
+ **Delivery:** there's no declaration attribute for this content, since it isn't part of the app
20
+ itself. Write it to `./DOCUMENTATION.md` at the project root, using the section headers below. The
21
+ user copies each section into Enfocus's `documentation-app_name.docx` template and uploads the
22
+ result during Appstore review; tell them that's the next step once the file is written.
23
+
24
+ Same writing-quality bar as [property-documentation.md § Writing quality](../switch-project/property-documentation.md#writing-quality):
25
+ no restated field names, no generic marketing vocabulary, no meta-commentary about the
26
+ implementation, no vague filler, no copy-pasted boilerplate, nothing that won't translate cleanly.
27
+
28
+ ## App name
29
+
30
+ The declaration's `DisplayName`.
31
+
32
+ ## Description
33
+
34
+ Reuse [the listing's `Description`](app-store-listing.md#description) — adapt lightly if the
35
+ extra room here helps (e.g. a screenshot the listing field can't carry), but don't rewrite the
36
+ same facts from scratch.
37
+
38
+ ## Compatibility
39
+
40
+ **Not the same field as the declaration's `Compatibility` attribute** — see the naming trap in
41
+ [app-store-listing.md § Compatibility](app-store-listing.md#compatibility). This section
42
+ is the minimum **Switch** version the app requires, e.g. "Switch 24.10 and higher." There's no
43
+ declaration attribute for it; it's implicit in which SwitchScripter version built the app (see
44
+ [script-structure.md § SwitchScripter and Switch version compatibility](../switch-project/script-structure.md#switchscripter-and-switch-version-compatibility)).
45
+ The same value also goes in the "Switch Version" field on the
46
+ [Appstore App Version submission form](app-store-submission.md#fields-that-reuse-content-youve-already-written)
47
+ — state it explicitly here rather than leaving it for the reader to infer.
48
+
49
+ ## Compatibility third-party applications
50
+
51
+ Reuse [the listing's `Compatibility`](app-store-listing.md#compatibility) content — the
52
+ third-party application versions this app supports, with links to the vendor's download/version
53
+ page where one exists.
54
+
55
+ ## Application discovery details
56
+
57
+ Reuse [the listing's `AppDiscovery`](app-store-listing.md#appdiscovery) content. Add a
58
+ screenshot here if the discovery mechanism (or the manual-path property, when auto-detection
59
+ fails) is easier to show than describe.
60
+
61
+ ## Connections
62
+
63
+ Reuse [the listing's `Connections`](app-store-listing.md#connections) content, expanded with
64
+ whatever detail or screenshots the shorter listing field couldn't carry.
65
+
66
+ ## Properties detailed info
67
+
68
+ Reuse each property's and connection's [`DetailedInfo`](../switch-project/property-documentation.md#detailedinfo)
69
+ content — but don't limit this section to whatever `DetailedInfo` happens to contain. `DetailedInfo`
70
+ is optional and often left blank; this manual is the far more likely surface to actually be read,
71
+ so write real per-property content here even for properties where `DetailedInfo` was skipped.
72
+
73
+ For an app bundle (multiple flow elements in one pack), document each flow element separately —
74
+ the template repeats this section per element ("Flow element 1", "Flow element 2", …).
75
+
76
+ ### Flow elements properties
77
+
78
+ Per flow element, per property: what it controls, and any tip or constraint a user needs to get
79
+ the element working — the same content `DetailedInfo` would hold, written in full regardless of
80
+ whether `DetailedInfo` itself was filled in.
81
+
82
+ ### Outgoing connections properties
83
+
84
+ Same treatment, for injected connection properties.
@@ -0,0 +1,69 @@
1
+ ---
2
+ id: app-store-listing
3
+ category: switch-appstore
4
+ order: 22
5
+ summary: "Writing guidance for the declaration's Appstore listing fields: `Description`, `Compatibility`, `SupportInfo`, `AppDiscovery`, `Connections`"
6
+ triggers:
7
+ - "Writing or reviewing the app's Appstore listing text"
8
+ ---
9
+
10
+ # Writing the Appstore Listing Fields
11
+
12
+ Guidance for the *content* of the declaration's prose fields that appear on the app's Enfocus
13
+ Appstore listing: `Description`, `Compatibility`, `SupportInfo`, `AppDiscovery`, and `Connections`
14
+ — see [script-declaration.md](../switch-project/script-declaration.md#built-in-elementfields) for the
15
+ attribute mechanics.
16
+
17
+ Audience here is a prospective buyer browsing the Appstore, deciding whether to install the app at
18
+ all — they haven't seen the flow element yet. That's different from
19
+ [property-documentation.md](../switch-project/property-documentation.md) (`Tooltip`/`DetailedInfo`, read only
20
+ after installing) and from [app-manual.md](app-manual.md) (a separate uploaded document).
21
+ This content is also reused directly in the app manual's Description, Compatibility third-party
22
+ applications, Application discovery details, and Connections sections — write it once, here, well.
23
+
24
+ **Delivery:** write this content directly into the matching top-level attributes in
25
+ `<ScriptID>.xml` (`Description`, `Compatibility`, `SupportInfo`, `AppDiscovery`, `Connections`) —
26
+ there is no separate file. See
27
+ [script-declaration.md § Agent editing policy](../switch-project/script-declaration.md#agent-editing-policy)
28
+ for how to edit the declaration safely.
29
+
30
+ Same writing-quality bar as [property-documentation.md § Writing quality](../switch-project/property-documentation.md#writing-quality):
31
+ no restated field names, no generic marketing vocabulary, no meta-commentary about the
32
+ implementation, no vague filler, no copy-pasted boilerplate, nothing that won't translate cleanly.
33
+
34
+ ## `Description`
35
+
36
+ What the app does, who it's for, and the third-party application or service it automates. Lead
37
+ with the concrete task the app performs, not a category label — "Converts incoming PDFs to the
38
+ customer's house color profile using ColorLogic" tells a buyer more than "A color management
39
+ app." A screenshot is worth adding here if it clarifies the app's purpose faster than prose would.
40
+
41
+ ## `Compatibility`
42
+
43
+ **Naming trap:** despite the name, this is **third-party application** compatibility, not Switch
44
+ version compatibility — Switch has no declaration field for the minimum Switch version an app
45
+ requires (see [app-manual.md § Compatibility](app-manual.md#compatibility) for where that
46
+ goes instead). State which versions of the automated third-party application are supported, e.g.
47
+ "Requires Acrobat Pro DC 2023 or later." Link to the third-party vendor's own download or version
48
+ page if one exists.
49
+
50
+ ## `SupportInfo`
51
+
52
+ The app creator's own support channel (email or URL) — **never Enfocus Support**, this is
53
+ mandatory per [app-guidelines.md § App-only](app-guidelines.md#top-level-declaration-properties).
54
+ State the channel plainly; this field has no dedicated section in the app manual, so it's the only
55
+ place this information reliably reaches a buyer before they install.
56
+
57
+ ## `AppDiscovery`
58
+
59
+ How the app locates the third-party application on disk. State the actual mechanism, concretely
60
+ enough that a user who hits a failure knows what to check: which known install paths are searched
61
+ first, and — if auto-detection can fail — which property lets the user point at the install
62
+ location manually.
63
+
64
+ ## `Connections`
65
+
66
+ A prose overview of the flow element's incoming and outgoing connections: what triggers the
67
+ element, what each outgoing connection carries, and when a job is routed to which one. This is the
68
+ topology summary a flow builder reads before wiring the element into a flow, so describe behavior
69
+ ("routes to Error when validation fails"), not just connection names.