@enfocussw/switch-scripting-context 25.11.0-beta.9 → 25.11.1-beta.1

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 (35) hide show
  1. package/CHANGELOG.md +256 -0
  2. package/README.md +59 -35
  3. package/dist/init.d.ts +15 -0
  4. package/dist/init.js +130 -76
  5. package/docs/switch-api/api-versions.md +116 -0
  6. package/docs/switch-api/connection.md +82 -0
  7. package/docs/switch-api/{api-document-classes.md → document-classes.md} +13 -2
  8. package/docs/switch-api/entry-points.md +171 -0
  9. package/docs/switch-api/{api-enums.md → enums.md} +23 -12
  10. package/docs/switch-api/{api-execution-environment.md → execution-environment.md} +39 -12
  11. package/docs/switch-api/{api-flow-element.md → flow-element.md} +23 -7
  12. package/docs/switch-api/{api-http.md → http.md} +10 -1
  13. package/docs/switch-api/job-patterns.md +178 -0
  14. package/docs/switch-api/{api-job.md → job.md} +23 -9
  15. package/docs/switch-api/{api-logging.md → logging.md} +47 -5
  16. package/docs/switch-api/{api-switch.md → switch.md} +68 -4
  17. package/docs/switch-appstore/app-guidelines.md +258 -0
  18. package/docs/switch-appstore/app-manual.md +84 -0
  19. package/docs/switch-appstore/app-store-listing.md +69 -0
  20. package/docs/switch-appstore/app-store-submission.md +83 -0
  21. package/docs/{switch-api/api-debugging.md → switch-project/debugging.md} +9 -0
  22. package/docs/{switch-api/api-logs-and-dataroot.md → switch-project/logs-and-dataroot.md} +10 -1
  23. package/docs/switch-project/project-planning.md +117 -0
  24. package/docs/switch-project/property-documentation.md +75 -0
  25. package/docs/{switch-api/api-property-editors.md → switch-project/property-editors.md} +55 -8
  26. package/docs/{switch-api/api-script-declaration.md → switch-project/script-declaration.md} +47 -13
  27. package/docs/switch-project/script-structure.md +188 -0
  28. package/docs/{switch-api/api-tooling.md → switch-project/tooling.md} +87 -1
  29. package/docs/{switch-api/api-vscode.md → switch-project/vscode.md} +19 -0
  30. package/docs/switch-scripting.md +49 -23
  31. package/package.json +11 -7
  32. package/docs/switch-api/api-connection.md +0 -63
  33. package/docs/switch-api/api-entry-points.md +0 -82
  34. package/docs/switch-api/api-job-patterns.md +0 -79
  35. package/docs/switch-api/api-script-structure.md +0 -85
