@superglue/cli 1.1.187 → 1.1.188
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/.claude-plugin/plugin.json +1 -1
- package/dist/cli.js +108 -120
- package/dist/skills/superglue/references/http.md +5 -4
- package/dist/skills/superglue/references/suitecloud.md +110 -21
- package/package.json +1 -1
- package/skills/superglue/references/http.md +5 -4
- package/skills/superglue/references/suitecloud.md +110 -21
|
@@ -171,7 +171,7 @@ XML responses are parsed into a JSON tree with ALL tag names normalized to UPPER
|
|
|
171
171
|
|
|
172
172
|
### Error Detection
|
|
173
173
|
|
|
174
|
-
Non-2xx responses
|
|
174
|
+
Non-2xx responses fail with `HTTP <status>: <response body>`. The runtime keeps up to 64 KB of the body and marks the cut when it is longer. A request with an Authorization or Cookie header that is redirected through a sign-in URL (such as a `login` path or a `ReturnUrl` parameter) and ends on a 2xx HTML page fails with `Redirected to the sign-in page <url>`: the server did not accept the credentials. Any other response with status in the 2xx range is treated as success and returned to the step as-is — the runtime does not scan successful response bodies for error fields, so APIs that return HTTP 200 with `{ "error": "..." }` must be handled by your transform or by setting a `stopCondition`/assertion at the step level.
|
|
175
175
|
|
|
176
176
|
Set step-level `failureBehavior: "continue"` to keep the workflow running after a non-2xx failure. The failure remains in `stepResults`.
|
|
177
177
|
|
|
@@ -179,13 +179,14 @@ Set step-level `failureBehavior: "continue"` to keep the workflow running after
|
|
|
179
179
|
|
|
180
180
|
**Connection/server errors:**
|
|
181
181
|
|
|
182
|
-
- Default 1 retry per request. Steps and direct system calls have no retry setting; schedule `options.retries` (0-3)
|
|
183
|
-
- Retries on fetch-level connection failures (reset, TLS, DNS) and on 5xx
|
|
182
|
+
- Default 1 retry per request. Steps and direct system calls have no retry setting; schedule `options.retries` (0-3) sets the retry count for every request in the scheduled run, for the failures below
|
|
183
|
+
- Retries on fetch-level connection failures (reset, TLS, DNS), on 5xx responses to GET, HEAD, and OPTIONS, and on 503 for every method. A 5xx other than 503 on POST, PUT, PATCH, or DELETE fails immediately, because the server can have applied the request. Other 4xx responses fail immediately
|
|
184
184
|
- Fixed 1000ms delay between retries
|
|
185
185
|
|
|
186
186
|
**Rate limiting (429):**
|
|
187
187
|
|
|
188
188
|
- Separate from the `retries` budget; applies even when `retries` is 0
|
|
189
|
+
- A 429 whose body reports `API Login Limit` (an Acumatica licence session limit) is not retried and fails immediately
|
|
189
190
|
- Respects `Retry-After` header (seconds or HTTP-date) on the first retry
|
|
190
191
|
- Otherwise exponential backoff `4^n * 1000ms + jitter` (4s, 16s, 64s, 256s, 1024s)
|
|
191
192
|
- Fails once the total 429 wait would exceed `MAX_RATE_LIMIT_WAIT_MS` (1hr)
|
|
@@ -249,7 +250,7 @@ pagination: {
|
|
|
249
250
|
- Non-array response: stop after first request
|
|
250
251
|
- Max 500 requests (vs 1000 max with a stopCondition)
|
|
251
252
|
|
|
252
|
-
**Paginated step results:** all pages merge into a single result. Access via `sourceData.stepId.data` — it is NOT an array of per-page results. Do not call `.map()` on a paginated step result. Returns single object if one result, array if multiple (unwrapped from single-element array).
|
|
253
|
+
**Paginated step results:** all pages merge into a single result. Access via `sourceData.stepId.data` — it is NOT an array of per-page results. Do not call `.map()` on a paginated step result. Returns single object if one result, array if multiple (unwrapped from single-element array). The same unwrapping applies to every request step result, paginated or not. `sg system call` returns the response body unchanged.
|
|
253
254
|
|
|
254
255
|
### File Output
|
|
255
256
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SuiteCloud
|
|
2
2
|
|
|
3
|
-
The `suitecloud://` protocol changes NetSuite account structure: custom fields, custom record types, custom lists, forms, workflows, saved searches, roles, and
|
|
3
|
+
The `suitecloud://` protocol changes NetSuite account structure: custom fields, custom record types, custom lists, forms, workflows, saved searches, roles, scripts, and dashboards. The NetSuite REST API cannot create these. It deploys SuiteCloud projects (SDF) to the account. Use it on the NetSuite system, next to its HTTP steps.
|
|
4
4
|
|
|
5
5
|
## Step Configuration
|
|
6
6
|
|
|
@@ -25,12 +25,12 @@ sg system call --system-id netsuite \
|
|
|
25
25
|
|
|
26
26
|
### Fields
|
|
27
27
|
|
|
28
|
-
| Field | Required | Notes
|
|
29
|
-
| ---------- | ---------- |
|
|
30
|
-
| `url` | yes | `suitecloud://<account>.app.netsuite.com`. No path, port, or credentials.
|
|
31
|
-
| `body` | yes | JSON string with `operation` and the operation's inputs
|
|
32
|
-
| `systemId` | yes | The NetSuite system. Its OAuth sign-in is used for the login.
|
|
33
|
-
| `modify` | tool steps | Defaults to `false`. Set `true` for `deploy`, which
|
|
28
|
+
| Field | Required | Notes |
|
|
29
|
+
| ---------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `url` | yes | `suitecloud://<account>.app.netsuite.com`. No path, port, or credentials. |
|
|
31
|
+
| `body` | yes | JSON string with `operation` and the operation's inputs |
|
|
32
|
+
| `systemId` | yes | The NetSuite system. Its OAuth sign-in is used for the login. |
|
|
33
|
+
| `modify` | tool steps | Defaults to `false`. Set `true` for `deploy`, `callRestlet`, and `cleanupScripts`, which change the live account. `sg system call` has no flag. |
|
|
34
34
|
|
|
35
35
|
`<account>` is the account ID in host form: lowercase, with `_` written as `-` (`1234567_SB1` becomes `1234567-sb1`). It is the same prefix as the host of the system's REST URL.
|
|
36
36
|
|
|
@@ -40,16 +40,17 @@ The step logs in with the system's NetSuite OAuth token, the same one the REST s
|
|
|
40
40
|
|
|
41
41
|
## Operations
|
|
42
42
|
|
|
43
|
-
| Operation
|
|
44
|
-
|
|
|
45
|
-
| `listObjects`
|
|
46
|
-
| `importObjects`
|
|
47
|
-
| `listFiles`
|
|
48
|
-
| `importFiles`
|
|
49
|
-
| `validate`
|
|
50
|
-
| `preview`
|
|
51
|
-
| `deploy`
|
|
52
|
-
| `callRestlet`
|
|
43
|
+
| Operation | Inputs | Result |
|
|
44
|
+
| ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
45
|
+
| `listObjects` | optional `objectTypes` (array), `scriptIdContains` | Custom objects in the account: type and script ID |
|
|
46
|
+
| `importObjects` | `objectType`, `scriptIds` (array) | `files`: object XML and referenced SuiteScript source; `data` holds import results |
|
|
47
|
+
| `listFiles` | optional `folderPath` (default `/SuiteScripts`) | `data`: array of File Cabinet paths |
|
|
48
|
+
| `importFiles` | `filePaths` (array of File Cabinet paths) | `files`: project paths to UTF-8 text; `data.results`: per-file status |
|
|
49
|
+
| `validate` | `files` | NetSuite's validation of the project. Changes nothing. |
|
|
50
|
+
| `preview` | `files` | The changes a deploy would make. Changes nothing. |
|
|
51
|
+
| `deploy` | `files`, step `modify: true` | Applies the project to the account |
|
|
52
|
+
| `callRestlet` | `scriptId`, `deployId`, optional `method`, `params`, `body` | The JSON response of the RESTlet |
|
|
53
|
+
| `cleanupScripts` | `scriptIds` (array of `customscript_` IDs, at most 50) | Per script: deleted deployments, `inactive`, and what needs the UI (see Temporary Scripts) |
|
|
53
54
|
|
|
54
55
|
A failed operation fails the step with NetSuite's error messages. `importObjects` succeeds as long as NetSuite answers: each object that could not be exported is listed in `data.failedImports` with NetSuite's message, and `files` holds only the exported ones. Objects locked by an installed bundle or SuiteApp fail with "You cannot download the XML file for this object because it is locked", and some workflows fail with "Inconsistent workflow data". `listObjects` does not show which objects are locked; `appId` is `null` for them too. When an import returns no files, pick another object of the same type.
|
|
55
56
|
|
|
@@ -69,6 +70,8 @@ Use `listFiles` and `importFiles` to inspect existing source without deploying a
|
|
|
69
70
|
|
|
70
71
|
The imported source is at `files["FileCabinet/SuiteScripts/example.js"]`. Imports return UTF-8 text; invalid UTF-8 fails the step. Supported roots are `/SuiteScripts`, `/Templates/E-mail Templates`, `/Templates/Marketing Templates`, and `/Web Site Hosting Files`. SuiteApp files cannot be imported. Check every `data.results[]` entry: `loaded: false` includes the failure in `message`, even when the operation succeeds.
|
|
71
72
|
|
|
73
|
+
The `importObjects` request field is `objectType`, not `type`. The returned `files` is a path-to-content map, not an array.
|
|
74
|
+
|
|
72
75
|
## Project Files
|
|
73
76
|
|
|
74
77
|
`files` maps a project path to the file content. Paths are relative and must stay inside the project.
|
|
@@ -178,18 +181,102 @@ Conditions (`initcondition`) have a required `type`, an optional `formula`, and
|
|
|
178
181
|
|
|
179
182
|
Oracle's example deploys three files together: the workflow, the custom list it uses (`customlist_approvalstatuses`), and the transaction body field that uses the list (`custbody_approvalstatus`).
|
|
180
183
|
|
|
184
|
+
## Temporary Scripts
|
|
185
|
+
|
|
186
|
+
Scripts that only do one job, such as a RESTlet that reads a preference or creates a saved search, must not stay in the client's account.
|
|
187
|
+
|
|
188
|
+
1. Give every temporary script a `customscript_` ID and its file a name with the same task prefix, and add both to the task's change log.
|
|
189
|
+
2. Deploy, call, and check the result. Do not deploy the same project again after cleanup: a deploy re-creates the deployments that cleanup deleted and reactivates the script.
|
|
190
|
+
3. Run `cleanupScripts` with every temporary script ID at the end of the task (in a saved tool, on a step with `modify: true`):
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"operation": "cleanupScripts",
|
|
195
|
+
"scriptIds": ["customscript_sg_prefs", "customscript_sg_search_builder"]
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`cleanupScripts` deploys superglue's own cleanup RESTlet (`customscript_superglue_cleanup`). For each listed script that the connected user owns, it deletes every deployment and inactivates the script. Scripts owned by another user come back as `skipped`, unknown IDs as `notFound`. Scripts that did not fit in the RESTlet's governance budget come back as `notProcessed`; run `cleanupScripts` again for them. The helper then deletes its own deployment and every helper deployment older than 15 minutes, and inactivates itself. `helper.remainingDeployments` can list the deployments of other cleanup runs from the last 15 minutes; they are not left by your task, so do not report them. Run one cleanup at a time; an overlapping run redeploys the helper once and retries. Give the step a timeout of at least 120 seconds: a cleanup that is cut off can leave the helper active until the next cleanup. When the helper call fails, superglue sets the helper inactive and reports it under `recovery`. superglue reads the result back with SuiteQL, and the step fails when a script still has a deployment or is still active.
|
|
200
|
+
|
|
201
|
+
NetSuite allows no API to delete a script record: SuiteScript `record.delete` fails with `RECORD_CANNOT_BE_CREATED_OR_DELETED`, and REST has no script record type. A script file cannot be deleted while a script record uses it. After cleanup, the scripts cannot run, but the inactive script records and their files stay. Report each entry of `manualRemoval` to the user with the UI path: Customization > Scripting > Scripts (filter Inactive) > Edit > Delete, then the file in Documents > Files > File Cabinet > SuiteScripts. The helper's own record is reused on the next cleanup.
|
|
202
|
+
|
|
203
|
+
## Saved Searches
|
|
204
|
+
|
|
205
|
+
SDF exports a saved search as a system-generated `definition` that cannot be edited, so do not write a `savedsearch` object by hand. Create the search with SuiteScript in a temporary RESTlet:
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
const s = search.create({
|
|
209
|
+
type: search.Type.INVOICE,
|
|
210
|
+
id: "customsearch_sg_open_invoices",
|
|
211
|
+
title: "SG Open Invoices",
|
|
212
|
+
filters: [["mainline", "is", "T"], "AND", ["status", "anyof", "CustInvc:A"]],
|
|
213
|
+
columns: ["tranid", "entity", "trandate", "amount"],
|
|
214
|
+
});
|
|
215
|
+
const internalId = s.save();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`id` sets the script ID that dashboards and other objects reference. `search.delete({ id })` removes a search. Remove the RESTlet with `cleanupScripts` afterwards.
|
|
219
|
+
|
|
220
|
+
## Dashboards
|
|
221
|
+
|
|
222
|
+
A role's dashboard is a `publisheddashboard` object. Import one from the account with `importObjects` as the pattern. The structure that deployed:
|
|
223
|
+
|
|
224
|
+
```xml
|
|
225
|
+
<publisheddashboard scriptid="custpubdashboard_sg_ar">
|
|
226
|
+
<center>BASIC</center>
|
|
227
|
+
<locknewbar>F</locknewbar>
|
|
228
|
+
<lockshortcuts>F</lockshortcuts>
|
|
229
|
+
<name>SG AR Dashboard</name>
|
|
230
|
+
<roles>
|
|
231
|
+
<role>
|
|
232
|
+
<role>[scriptid=customrole_sg_ar_clerk]</role>
|
|
233
|
+
</role>
|
|
234
|
+
</roles>
|
|
235
|
+
<dashboards>
|
|
236
|
+
<dashboard>
|
|
237
|
+
<centertab>BASICCENTERHOMEHOME</centertab>
|
|
238
|
+
<layout>TWO_COLUMN</layout>
|
|
239
|
+
<mode>UNLOCKED</mode>
|
|
240
|
+
<leftcolumn>
|
|
241
|
+
<settings>
|
|
242
|
+
<isminimized>F</isminimized>
|
|
243
|
+
</settings>
|
|
244
|
+
</leftcolumn>
|
|
245
|
+
<centercolumn>
|
|
246
|
+
<customsearch>
|
|
247
|
+
<allowinlineediting>F</allowinlineediting>
|
|
248
|
+
<backgroundtype>GLOBAL_BACKGROUND</backgroundtype>
|
|
249
|
+
<charttheme>GLOBAL_THEME</charttheme>
|
|
250
|
+
<drilldown>NEW_PAGE</drilldown>
|
|
251
|
+
<isminimized>F</isminimized>
|
|
252
|
+
<resultssize>10</resultssize>
|
|
253
|
+
<savedsearch>[scriptid=customsearch_sg_open_invoices]</savedsearch>
|
|
254
|
+
</customsearch>
|
|
255
|
+
</centercolumn>
|
|
256
|
+
</dashboard>
|
|
257
|
+
</dashboards>
|
|
258
|
+
</publisheddashboard>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `roles` lists the roles that get the dashboard. `center` and `centertab` must match the role's center type, such as `BASIC` with `BASICCENTERHOMEHOME`.
|
|
262
|
+
- Portlets in a column: `customsearch` (a saved search), `keyperformanceindicators`, `kpimeter`, `kpireport`, `trendgraph`, `list`, `reminders`, `quicksearch`, `tasklinks`, `recentrecords`, `settings`, and `customportlet` (a portlet script deployment, `[scriptid=<script>.<deployment>]`).
|
|
263
|
+
- A `reminders` portlet needs at least one `reminder`, and each reminder can need a feature in the manifest (`EVENTSTORESPOND` needs `CRM`).
|
|
264
|
+
- Declare a saved search that already exists in the account under `<dependencies><objects>` in the manifest. Validation names missing optional features such as `EXTREMELIST` (for `allowinlineediting`), `SUBSIDIARIES`, and `WEBSERVICES`; declare them `required="false"`.
|
|
265
|
+
- After the deploy, import the dashboard and compare it with what you sent.
|
|
266
|
+
|
|
181
267
|
## Preferences and Other SuiteScript-Only Changes
|
|
182
268
|
|
|
183
269
|
Some changes need SuiteScript that runs inside NetSuite, for example company and accounting preferences (`N/config`). A deploy cannot make these changes, but it can install the script that does:
|
|
184
270
|
|
|
185
271
|
1. Deploy a RESTlet with a `suitecloud://` deploy. The project needs the script file under `FileCabinet/SuiteScripts/` and a `restlet` object under `Objects/`. The deployment `status` is `TESTING` by default, which runs the script for the script owner and the specified audience. `RELEASED` runs it for all specified audience members. The audience fields include `allroles`, `audslctrole`, and `allemployees`. Deploy with `status` `TESTING` and `allemployees` `T` first and call it; switch to `RELEASED` with an explicit audience after it works. NetSuite can drop audience flags on the first deploy of a script, so import the script object after the deploy and check the stored deployment before you call it. Import an existing RESTlet with `importObjects` and use it as the pattern.
|
|
186
|
-
2. Call the RESTlet with the `callRestlet` operation and the script ID and deployment ID from the project, for example `customscript_sg_prefs` and `customdeploy_sg_prefs`. `method` selects the RESTlet entry point: `POST` (default) and `PUT` send `body` as JSON; `GET` and `DELETE` send `params` as query parameters and take no `body`. `params` is an object of string, number, or boolean values and is added to the URL for every method. An empty or `null` response means the RESTlet has no entry point for that method. A RESTlet has 5,000 governance units per call. NetSuite stops the script with `SSS_USAGE_LIMIT_EXCEEDED` when they run out, so keep each call small and check `runtime.getCurrentScript().getRemainingUsage()` in loops.
|
|
272
|
+
2. Call the RESTlet with the `callRestlet` operation (in a saved tool, on a step with `modify: true`) and the script ID and deployment ID from the project, for example `customscript_sg_prefs` and `customdeploy_sg_prefs`. `method` selects the RESTlet entry point: `POST` (default) and `PUT` send `body` as JSON; `GET` and `DELETE` send `params` as query parameters and take no `body`. `params` is an object of string, number, or boolean values and is added to the URL for every method. An empty or `null` response means the RESTlet has no entry point for that method. A RESTlet has 5,000 governance units per call. NetSuite stops the script with `SSS_USAGE_LIMIT_EXCEEDED` when they run out, so keep each call small and check `runtime.getCurrentScript().getRemainingUsage()` in loops.
|
|
187
273
|
|
|
188
|
-
Read the current value before you change it, and tell the user when a setting cannot be undone.
|
|
274
|
+
Read config values with `getValue`; `getText` on a config record fails with `SSS_INVALID_API_USAGE`. Read the current value before you change it, and tell the user when a setting cannot be undone. Remove the RESTlet with `cleanupScripts` when the work is done.
|
|
189
275
|
|
|
190
276
|
## Common Pitfalls
|
|
191
277
|
|
|
192
|
-
- A deploy adds and updates objects. It never deletes them. Removing an object needs the NetSuite UI.
|
|
278
|
+
- A deploy adds and updates objects. It never deletes them. Removing an object needs the NetSuite UI; scripts get as far as `cleanupScripts` takes them.
|
|
279
|
+
- A deploy overwrites a File Cabinet file at the same path. Run `listFiles` first and choose new file names.
|
|
193
280
|
- Some changes cannot be undone, for example enabling a feature or changing a field's type after data exists. Tell the user before the deploy.
|
|
194
281
|
- The file name and the `scriptid` attribute must match, and the prefix must fit the object type.
|
|
195
282
|
- A script object needs `name`, `scriptfile`, and `scriptdeployments`. `scriptfile` must be a `.js` file, written as `[/SuiteScripts/<file>.js]`. Each `scriptdeployment` needs `status`; server script deployments also need `title`, which client script deployments ignore. Script IDs start with `customscript_` and deployment IDs with `customdeploy_`. Oracle's RESTlet example: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_160514060027.html
|
|
@@ -201,9 +288,11 @@ Read the current value before you change it, and tell the user when a setting ca
|
|
|
201
288
|
## Error Recovery
|
|
202
289
|
|
|
203
290
|
1. **"SuiteCloud needs the NetSuite OAuth sign-in"**: the system has no OAuth token. The user connects NetSuite with OAuth first. Link the setup guide https://superglue.ai/docs/guides/netsuite-suitecloud/ and stop.
|
|
204
|
-
2. **401 or "Invalid login attempt"**: the token has expired or the sign-in lacks a scope.
|
|
291
|
+
2. **401 or "Invalid login attempt"**: the token has expired or the sign-in lacks a scope. How long a sign-in lasts depends on the integration record; the user reconnects NetSuite in superglue. If the `restlets` scope is missing, the user reconnects after the scope was added. NetSuite records the failure detail in the Login Audit Trail under **Setup > Users/Roles > User Management > View Login Audit Trail**.
|
|
205
292
|
3. **Validation errors**: read each message. "The manifest must define the X feature as required" means add `<feature required="true">X</feature>` to `manifest.xml`. For other errors, compare the XML with an imported object of the same type.
|
|
206
293
|
4. **Permission errors on validate, preview, or deploy**: report NetSuite's message to the user verbatim.
|
|
207
294
|
5. **`INSUFFICIENT_PERMISSION` from `callRestlet`**: the deployment's audience does not include the connected user and role. Import the script object to see the stored `status` and audience, then redeploy with `status` `TESTING` and `allemployees` `T`.
|
|
208
295
|
6. **429, `CONCURRENCY_LIMIT_EXCEEDED`, or `SSS_REQUEST_LIMIT_EXCEEDED`**: the account's concurrency limit is in use. NetSuite allows 15 concurrent requests by default, shared by REST, SOAP, and RESTlets, and rejects extra requests instead of queuing them. Run NetSuite steps one after another and retry after a short wait.
|
|
209
296
|
7. **`SSS_USAGE_LIMIT_EXCEEDED` from `callRestlet`**: the RESTlet used its 5,000 governance units. Split the work into smaller calls.
|
|
297
|
+
8. **`SSS_INVALID_SCRIPTLET_ID` or "You are not allowed to navigate directly to this page" from `callRestlet`**: the deployment was deleted or the script is inactive, for example after `cleanupScripts`. Deploy the project again only when the script is still needed.
|
|
298
|
+
9. **`cleanupScripts` fails with "did not finish"**: the error holds the report, including the helper's state. Run `cleanupScripts` once more with the same IDs: a second run is safe. A leftover helper deployment cannot run, because the helper is inactive, and a later cleanup deletes it after 15 minutes. When a script still keeps a deployment or stays active, report it to the user. Do not deploy that script's project again.
|
package/package.json
CHANGED
|
@@ -171,7 +171,7 @@ XML responses are parsed into a JSON tree with ALL tag names normalized to UPPER
|
|
|
171
171
|
|
|
172
172
|
### Error Detection
|
|
173
173
|
|
|
174
|
-
Non-2xx responses
|
|
174
|
+
Non-2xx responses fail with `HTTP <status>: <response body>`. The runtime keeps up to 64 KB of the body and marks the cut when it is longer. A request with an Authorization or Cookie header that is redirected through a sign-in URL (such as a `login` path or a `ReturnUrl` parameter) and ends on a 2xx HTML page fails with `Redirected to the sign-in page <url>`: the server did not accept the credentials. Any other response with status in the 2xx range is treated as success and returned to the step as-is — the runtime does not scan successful response bodies for error fields, so APIs that return HTTP 200 with `{ "error": "..." }` must be handled by your transform or by setting a `stopCondition`/assertion at the step level.
|
|
175
175
|
|
|
176
176
|
Set step-level `failureBehavior: "continue"` to keep the workflow running after a non-2xx failure. The failure remains in `stepResults`.
|
|
177
177
|
|
|
@@ -179,13 +179,14 @@ Set step-level `failureBehavior: "continue"` to keep the workflow running after
|
|
|
179
179
|
|
|
180
180
|
**Connection/server errors:**
|
|
181
181
|
|
|
182
|
-
- Default 1 retry per request. Steps and direct system calls have no retry setting; schedule `options.retries` (0-3)
|
|
183
|
-
- Retries on fetch-level connection failures (reset, TLS, DNS) and on 5xx
|
|
182
|
+
- Default 1 retry per request. Steps and direct system calls have no retry setting; schedule `options.retries` (0-3) sets the retry count for every request in the scheduled run, for the failures below
|
|
183
|
+
- Retries on fetch-level connection failures (reset, TLS, DNS), on 5xx responses to GET, HEAD, and OPTIONS, and on 503 for every method. A 5xx other than 503 on POST, PUT, PATCH, or DELETE fails immediately, because the server can have applied the request. Other 4xx responses fail immediately
|
|
184
184
|
- Fixed 1000ms delay between retries
|
|
185
185
|
|
|
186
186
|
**Rate limiting (429):**
|
|
187
187
|
|
|
188
188
|
- Separate from the `retries` budget; applies even when `retries` is 0
|
|
189
|
+
- A 429 whose body reports `API Login Limit` (an Acumatica licence session limit) is not retried and fails immediately
|
|
189
190
|
- Respects `Retry-After` header (seconds or HTTP-date) on the first retry
|
|
190
191
|
- Otherwise exponential backoff `4^n * 1000ms + jitter` (4s, 16s, 64s, 256s, 1024s)
|
|
191
192
|
- Fails once the total 429 wait would exceed `MAX_RATE_LIMIT_WAIT_MS` (1hr)
|
|
@@ -249,7 +250,7 @@ pagination: {
|
|
|
249
250
|
- Non-array response: stop after first request
|
|
250
251
|
- Max 500 requests (vs 1000 max with a stopCondition)
|
|
251
252
|
|
|
252
|
-
**Paginated step results:** all pages merge into a single result. Access via `sourceData.stepId.data` — it is NOT an array of per-page results. Do not call `.map()` on a paginated step result. Returns single object if one result, array if multiple (unwrapped from single-element array).
|
|
253
|
+
**Paginated step results:** all pages merge into a single result. Access via `sourceData.stepId.data` — it is NOT an array of per-page results. Do not call `.map()` on a paginated step result. Returns single object if one result, array if multiple (unwrapped from single-element array). The same unwrapping applies to every request step result, paginated or not. `sg system call` returns the response body unchanged.
|
|
253
254
|
|
|
254
255
|
### File Output
|
|
255
256
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# SuiteCloud
|
|
2
2
|
|
|
3
|
-
The `suitecloud://` protocol changes NetSuite account structure: custom fields, custom record types, custom lists, forms, workflows, saved searches, roles, and
|
|
3
|
+
The `suitecloud://` protocol changes NetSuite account structure: custom fields, custom record types, custom lists, forms, workflows, saved searches, roles, scripts, and dashboards. The NetSuite REST API cannot create these. It deploys SuiteCloud projects (SDF) to the account. Use it on the NetSuite system, next to its HTTP steps.
|
|
4
4
|
|
|
5
5
|
## Step Configuration
|
|
6
6
|
|
|
@@ -25,12 +25,12 @@ sg system call --system-id netsuite \
|
|
|
25
25
|
|
|
26
26
|
### Fields
|
|
27
27
|
|
|
28
|
-
| Field | Required | Notes
|
|
29
|
-
| ---------- | ---------- |
|
|
30
|
-
| `url` | yes | `suitecloud://<account>.app.netsuite.com`. No path, port, or credentials.
|
|
31
|
-
| `body` | yes | JSON string with `operation` and the operation's inputs
|
|
32
|
-
| `systemId` | yes | The NetSuite system. Its OAuth sign-in is used for the login.
|
|
33
|
-
| `modify` | tool steps | Defaults to `false`. Set `true` for `deploy`, which
|
|
28
|
+
| Field | Required | Notes |
|
|
29
|
+
| ---------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `url` | yes | `suitecloud://<account>.app.netsuite.com`. No path, port, or credentials. |
|
|
31
|
+
| `body` | yes | JSON string with `operation` and the operation's inputs |
|
|
32
|
+
| `systemId` | yes | The NetSuite system. Its OAuth sign-in is used for the login. |
|
|
33
|
+
| `modify` | tool steps | Defaults to `false`. Set `true` for `deploy`, `callRestlet`, and `cleanupScripts`, which change the live account. `sg system call` has no flag. |
|
|
34
34
|
|
|
35
35
|
`<account>` is the account ID in host form: lowercase, with `_` written as `-` (`1234567_SB1` becomes `1234567-sb1`). It is the same prefix as the host of the system's REST URL.
|
|
36
36
|
|
|
@@ -40,16 +40,17 @@ The step logs in with the system's NetSuite OAuth token, the same one the REST s
|
|
|
40
40
|
|
|
41
41
|
## Operations
|
|
42
42
|
|
|
43
|
-
| Operation
|
|
44
|
-
|
|
|
45
|
-
| `listObjects`
|
|
46
|
-
| `importObjects`
|
|
47
|
-
| `listFiles`
|
|
48
|
-
| `importFiles`
|
|
49
|
-
| `validate`
|
|
50
|
-
| `preview`
|
|
51
|
-
| `deploy`
|
|
52
|
-
| `callRestlet`
|
|
43
|
+
| Operation | Inputs | Result |
|
|
44
|
+
| ---------------- | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
45
|
+
| `listObjects` | optional `objectTypes` (array), `scriptIdContains` | Custom objects in the account: type and script ID |
|
|
46
|
+
| `importObjects` | `objectType`, `scriptIds` (array) | `files`: object XML and referenced SuiteScript source; `data` holds import results |
|
|
47
|
+
| `listFiles` | optional `folderPath` (default `/SuiteScripts`) | `data`: array of File Cabinet paths |
|
|
48
|
+
| `importFiles` | `filePaths` (array of File Cabinet paths) | `files`: project paths to UTF-8 text; `data.results`: per-file status |
|
|
49
|
+
| `validate` | `files` | NetSuite's validation of the project. Changes nothing. |
|
|
50
|
+
| `preview` | `files` | The changes a deploy would make. Changes nothing. |
|
|
51
|
+
| `deploy` | `files`, step `modify: true` | Applies the project to the account |
|
|
52
|
+
| `callRestlet` | `scriptId`, `deployId`, optional `method`, `params`, `body` | The JSON response of the RESTlet |
|
|
53
|
+
| `cleanupScripts` | `scriptIds` (array of `customscript_` IDs, at most 50) | Per script: deleted deployments, `inactive`, and what needs the UI (see Temporary Scripts) |
|
|
53
54
|
|
|
54
55
|
A failed operation fails the step with NetSuite's error messages. `importObjects` succeeds as long as NetSuite answers: each object that could not be exported is listed in `data.failedImports` with NetSuite's message, and `files` holds only the exported ones. Objects locked by an installed bundle or SuiteApp fail with "You cannot download the XML file for this object because it is locked", and some workflows fail with "Inconsistent workflow data". `listObjects` does not show which objects are locked; `appId` is `null` for them too. When an import returns no files, pick another object of the same type.
|
|
55
56
|
|
|
@@ -69,6 +70,8 @@ Use `listFiles` and `importFiles` to inspect existing source without deploying a
|
|
|
69
70
|
|
|
70
71
|
The imported source is at `files["FileCabinet/SuiteScripts/example.js"]`. Imports return UTF-8 text; invalid UTF-8 fails the step. Supported roots are `/SuiteScripts`, `/Templates/E-mail Templates`, `/Templates/Marketing Templates`, and `/Web Site Hosting Files`. SuiteApp files cannot be imported. Check every `data.results[]` entry: `loaded: false` includes the failure in `message`, even when the operation succeeds.
|
|
71
72
|
|
|
73
|
+
The `importObjects` request field is `objectType`, not `type`. The returned `files` is a path-to-content map, not an array.
|
|
74
|
+
|
|
72
75
|
## Project Files
|
|
73
76
|
|
|
74
77
|
`files` maps a project path to the file content. Paths are relative and must stay inside the project.
|
|
@@ -178,18 +181,102 @@ Conditions (`initcondition`) have a required `type`, an optional `formula`, and
|
|
|
178
181
|
|
|
179
182
|
Oracle's example deploys three files together: the workflow, the custom list it uses (`customlist_approvalstatuses`), and the transaction body field that uses the list (`custbody_approvalstatus`).
|
|
180
183
|
|
|
184
|
+
## Temporary Scripts
|
|
185
|
+
|
|
186
|
+
Scripts that only do one job, such as a RESTlet that reads a preference or creates a saved search, must not stay in the client's account.
|
|
187
|
+
|
|
188
|
+
1. Give every temporary script a `customscript_` ID and its file a name with the same task prefix, and add both to the task's change log.
|
|
189
|
+
2. Deploy, call, and check the result. Do not deploy the same project again after cleanup: a deploy re-creates the deployments that cleanup deleted and reactivates the script.
|
|
190
|
+
3. Run `cleanupScripts` with every temporary script ID at the end of the task (in a saved tool, on a step with `modify: true`):
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"operation": "cleanupScripts",
|
|
195
|
+
"scriptIds": ["customscript_sg_prefs", "customscript_sg_search_builder"]
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
`cleanupScripts` deploys superglue's own cleanup RESTlet (`customscript_superglue_cleanup`). For each listed script that the connected user owns, it deletes every deployment and inactivates the script. Scripts owned by another user come back as `skipped`, unknown IDs as `notFound`. Scripts that did not fit in the RESTlet's governance budget come back as `notProcessed`; run `cleanupScripts` again for them. The helper then deletes its own deployment and every helper deployment older than 15 minutes, and inactivates itself. `helper.remainingDeployments` can list the deployments of other cleanup runs from the last 15 minutes; they are not left by your task, so do not report them. Run one cleanup at a time; an overlapping run redeploys the helper once and retries. Give the step a timeout of at least 120 seconds: a cleanup that is cut off can leave the helper active until the next cleanup. When the helper call fails, superglue sets the helper inactive and reports it under `recovery`. superglue reads the result back with SuiteQL, and the step fails when a script still has a deployment or is still active.
|
|
200
|
+
|
|
201
|
+
NetSuite allows no API to delete a script record: SuiteScript `record.delete` fails with `RECORD_CANNOT_BE_CREATED_OR_DELETED`, and REST has no script record type. A script file cannot be deleted while a script record uses it. After cleanup, the scripts cannot run, but the inactive script records and their files stay. Report each entry of `manualRemoval` to the user with the UI path: Customization > Scripting > Scripts (filter Inactive) > Edit > Delete, then the file in Documents > Files > File Cabinet > SuiteScripts. The helper's own record is reused on the next cleanup.
|
|
202
|
+
|
|
203
|
+
## Saved Searches
|
|
204
|
+
|
|
205
|
+
SDF exports a saved search as a system-generated `definition` that cannot be edited, so do not write a `savedsearch` object by hand. Create the search with SuiteScript in a temporary RESTlet:
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
const s = search.create({
|
|
209
|
+
type: search.Type.INVOICE,
|
|
210
|
+
id: "customsearch_sg_open_invoices",
|
|
211
|
+
title: "SG Open Invoices",
|
|
212
|
+
filters: [["mainline", "is", "T"], "AND", ["status", "anyof", "CustInvc:A"]],
|
|
213
|
+
columns: ["tranid", "entity", "trandate", "amount"],
|
|
214
|
+
});
|
|
215
|
+
const internalId = s.save();
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
`id` sets the script ID that dashboards and other objects reference. `search.delete({ id })` removes a search. Remove the RESTlet with `cleanupScripts` afterwards.
|
|
219
|
+
|
|
220
|
+
## Dashboards
|
|
221
|
+
|
|
222
|
+
A role's dashboard is a `publisheddashboard` object. Import one from the account with `importObjects` as the pattern. The structure that deployed:
|
|
223
|
+
|
|
224
|
+
```xml
|
|
225
|
+
<publisheddashboard scriptid="custpubdashboard_sg_ar">
|
|
226
|
+
<center>BASIC</center>
|
|
227
|
+
<locknewbar>F</locknewbar>
|
|
228
|
+
<lockshortcuts>F</lockshortcuts>
|
|
229
|
+
<name>SG AR Dashboard</name>
|
|
230
|
+
<roles>
|
|
231
|
+
<role>
|
|
232
|
+
<role>[scriptid=customrole_sg_ar_clerk]</role>
|
|
233
|
+
</role>
|
|
234
|
+
</roles>
|
|
235
|
+
<dashboards>
|
|
236
|
+
<dashboard>
|
|
237
|
+
<centertab>BASICCENTERHOMEHOME</centertab>
|
|
238
|
+
<layout>TWO_COLUMN</layout>
|
|
239
|
+
<mode>UNLOCKED</mode>
|
|
240
|
+
<leftcolumn>
|
|
241
|
+
<settings>
|
|
242
|
+
<isminimized>F</isminimized>
|
|
243
|
+
</settings>
|
|
244
|
+
</leftcolumn>
|
|
245
|
+
<centercolumn>
|
|
246
|
+
<customsearch>
|
|
247
|
+
<allowinlineediting>F</allowinlineediting>
|
|
248
|
+
<backgroundtype>GLOBAL_BACKGROUND</backgroundtype>
|
|
249
|
+
<charttheme>GLOBAL_THEME</charttheme>
|
|
250
|
+
<drilldown>NEW_PAGE</drilldown>
|
|
251
|
+
<isminimized>F</isminimized>
|
|
252
|
+
<resultssize>10</resultssize>
|
|
253
|
+
<savedsearch>[scriptid=customsearch_sg_open_invoices]</savedsearch>
|
|
254
|
+
</customsearch>
|
|
255
|
+
</centercolumn>
|
|
256
|
+
</dashboard>
|
|
257
|
+
</dashboards>
|
|
258
|
+
</publisheddashboard>
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
- `roles` lists the roles that get the dashboard. `center` and `centertab` must match the role's center type, such as `BASIC` with `BASICCENTERHOMEHOME`.
|
|
262
|
+
- Portlets in a column: `customsearch` (a saved search), `keyperformanceindicators`, `kpimeter`, `kpireport`, `trendgraph`, `list`, `reminders`, `quicksearch`, `tasklinks`, `recentrecords`, `settings`, and `customportlet` (a portlet script deployment, `[scriptid=<script>.<deployment>]`).
|
|
263
|
+
- A `reminders` portlet needs at least one `reminder`, and each reminder can need a feature in the manifest (`EVENTSTORESPOND` needs `CRM`).
|
|
264
|
+
- Declare a saved search that already exists in the account under `<dependencies><objects>` in the manifest. Validation names missing optional features such as `EXTREMELIST` (for `allowinlineediting`), `SUBSIDIARIES`, and `WEBSERVICES`; declare them `required="false"`.
|
|
265
|
+
- After the deploy, import the dashboard and compare it with what you sent.
|
|
266
|
+
|
|
181
267
|
## Preferences and Other SuiteScript-Only Changes
|
|
182
268
|
|
|
183
269
|
Some changes need SuiteScript that runs inside NetSuite, for example company and accounting preferences (`N/config`). A deploy cannot make these changes, but it can install the script that does:
|
|
184
270
|
|
|
185
271
|
1. Deploy a RESTlet with a `suitecloud://` deploy. The project needs the script file under `FileCabinet/SuiteScripts/` and a `restlet` object under `Objects/`. The deployment `status` is `TESTING` by default, which runs the script for the script owner and the specified audience. `RELEASED` runs it for all specified audience members. The audience fields include `allroles`, `audslctrole`, and `allemployees`. Deploy with `status` `TESTING` and `allemployees` `T` first and call it; switch to `RELEASED` with an explicit audience after it works. NetSuite can drop audience flags on the first deploy of a script, so import the script object after the deploy and check the stored deployment before you call it. Import an existing RESTlet with `importObjects` and use it as the pattern.
|
|
186
|
-
2. Call the RESTlet with the `callRestlet` operation and the script ID and deployment ID from the project, for example `customscript_sg_prefs` and `customdeploy_sg_prefs`. `method` selects the RESTlet entry point: `POST` (default) and `PUT` send `body` as JSON; `GET` and `DELETE` send `params` as query parameters and take no `body`. `params` is an object of string, number, or boolean values and is added to the URL for every method. An empty or `null` response means the RESTlet has no entry point for that method. A RESTlet has 5,000 governance units per call. NetSuite stops the script with `SSS_USAGE_LIMIT_EXCEEDED` when they run out, so keep each call small and check `runtime.getCurrentScript().getRemainingUsage()` in loops.
|
|
272
|
+
2. Call the RESTlet with the `callRestlet` operation (in a saved tool, on a step with `modify: true`) and the script ID and deployment ID from the project, for example `customscript_sg_prefs` and `customdeploy_sg_prefs`. `method` selects the RESTlet entry point: `POST` (default) and `PUT` send `body` as JSON; `GET` and `DELETE` send `params` as query parameters and take no `body`. `params` is an object of string, number, or boolean values and is added to the URL for every method. An empty or `null` response means the RESTlet has no entry point for that method. A RESTlet has 5,000 governance units per call. NetSuite stops the script with `SSS_USAGE_LIMIT_EXCEEDED` when they run out, so keep each call small and check `runtime.getCurrentScript().getRemainingUsage()` in loops.
|
|
187
273
|
|
|
188
|
-
Read the current value before you change it, and tell the user when a setting cannot be undone.
|
|
274
|
+
Read config values with `getValue`; `getText` on a config record fails with `SSS_INVALID_API_USAGE`. Read the current value before you change it, and tell the user when a setting cannot be undone. Remove the RESTlet with `cleanupScripts` when the work is done.
|
|
189
275
|
|
|
190
276
|
## Common Pitfalls
|
|
191
277
|
|
|
192
|
-
- A deploy adds and updates objects. It never deletes them. Removing an object needs the NetSuite UI.
|
|
278
|
+
- A deploy adds and updates objects. It never deletes them. Removing an object needs the NetSuite UI; scripts get as far as `cleanupScripts` takes them.
|
|
279
|
+
- A deploy overwrites a File Cabinet file at the same path. Run `listFiles` first and choose new file names.
|
|
193
280
|
- Some changes cannot be undone, for example enabling a feature or changing a field's type after data exists. Tell the user before the deploy.
|
|
194
281
|
- The file name and the `scriptid` attribute must match, and the prefix must fit the object type.
|
|
195
282
|
- A script object needs `name`, `scriptfile`, and `scriptdeployments`. `scriptfile` must be a `.js` file, written as `[/SuiteScripts/<file>.js]`. Each `scriptdeployment` needs `status`; server script deployments also need `title`, which client script deployments ignore. Script IDs start with `customscript_` and deployment IDs with `customdeploy_`. Oracle's RESTlet example: https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_160514060027.html
|
|
@@ -201,9 +288,11 @@ Read the current value before you change it, and tell the user when a setting ca
|
|
|
201
288
|
## Error Recovery
|
|
202
289
|
|
|
203
290
|
1. **"SuiteCloud needs the NetSuite OAuth sign-in"**: the system has no OAuth token. The user connects NetSuite with OAuth first. Link the setup guide https://superglue.ai/docs/guides/netsuite-suitecloud/ and stop.
|
|
204
|
-
2. **401 or "Invalid login attempt"**: the token has expired or the sign-in lacks a scope.
|
|
291
|
+
2. **401 or "Invalid login attempt"**: the token has expired or the sign-in lacks a scope. How long a sign-in lasts depends on the integration record; the user reconnects NetSuite in superglue. If the `restlets` scope is missing, the user reconnects after the scope was added. NetSuite records the failure detail in the Login Audit Trail under **Setup > Users/Roles > User Management > View Login Audit Trail**.
|
|
205
292
|
3. **Validation errors**: read each message. "The manifest must define the X feature as required" means add `<feature required="true">X</feature>` to `manifest.xml`. For other errors, compare the XML with an imported object of the same type.
|
|
206
293
|
4. **Permission errors on validate, preview, or deploy**: report NetSuite's message to the user verbatim.
|
|
207
294
|
5. **`INSUFFICIENT_PERMISSION` from `callRestlet`**: the deployment's audience does not include the connected user and role. Import the script object to see the stored `status` and audience, then redeploy with `status` `TESTING` and `allemployees` `T`.
|
|
208
295
|
6. **429, `CONCURRENCY_LIMIT_EXCEEDED`, or `SSS_REQUEST_LIMIT_EXCEEDED`**: the account's concurrency limit is in use. NetSuite allows 15 concurrent requests by default, shared by REST, SOAP, and RESTlets, and rejects extra requests instead of queuing them. Run NetSuite steps one after another and retry after a short wait.
|
|
209
296
|
7. **`SSS_USAGE_LIMIT_EXCEEDED` from `callRestlet`**: the RESTlet used its 5,000 governance units. Split the work into smaller calls.
|
|
297
|
+
8. **`SSS_INVALID_SCRIPTLET_ID` or "You are not allowed to navigate directly to this page" from `callRestlet`**: the deployment was deleted or the script is inactive, for example after `cleanupScripts`. Deploy the project again only when the script is still needed.
|
|
298
|
+
9. **`cleanupScripts` fails with "did not finish"**: the error holds the report, including the helper's state. Run `cleanupScripts` once more with the same IDs: a second run is safe. A leftover helper deployment cannot run, because the helper is inactive, and a later cleanup deletes it after 15 minutes. When a script still keeps a deployment or stays active, report it to the user. Do not deploy that script's project again.
|