@@ -0,0 +1,75 @@
1
+ ---
2
+ id: property-documentation
3
+ category: switch-project
4
+ order: 21
5
+ summary: "Writing guidance for `Tooltip` and `DetailedInfo` content on properties/connections"
6
+ triggers:
7
+ - "Writing or reviewing the text in a property's or connection's `Tooltip`/`DetailedInfo`"
8
+ ---
9
+
10
+ # Writing Property and Connection Documentation
11
+
12
+ Guidance for the *content* of `Tooltip` and `DetailedInfo` on properties and connections — see
13
+ [script-declaration.md](script-declaration.md#custom-property-elements-elementfields) for
14
+ the attribute mechanics (when each is required, what breaks if omitted). This page covers what to
15
+ actually write in them.
16
+
17
+ Both attributes are read only inside Switch Designer, by someone who has already added the flow
18
+ element to a flow. Compare with [app-store-listing.md](../switch-appstore/app-store-listing.md), read by
19
+ someone deciding whether to install the app at all, and [app-manual.md](../switch-appstore/app-manual.md), a
20
+ separate uploaded document.
21
+
22
+ **Delivery:** write this content directly into the `Tooltip`/`DetailedInfo` attributes in
23
+ `<ScriptID>.xml` — there is no separate file. See
24
+ [script-declaration.md § Agent editing policy](script-declaration.md#agent-editing-policy)
25
+ for how to edit the declaration safely.
26
+
27
+ ## Tooltip
28
+
29
+ The one-line hint shown in the Properties pane. This is the field that matters most here — it's
30
+ always visible to whoever is configuring the property, connection, or the app's own icon, with no
31
+ extra click required.
32
+
33
+ - Add information the property name doesn't already give: units, valid range, format, or an
34
+ example value. A tooltip that just restates the label ("Output folder: the output folder to
35
+ use") wastes the one line a user reliably reads.
36
+ - If no sensible default is possible, put an example value in the tooltip — see the `Default`
37
+ guideline in [script-declaration.md](script-declaration.md#custom-property-elements-elementfields).
38
+ - Sentence-style capitalization, no trailing period, one line. Match the style already used in
39
+ declarations: `Tooltip="Select the output folder"`.
40
+ - Skip it only when the property is genuinely self-explanatory from its name alone, with no
41
+ constraints worth stating (e.g. "Number of copies" with no min/max).
42
+
43
+ ## DetailedInfo
44
+
45
+ Feeds the HTML documentation Switch Designer can export for a whole *flow* — not something
46
+ specific to this app, it's compiled from every element the flow builder happens to have placed in
47
+ that flow. Real, but low priority in practice: it's an opt-in export most flow builders never
48
+ generate, and plenty of shipped apps leave it blank. Don't treat an empty `DetailedInfo` as a
49
+ defect to pad out.
50
+
51
+ Fill it in when the tooltip's one line genuinely isn't enough — an interaction with another
52
+ property, an edge case worth flagging, or why a setting matters, not a restatement of the tooltip
53
+ in longer sentences. A trivial property with `DetailedInfo=""` is expected and fine.
54
+
55
+ If you do write `DetailedInfo`, don't rely on it being the only place that content exists — the
56
+ property's fuller description belongs in [app-manual.md](../switch-appstore/app-manual.md#properties-detailed-info)
57
+ regardless, since that document is far more likely to actually be read.
58
+
59
+ ## Writing quality
60
+
61
+ Applies to both fields, and is the same bar [app-store-listing.md](../switch-appstore/app-store-listing.md)
62
+ and [app-manual.md](../switch-appstore/app-manual.md) hold their content to:
63
+
64
+ - **Don't restate the field name.** A tooltip on "Output folder" that says "The output folder"
65
+ adds nothing a user didn't already know.
66
+ - **No generic AI/marketing vocabulary** — powerful, seamless, robust, intuitive, and similar
67
+ words that could describe any property in any app. Say the specific thing this one does.
68
+ - **No meta-commentary about the implementation.** Write what the user sees or does, not how the
69
+ script is coded internally ("this property is passed to the internal handler").
70
+ - **No vague filler.** "Set this to an appropriate value" tells a user nothing; a concrete unit,
71
+ range, or example does.
72
+ - **No copy-pasted boilerplate across properties.** Identical tooltip text reused on unrelated
73
+ properties signals nobody thought about the individual property.
74
+ - **Keep it translatable.** Idioms and culture-specific references don't translate cleanly into
75
+ the six Switch languages — see [switch.md § Translation extraction rules](../switch-api/switch.md#translation-extraction-rules).
@@ -1,20 +1,29 @@
1
+ ---
2
+ id: property-editors
3
+ category: switch-project
4
+ order: 15
5
+ summary: "Property editor types, string return values, literal editors, dropdowns"
6
+ triggers:
7
+ - "Defining property editors in XML or reading property values in code"
8
+ ---
9
+
1
10
  # Property Editors
2
11
 
3
12
  Property editors determine how a user enters a value in Switch Designer, and what string is
4
13
  returned by `getPropertyStringValue()` at runtime. They're set in the XML declaration via the
5
14
  `Editor` and `Type` attributes on a custom property — see
6
- [api-script-declaration.md](api-script-declaration.md) for the surrounding attribute grammar.
15
+ [script-declaration.md](script-declaration.md) for the surrounding attribute grammar.
7
16
 
8
17
  All property values are returned as `string` or `string[]`. `getPropertyType()` returns one of a
9
18
  fixed set of `PropertyType` values (`literal`, `string`, `number`, `date`, `boolean`, `filepath`,
10
- `folderpath`, `regex`, `oauthtoken`, etc. — see [api-enums.md](api-enums.md)) describing what kind of
19
+ `folderpath`, `regex`, `oauthtoken`, etc. — see [enums.md](../switch-api/enums.md)) describing what kind of
11
20
  value is actually present; `literal` is only one of these, not a binary "literal vs. user-entered"
12
21
  flag. Use it before interpreting the returned string, e.g. to check for a literal editor's constant
13
22
  before comparing it.
14
23
 
15
24
  `Editor` is a `;`-joined list: the inline editor's token (if any) first, then modal editor tokens in
16
25
  the order they're offered. `Type` follows from which editor(s) are chosen — see the tables below and
17
- [the Type reference](api-script-declaration.md#type-reference).
26
+ [the Type reference](script-declaration.md#type-reference).
18
27
 
19
28
  ---
20
29
 
@@ -48,8 +57,8 @@ open a dialog when the user clicks the editor button. All have `Type="string"` u
48
57
  | `regexp` | Regular expression string as entered |
49
58
  | `filetype` | Filename pattern(s) for the selected file type |
50
59
  | `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`) |
60
+ | `askplugin` | String value selected from the library dialog (calls `getLibraryForProperty`) — **requires the script to implement `getLibraryForProperty`** (`getLibraryForConnectionProperty` for a connection field), see [entry-points.md](../switch-api/entry-points.md#property-ui-callbacks) |
61
+ | `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 [entry-points.md](../switch-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
62
  | `description` | Multi-line text as a single string (may include newlines) |
54
63
  | `scriptexp` | Result of script expression evaluated in job context, as a string |
55
64
  | `sltextwithvar` | Single-line text with variables substituted, as a string |
@@ -58,7 +67,7 @@ open a dialog when the user clicks the editor button. All have `Type="string"` u
58
67
  | `filepatterns` | `string[]` — one pattern string per entry |
59
68
  | `folderpatterns` | `string[]` — one pattern string per entry |
60
69
  | `stringlist` (`Type="stringlist"`) | `string[]` — one string per line |
61
- | `oauth` | OAuth 2.0 access token string, or `""` if not yet authorized — see [ExtraProperties](api-script-declaration.md#extraproperties) |
70
+ | `oauth` | OAuth 2.0 access token string, or `""` if not yet authorized — see [ExtraProperties](script-declaration.md#extraproperties) |
62
71
  | `external`, `external2`, … | File path to a property set edited by an external companion app |
63
72
 
64
73
  > Duplicate tokens in `Editor` are meaningless — each modal editor can only be offered once, except
@@ -75,6 +84,14 @@ constant as one of the choices; all literal tokens have `Type="string"`.
75
84
  Always call `getPropertyType()` first to check whether the value is a literal before comparing the
76
85
  string.
77
86
 
87
+ Use the matching literal editor for a keyword-style value like "Default", "None", or "Automatic"
88
+ instead of making it a typed string default — e.g. `default` instead of a `Default`
89
+ attribute set to the literal text `"Default"`. Typing the keyword as an ordinary string default lets
90
+ a user accidentally change it to arbitrary text and loses the `PropertyType.Literal` signal the
91
+ script relies on. `none` is specifically the sanctioned way to let a property hold an empty value
92
+ under `Validation="Standard"` — combine it into the `Editor` chain instead of relaxing to
93
+ `Validation="None"` just to permit emptiness.
94
+
78
95
  | `Editor` token | `getPropertyStringValue()` returns |
79
96
  |---|---|
80
97
  | `default` | `"Default"` |
@@ -89,6 +106,19 @@ string.
89
106
  | `next` | `"Next"` |
90
107
  | `current` | `"Current"` |
91
108
 
109
+ `nofiles`/`allfiles`/`allotherfiles` and `nofolders`/`allfolders`/`allotherfolders` are not
110
+ general-purpose — every real use of them is a connection **include/exclude filter mask** (an
111
+ `IncludeMask`/`ExcludeMask`, or `IncludeFolderMask`/`ExcludeFolderMask`, on a `ConnectionType="Filter"`
112
+ connection). Switch Designer labels them "No/All/All other jobs" and "No/All folders" in that
113
+ context, not the XML token names above. See [Common practices](#common-practices) below for the
114
+ exact chain.
115
+
116
+ `next` and `current` exist in the editor grammar but have no confirmed script-facing use case — no
117
+ built-in Switch flow element declares a property using them, and Enfocus's own property-editor
118
+ documentation omits both (along with `allotherfolders`, apparently by oversight, since it's used the
119
+ same way as `allotherfiles`). Don't reach for `next`/`current` without confirming a concrete need
120
+ first.
121
+
92
122
  ---
93
123
 
94
124
  ## Extra XML attributes for certain editors
@@ -115,6 +145,17 @@ flow is exported.
115
145
  The following are recommended conventions for pairing property kinds with editor chains, not
116
146
  restrictions enforced by Switch — a script writer can combine editors differently if asked to.
117
147
 
148
+ Enable the dynamic editors (`sltextwithvar`/`mltextwithvar` and `conditionwithvar`, `scriptexp`)
149
+ wherever the value could plausibly vary per job — the rows below already reflect this. Conversely,
150
+ don't add them to a property whose value is inherently static (e.g. a dataset name, or anything a
151
+ `Dependency` master needs to read at flow-configuration time) — offering a variable/script option
152
+ there just invites a value the script can't sensibly use.
153
+
154
+ **App guideline (mandatory for apps, recommended for scripts):** make sure at least one editor in
155
+ the chain works without an add-on Switch module: `scriptexp` requires the Scripting Module, so never
156
+ offer it as the property's only editor — pair it with a module-free option (e.g. `inline` or
157
+ `sltextwithvar`) so the property still works for a user without that module.
158
+
118
159
  | Property kind | Recommended `Editor` chain | Why |
119
160
  |---|---|---|
120
161
  | 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. |
@@ -125,7 +166,9 @@ restrictions enforced by Switch — a script writer can combine editors differen
125
166
  | Boolean consumed directly by script logic (not a `Dependency` master) | `inline` + `conditionwithvar` + `scriptexp` | Safe to toggle dynamically since nothing else depends on it for visibility. Also covers booleans that are themselves a `Dependency` *dependent* (not a master). |
126
167
  | Enum (`Type="enum:..."`) | `inline` only, always | No editor guarantees a var/expression result matches one of the declared items. |
127
168
  | File/folder path that must exist for the app to work | `choosefile`/`choosefolder` only | Switch validates the picked path exists at flow start. |
128
- | 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. |
169
+ | 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 [flow-element.md](../switch-api/flow-element.md#properties) if the path is needed later in the flow. |
170
+ | 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"`. |
171
+ | 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
172
 
130
173
  `regexp`, `filetype`/`types`, `askplugin`/`askplugin2`, `oauth`, and `external` can technically be
131
174
  combined with `sltextwithvar`/`scriptexp` too, but this is uncommon in practice and not covered by a
@@ -137,8 +180,12 @@ dedicated row above.
137
180
 
138
181
  - **OAuth 2.0**: endpoint, client ID/secret, scope, and redirect ports are configured per-property
139
182
  in a matching `<ExtraProperties>` child element (see
140
- [api-script-declaration.md § ExtraProperties](api-script-declaration.md#extraproperties)), not in
183
+ [script-declaration.md § ExtraProperties](script-declaration.md#extraproperties)), not in
141
184
  `Editor`/`Type`. The script receives a ready-to-use access token string via
142
185
  `getPropertyStringValue()`.
143
186
  - **External editor**: requires a separate companion application. The script receives a file path to
144
187
  the property set file. Entry point `findExternalEditorPath` resolves the editor executable path.
188
+ One of these two must be true — either a dependent property named `Application` (see
189
+ [Dependency](script-declaration.md#custom-property-elements-elementfields)) is non-empty, or
190
+ the script implements `findExternalEditorPath` — see
191
+ [entry-points.md](../switch-api/entry-points.md#property-ui-callbacks).
@@ -1,3 +1,12 @@
1
+ ---
2
+ id: script-declaration
3
+ category: switch-project
4
+ order: 14
5
+ summary: "XML declaration reference: properties, connections, execution config; agents may edit this file directly"
6
+ triggers:
7
+ - "Understanding, adding, or editing script properties/connections/execution config"
8
+ ---
9
+
1
10
  # Script Declaration (XML)
2
11
 
3
12
  The file `<ScriptID>.xml` defines the script's metadata, custom properties, connection properties,
@@ -13,8 +22,10 @@ documented below — you can edit it directly.
13
22
  - `Name` is the script's stable ID — never change it on an existing script (it's used as the flow
14
23
  element type and referenced by installed flows). `DisplayName`, tooltips, defaults, adding/removing
15
24
  custom properties, and connection topology are all safe to change directly.
16
- - After editing, `Version` should be bumped (integer, no leading zeros necessary beyond `1`) so
17
- Switch treats it as an update.
25
+ - `Version` identifies a released build, not an edit. Bump it only when the current value has
26
+ already been released (installed on a customer's or production Switch, or published on the
27
+ Appstore). If the current value was never released, keep editing under it; don't bump per
28
+ change. See the `Version` row below for format and what Switch does with it.
18
29
  - Custom property tag names must match `^[A-Za-z][A-Za-z0-9]*$`, must not start with `xml`
19
30
  (case-insensitive), and must not collide with a [reserved tag](#reserved-tag-names) below. Switch
20
31
  does **not** sanitize or rewrite bad tag names on load — it just fails to work correctly.
@@ -54,11 +65,11 @@ value is non-string (see the table); an empty string field can omit `Type`.
54
65
  |---|---|---|
55
66
  | `Name` | string | Stable script ID — **never change on an existing script**. Becomes the flow element `Type`. |
56
67
  | `DisplayName` | string | Shown in Switch. Empty falls back to `Name`. A `~` splits "Vendor~Element" — Switch shows the part after `~` as the short name and the whole string (space-joined) as the full name. |
57
- | `Version` | number | Format `[1-9][0-9]*(\.(0\|[1-9][0-9]*))*`, e.g. `1`, `2.1`. Bump on every declaration change. |
68
+ | `Version` | number | Format `[1-9][0-9]*(\.(0\|[1-9][0-9]*))*`, e.g. `1`, `2.1`. SwitchScripter accepts a whole number only for a script and at most one minor level for an app (`2.1`); it resets anything else. Bump once per release, not per edit (see [Agent editing policy](#agent-editing-policy)). Each flow records the version of the element it uses; Switch compares that recorded value with `UpgradeMaximumVersion` to decide whether to show `FlowUpgradeWarning`. |
58
69
  | `Keywords` | string | Space/comma/semicolon-separated. Elements pane search. |
59
70
  | `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). |
71
+ | `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 [entry-points.md](../switch-api/entry-points.md#job-processing). |
72
+ | `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 [job-patterns.md § Sending jobs](../switch-api/job-patterns.md#sending-jobs). |
62
73
  | `ConnectionType` | `Move` \| `Filter` \| `TrafficLight` | Governs which `ConnectionFields` children are legal — see [ConnectionFields](#connectionfields). |
63
74
  | `FunctionsNodeJSScript` | `;`-separated names | Entry point function names found in the script. Omit the element entirely if there is no Node.js program. |
64
75
  | `ExecutionMode` | `Concurrent` \| `Serialized` | |
@@ -108,13 +119,13 @@ Key attributes:
108
119
  |---|---|
109
120
  | `UserDefined` | Always `"true"` for custom properties. |
110
121
  | `Type` | Value type discriminator — see [Type reference](#type-reference). |
111
- | `Editor` | `;`-joined chain of editor tokens (inline editor first, then modal editors) — see [api-property-editors.md](api-property-editors.md). |
122
+ | `Editor` | `;`-joined chain of editor tokens (inline editor first, then modal editors) — see [property-editors.md](property-editors.md). |
112
123
  | `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. |
124
+ | `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"`). |
125
+ | `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
126
  | `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. |
127
+ | `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 [entry-points.md](../switch-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 [property-editors.md § Literal editors](property-editors.md#literal-editors)) instead of relaxing to `Validation="None"` just to permit emptiness. |
128
+ | `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
129
  | `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
130
  | `DependencyCondition` | One of: `Not-empty`, `Equals`, `Not-equals`, `Contains`, `Does not contain`, `Matches`, `Does not match`, `Starts with`, `Does not start with`. |
120
131
  | `Dependencyvalue` | Value(s) to compare against — `;`-separated for multiple. Note the lowercase `v`. |
@@ -131,6 +142,24 @@ Key attributes:
131
142
  Validation="Standard" Default=""></OutputFolder>
132
143
  ```
133
144
 
145
+ **Runtime pitfall:** when a dependent property is hidden (its master's current value doesn't satisfy
146
+ `DependencyCondition`/`Dependencyvalue`), Switch treats the tag as if it doesn't exist. Calling
147
+ `flowElement.getPropertyStringValue(tag)` on it throws `"Invalid tag: <tag>"` — see
148
+ [flow-element.md](../switch-api/flow-element.md#properties). Only fetch a dependent property once you know
149
+ its condition holds, either by checking the already-read master value yourself, or by calling
150
+ `flowElement.hasProperty(tag)` first (it returns `false` for a hidden dependent). This matters most
151
+ for an enum/drop-down master with several dependents keyed off different values — fetch only the
152
+ ones actually shown for the current selection, not all of them.
153
+
154
+ A script must never assume the set of fetchable dependents is fixed once at flow configuration
155
+ time: if the master's own value is dynamic (a Switch variable or script expression, resolved per
156
+ job in `jobArrived`), which dependents are shown can differ from job to job in the same flow
157
+ execution. Re-check the condition (or call `hasProperty()`) on every invocation rather than caching
158
+ an earlier "shown" determination. There is no way to read a currently-hidden dependent's value for
159
+ a given job — if a script genuinely needs data from more than one dependency group at once (e.g.
160
+ values entered while the master previously pointed elsewhere), this pattern cannot provide it, and
161
+ a different property structure (not `Dependency`-based hiding) is needed instead.
162
+
134
163
  Attribute order in the file doesn't matter functionally — SwitchScripter's own output happens to be
135
164
  alphabetical (QDom sorts attributes on write), but any order parses correctly.
136
165
 
@@ -172,6 +201,11 @@ most important thing to get right when hand-editing this section:
172
201
 
173
202
  Custom (author-defined) connection properties may be added regardless of `ConnectionType`.
174
203
 
204
+ The declared connections and the script's `sendTo*()` calls must agree with each other — see
205
+ [job-patterns.md § Sending jobs](../switch-api/job-patterns.md#sending-jobs) for the exact rules per
206
+ `ConnectionType`. In particular, omit a `Success`/`Warning`/`Error` element entirely (as shown
207
+ above) if the script never routes to it, rather than leaving an unused connection declared.
208
+
175
209
  **Caution — package-swap connection preservation:** Switch Designer decides whether existing flow
176
210
  connections survive a script package replace by comparing the serialised `ConnectionFields` and
177
211
  `IncomingConnections` text **byte-for-byte** between old and new declarations, and additionally
@@ -184,7 +218,7 @@ properties to reset on next package update.
184
218
  ## ExtraProperties
185
219
 
186
220
  Only used for OAuth 2.0 (`Editor` containing `oauth`, see
187
- [api-property-editors.md](api-property-editors.md)). One child element per OAuth property, named
221
+ [property-editors.md](property-editors.md)). One child element per OAuth property, named
188
222
  identically to the property's tag, in `ElementFields` or `ConnectionFields`:
189
223
 
190
224
  ```xml
@@ -236,7 +270,7 @@ If no OAuth property exists on the script, omit `ExtraProperties` entirely or le
236
270
 
237
271
  This is the *authoring* vocabulary written into the XML `Type` attribute. It does not map one-to-one
238
272
  onto the runtime `PropertyType` returned by `getPropertyType()` (see
239
- [api-property-editors.md](api-property-editors.md) and [api-enums.md](api-enums.md)) — e.g. XML
273
+ [property-editors.md](property-editors.md) and [enums.md](../switch-api/enums.md)) — e.g. XML
240
274
  `bool` becomes runtime `boolean`, XML `path` splits into runtime `filepath`/`folderpath`, and several
241
275
  XML types (`rational`, `stringlist`, `accountlist`, `enum:...`, `datetime`, `time`) have no direct
242
276
  `PropertyType` counterpart while several runtime values (`filetype`, `folderpattern`, `regex`,
@@ -244,7 +278,7 @@ XML types (`rational`, `stringlist`, `accountlist`, `enum:...`, `datetime`, `tim
244
278
  interchangeable.
245
279
 
246
280
  `Type` is not independently authored on properties with a modal editor chain — it follows from
247
- which editor(s) you choose, per [api-property-editors.md](api-property-editors.md). When combining
281
+ which editor(s) you choose, per [property-editors.md](property-editors.md). When combining
248
282
  an inline editor with modal editors (e.g. `Editor="inline;sltextwithvar;scriptexp"`), the inline
249
283
  editor's type wins: an inline Number editor plus those modal editors still yields `Type="number"`.
250
284
 
@@ -0,0 +1,188 @@
1
+ ---
2
+ id: script-structure
3
+ category: switch-project
4
+ order: 10
5
+ summary: "What files a script folder contains, `manifest.xml` format, how `SwitchVersion` selects the Node.js version and which tools overwrite it, Script vs App"
6
+ triggers:
7
+ - "Setting up a new script project, converting a Script to an App, or working out which Node.js version a script or app will run on"
8
+ ---
9
+
10
+ # Script Project Structure
11
+
12
+ A Switch script project lives in a **script folder** during development and is distributed as a `.sscript` package (ZIP).
13
+
14
+ > **Agent editing policy**: Edit TypeScript/JavaScript source files (`main.ts`, `main.js`) and the XML declaration (`<ScriptID>.xml`) freely — see [script-declaration.md](script-declaration.md) for the declaration's rules, and [entry-points.md § Entry-point scanner constraints](../switch-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.
15
+
16
+ ## Script folder files
17
+
18
+ | File | Required | Notes |
19
+ |---|---|---|
20
+ | `manifest.xml` | Yes | Switch internal use only. Do not edit manually. |
21
+ | `<ScriptID>.xml` | Yes | Script declaration (properties, connections, execution config). See [script-declaration.md](script-declaration.md). |
22
+ | `main.ts` | Yes (TypeScript) | Source file. Edit this. |
23
+ | `main.js` | Yes | Compiled output executed by Switch. Generated by transpiling `main.ts`. |
24
+ | `main.js.map` | No | Source map, generated alongside `main.js`. |
25
+ | `package.json` | Yes (TypeScript) | NPM config for type declarations. |
26
+ | `tsconfig.json` | Yes (TypeScript) | TypeScript transpilation options. |
27
+ | `<IconFileName>.png` | No | 32×32 px, RGB, not interlaced. Extension must be `.png`. |
28
+ | `node_modules/` | No | Local npm packages. |
29
+ | `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. |
30
+ | `<LanguageCode>.ts` | No | Translation files (Switch App SDK). |
31
+ | `.vscode/launch.json` | No | Debug config (created by SwitchScriptTool). |
32
+ | `.vscode/switch.code-snippets` | No | Entry point snippets (TypeScript only). |
33
+
34
+ > After editing `main.ts`, transpile with SwitchScriptTool to regenerate `main.js` before testing in Switch.
35
+
36
+ ## manifest.xml format
37
+
38
+ ```xml
39
+ <Manifest>
40
+ <ScripterInstanceName>myScript</ScripterInstanceName>
41
+ <SwitchVersion>24.0</SwitchVersion>
42
+ <ScriptPackageFormatVersion>1.0</ScriptPackageFormatVersion>
43
+ <ScriptPasswordProtected>No</ScriptPasswordProtected>
44
+ <ScriptPackageType>Script</ScriptPackageType>
45
+ <ScriptDeclarationFile>myScript.xml</ScriptDeclarationFile>
46
+ <ScriptProgramFiles>
47
+ <ScriptProgramFile ScriptLanguage="NodeJSScript">main.ts</ScriptProgramFile>
48
+ </ScriptProgramFiles>
49
+ <ScriptIconFile>myScript_32.png</ScriptIconFile>
50
+ <LocalizationItems/>
51
+ </Manifest>
52
+ ```
53
+
54
+ `ScriptPackageType` is either `Script` (regular) or `App` (scripted plug-in).
55
+
56
+ ## Node.js version per Switch version
57
+
58
+ The Switch Server running the script picks its Node.js version by looking up `SwitchVersion` from
59
+ `manifest.xml` in a fixed table that ships with each Switch release:
60
+
61
+ | Switch version | `SwitchVersion` | Node.js used |
62
+ |---|---|---|
63
+ | Switch 2020 Spring | `20.0` | 12 |
64
+ | Switch 2020 Fall | `20.1` | 12 |
65
+ | Switch 2021 Spring | `21.0` | 14 |
66
+ | Switch 2021 Fall | `21.1` | 16 |
67
+ | Switch 2022 Spring | `22.0` | 16 |
68
+ | Switch 2022 Fall | `22.1` | 16 |
69
+ | Switch 2023 Fall | `23.1` | 18 |
70
+ | Switch 2024 Spring | `24.0` | 18 |
71
+ | Switch 2024 Fall | `24.1` | 20 |
72
+ | Switch 25.07 | `25.07` | 20 |
73
+ | Switch 25.11 | `25.11` | 20 |
74
+ | Switch 26.03 | `26.03` | 24 |
75
+ | Switch 26.07 | `26.07` | 24 |
76
+
77
+ Switch 26.07 bundles Node.js 12.22.12, 14.21.3, 16.20.2, 18.20.8, 20.20.0, and 24.13.0.
78
+
79
+ `SwitchVersion` controls the Node.js version only. Which scripting API methods exist depends on the
80
+ Switch version the script runs on, see [api-versions.md](../switch-api/api-versions.md).
81
+
82
+ ### What writes `SwitchVersion`
83
+
84
+ `SwitchVersion` is the version of whichever tool last wrote the manifest. It is not a setting the
85
+ author picks:
86
+
87
+ - `SwitchScriptTool --create` writes the tool's own version into the new script folder.
88
+ - `SwitchScriptTool --pack` writes the tool's own version into the packed `.sscript`, whatever the
89
+ script folder's manifest says. The script folder's manifest itself is left unchanged. Verified
90
+ with SwitchScriptTool 26.07: a folder whose manifest said `21.0` packed to a `.sscript` whose
91
+ manifest says `26.07`, which moves the script from Node.js 16 to Node.js 24.
92
+ - SwitchScripter writes its own version on every save. Opening a script with a different
93
+ `SwitchVersion` shows a warning that saving updates the script to the current Node.js version.
94
+
95
+ **Gotcha:** a script folder tested directly in Switch and the `.sscript` packed from it can run on
96
+ different Node.js versions. A folder created with an older SwitchScriptTool keeps that tool's
97
+ version, but packing it with a newer SwitchScriptTool stamps the newer one. Test the packed
98
+ `.sscript`, not only the folder, before shipping it.
99
+
100
+ To target an older Node.js version, build the package with the SwitchScriptTool or SwitchScripter
101
+ of that Switch release. Editing `SwitchVersion` by hand doesn't last, because `--pack` and
102
+ SwitchScripter overwrite it.
103
+
104
+ ### Values missing from the table
105
+
106
+ Behavior of the Switch 26.07 Server:
107
+
108
+ - No `SwitchVersion` element: treated as `20.1`, so Node.js 12.
109
+ - Lower than the first entry: Node.js 12.
110
+ - Higher than the last entry, for example a package built by a newer tool than the Switch it runs
111
+ on: the newest Node.js that Switch bundles. The Server logs a debug message that the script
112
+ "might not be compatible with the current Switch version" and runs it anyway.
113
+ - **Known issue:** a value that isn't an exact entry but falls numerically between the first and
114
+ last entries matches no Node.js version, for example `25.7` instead of `25.07`, `24.10` instead of
115
+ `24.1`, or `23.0`. The Server then fails to build the path to a Node.js binary, so no executor
116
+ process starts for the script. This was confirmed by running the Switch 26.07 lookup code with
117
+ these values, not by running such a script in Switch, so the exact symptom in Switch hasn't been
118
+ checked. A value such as `26.3` works today only because it's higher than the last entry, and it
119
+ will break once a later release adds an entry above it. Never hand-edit `SwitchVersion`. If you
120
+ must, copy a value exactly as the table above spells it.
121
+
122
+ ### Other effects of `SwitchVersion`
123
+
124
+ - npm dependencies that are ES modules are bundled with esbuild before execution only when
125
+ `SwitchVersion` is `24.0` or higher.
126
+ - A script expression has no manifest. It always runs on the newest Node.js the running Switch
127
+ bundles.
128
+
129
+ > **Mac (Apple Silicon):** For scripts whose `SwitchVersion` is `21.1` (Switch 2021 Fall) or later, Switch runs the native ARM build of Node.js on Apple Silicon. Scripts with an earlier `SwitchVersion` run under Rosetta, because the bundled Node.js 12 and 14 are Intel-only builds. If a script bundles platform-specific binaries, ship both Intel and ARM variants.
130
+
131
+ ## Script vs App
132
+
133
+ | | Script | App |
134
+ |---|---|---|
135
+ | `ScriptPackageType` | `Script` | `App` |
136
+ | Appears in Elements pane | No (used via Script element) | Yes, as its own element |
137
+ | Distribution | Freely | Enfocus Appstore only |
138
+ | Position in pane | — | Configured via `PositionInElementPane` in XML |
139
+
140
+ ## Packing an app
141
+
142
+ An app is produced by SwitchScripter, not by SwitchScriptTool, and the two workflows have to be
143
+ joined up explicitly:
144
+
145
+ - **A script folder can only be typed `Script`.** To ship one as an app, pack it with
146
+ `SwitchScriptTool --pack` first, then open the resulting package in SwitchScripter and change the
147
+ type to `App` before creating the `.enfpack`. If "Create pack" is greyed out, the type is still
148
+ `Script`.
149
+ - **Extra files can't be managed while SwitchScripter is working with a script folder.** Convert to
150
+ a script package first. The conversion is reversible, so the `.pdesc` holding the extra-files
151
+ configuration can be round-tripped back into the script folder and kept in version control.
152
+ - **Creating a pack does not save the script.** Save first, or the pack is built from the last
153
+ saved state.
154
+ - **The pack ID is derived from the script ID and always starts with `com.enfocus.`**, whatever the
155
+ company name and domain in SwitchScripter preferences. To keep it stable across an update, open
156
+ the previous `.enfpack` rather than the `.sscript` — SwitchScripter carries the pack ID over from
157
+ the pack it opened, even if the script ID or domain has since changed.
158
+
159
+ ### SwitchScripter and Switch version compatibility
160
+
161
+ An app loads in the Switch version it was built with **or newer**, never older. So the SwitchScripter
162
+ used must be at or below the oldest Switch version you intend to support, and portable
163
+ SwitchScripters exist for older releases specifically to build backwards-compatible apps. There is
164
+ no SwitchScripter for Switch 25.07 or 25.11 — use the Switch 2024 Fall SwitchScripter for those.
165
+ Apps as a concept require Switch 13.1 or later.
166
+
167
+ The SwitchScripter version also fixes the app's Node.js version, see
168
+ [Node.js version per Switch version](#nodejs-version-per-switch-version). An app built with the
169
+ Switch 2024 Fall SwitchScripter has `SwitchVersion` `24.1`, so it runs on Node.js 20 in every Switch
170
+ version that loads it, including 26.x. Switch 26.07 ships SwitchScripter 26.07. An app built with
171
+ it runs on Node.js 24, and it loads only in Switch 26.07 or newer.
172
+
173
+ An unsigned app loads only in Switch on the machine that created it. That's what allows local
174
+ testing before submission; it loads elsewhere only once Enfocus has signed it.
175
+
176
+ ## App Store submission guidelines
177
+
178
+ App-specific submission rules (localization, universal/signed macOS binaries, immutable extra
179
+ files) live in [app-guidelines.md § App-only](../switch-appstore/app-guidelines.md#app-only), alongside the
180
+ rest of the pre-publish checklist.
181
+
182
+ ## Execution modes
183
+
184
+ `Concurrent`/`Serialized`, `ExecutionGroup` and `NumberOfSlots` are set in the XML declaration (see
185
+ [script-declaration.md](script-declaration.md#built-in-elementfields) for the attributes)
186
+ and documented in
187
+ [execution-environment.md § Execution modes](../switch-api/execution-environment.md#execution-modes),
188
+ which also explains why none of them maps to a count of Node.js processes.
@@ -1,3 +1,12 @@
1
+ ---
2
+ id: tooling
3
+ category: switch-project
4
+ order: 11
5
+ summary: "SwitchScriptTool commands and install paths, folder vs `.sscript` trade-offs, deployment"
6
+ triggers:
7
+ - "Running SwitchScriptTool: transpiling, packing, unpacking, or deploying"
8
+ ---
9
+
1
10
  # Script Folders, Packages & SwitchScriptTool
2
11
 
3
12
  ## Script folder vs script package
@@ -36,10 +45,87 @@ location for the current OS if the bare command isn't found.
36
45
 
37
46
  **Transpile** is required after every edit to `main.ts` before testing a **script folder** directly in Switch. Switch executes `main.js` only — `main.ts` is never run directly. Using SwitchScriptTool (not `tsc`) ensures the same transpile options as SwitchScripter, which is required for consistent behavior. This step is **not** needed before packing — see Pack below.
38
47
 
39
- **Pack** transpiles `main.ts` fresh into the package on every run; it never reads or modifies any `main.js` already sitting in the source folder (a stale hand-edited `main.js` there is ignored and left untouched — see the excluded-files note below). It always excludes `package.json` and `.vscode/` from the packed output, printed as an "Info: will not be packed" list even without `--verbose`; `--verbose` additionally prints the full list of what *will* be packed. `node_modules` is packed as-is, so run `npm prune --production` first (see Deployment) — otherwise `devDependencies` (including `@types/*` packages, which the scaffolded `package.json` puts there) get bundled into the package for no runtime benefit. One harmless quirk: the placeholder `<ScriptID>.sscript` file scaffolded by `--create` is not excluded and ends up packed inside the real `.sscript` too.
48
+ **Pack** transpiles `main.ts` fresh into the package on every run; it never reads or modifies any `main.js` already sitting in the source folder (a stale hand-edited `main.js` there is ignored and left untouched — see the excluded-files note below). It always excludes `package.json` and `.vscode/` from the packed output, printed as an "Info: will not be packed" list even without `--verbose`; `--verbose` additionally prints the full list of what *will* be packed. `node_modules` is packed as-is, so run `npm prune --production` first (see Deployment) — otherwise `devDependencies` (including `@types/*` packages, which the scaffolded `package.json` puts there) get bundled into the package for no runtime benefit. One harmless quirk: the placeholder `<ScriptID>.sscript` file scaffolded by `--create` is not excluded and ends up packed inside the real `.sscript` too. Pack also replaces `SwitchVersion` in the packed `manifest.xml` with SwitchScriptTool's own version (`--create` writes it too), and that value picks the Node.js version the package runs on. See [script-structure.md § What writes `SwitchVersion`](script-structure.md#what-writes-switchversion).
40
49
 
41
50
  **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
51
 
52
+ ## Verify entry points before packing
53
+
54
+ Switch's entry-point scanner is regex-based, not a real parser, and can silently drop entry-point
55
+ functions from certain string/regex/division shapes — see
56
+ [entry-points.md § Entry-point scanner constraints](../switch-api/entry-points.md#entry-point-scanner-constraints).
57
+ `--pack` does not validate or warn about this: a script that packs cleanly can still fail to load,
58
+ or load with a property callback that silently never runs. Compiling and passing tests doesn't
59
+ catch it either, since the source is syntactically valid JS/TS — only extraction against the built
60
+ `main.js` does.
61
+
62
+ After any edit to `main.ts`/`main.js`, run the extracted entry points against the **built**
63
+ `main.js` and confirm the expected names come back. Python:
64
+
65
+ ```python
66
+ #!/usr/bin/env python3
67
+ import re, sys
68
+
69
+ STRIP_PATTERN = re.compile(
70
+ r'(?:/\*.*?\*/)'
71
+ r'|(?:(["\'`])\1)'
72
+ r'|(?://.*?$)'
73
+ r'|(?:([/"\'`]).*?[^\\]\2)'
74
+ r'|(?:/(?:\\.|[^/\n])*/)'
75
+ r'|(?:\(\s*\w+\s*/\s*\w+\s*\))'
76
+ r'|(?:\w+\s*/\s*\w+)',
77
+ re.DOTALL | re.MULTILINE,
78
+ )
79
+ FUNC_PATTERN = re.compile(r'function[\s\n\r]+(\$?\w+)[\s\n\r]*\(', re.MULTILINE)
80
+ NODE_ENTRY_POINTS = {
81
+ 'timerFired', 'jobArrived', 'getLibraryForProperty', 'getLibraryForConnectionProperty',
82
+ 'findExternalEditorPath', 'validateProperties', 'validateConnectionProperties',
83
+ 'httpRequestTriggeredSync', 'httpRequestTriggeredAsync', 'flowStopTriggered',
84
+ 'flowStartTriggered', 'abort',
85
+ }
86
+ REQUIRED_ANY = {'jobArrived', 'timerFired', 'flowStopTriggered', 'httpRequestTriggeredAsync'}
87
+
88
+ code = open(sys.argv[1], encoding='utf-8').read()
89
+ stripped = STRIP_PATTERN.sub('', code)
90
+ found = [m.group(1) for m in FUNC_PATTERN.finditer(stripped) if m.group(1) in NODE_ENTRY_POINTS]
91
+ print('Entry points found:', found or '(none)')
92
+ if not (REQUIRED_ANY & set(found)):
93
+ sys.exit('FAIL: none of ' + str(sorted(REQUIRED_ANY)) + ' survived extraction.')
94
+ ```
95
+
96
+ Or Node.js:
97
+
98
+ ```js
99
+ #!/usr/bin/env node
100
+ const fs = require('fs');
101
+
102
+ const STRIP_PATTERN = /(?:\/\*[\s\S]*?\*\/)|(?:(["'`])\1)|(?:\/\/.*?$)|(?:([/"'`])[\s\S]*?[^\\]\2)|(?:\/(?:\\.|[^/\n])*\/)|(?:\(\s*\w+\s*\/\s*\w+\s*\))|(?:\w+\s*\/\s*\w+)/gm;
103
+ const FUNC_PATTERN = /function[\s\n\r]+(\$?\w+)[\s\n\r]*\(/gm;
104
+ const NODE_ENTRY_POINTS = new Set([
105
+ 'timerFired', 'jobArrived', 'getLibraryForProperty', 'getLibraryForConnectionProperty',
106
+ 'findExternalEditorPath', 'validateProperties', 'validateConnectionProperties',
107
+ 'httpRequestTriggeredSync', 'httpRequestTriggeredAsync', 'flowStopTriggered',
108
+ 'flowStartTriggered', 'abort',
109
+ ]);
110
+ const REQUIRED_ANY = ['jobArrived', 'timerFired', 'flowStopTriggered', 'httpRequestTriggeredAsync'];
111
+
112
+ const stripped = fs.readFileSync(process.argv[2], 'utf8').replace(STRIP_PATTERN, '');
113
+ const found = [];
114
+ let m;
115
+ while ((m = FUNC_PATTERN.exec(stripped)) !== null) {
116
+ if (NODE_ENTRY_POINTS.has(m[1])) found.push(m[1]);
117
+ }
118
+ console.log('Entry points found:', found.length ? found : '(none)');
119
+ if (!found.some((f) => REQUIRED_ANY.includes(f))) {
120
+ console.error('FAIL: none of', REQUIRED_ANY, 'survived extraction.');
121
+ process.exit(1);
122
+ }
123
+ ```
124
+
125
+ If the required entry point is missing, dump `stripped`/the stripped output and diff it against
126
+ `main.js` — the first swallowed region starts at the string, regex, or division literal identified
127
+ in the rules above.
128
+
43
129
  ## Deployment
44
130
 
45
131
  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`: