@tenonhq/sincronia-core 0.0.78 → 0.0.80

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.
@@ -0,0 +1,277 @@
1
+ "use strict";
2
+ /**
3
+ * Tests for US-007: Verify update set is active after creation
4
+ *
5
+ * Validates:
6
+ * - After switchToUpdateSet(), getCurrentUpdateSet() is called to verify the sys_id matches
7
+ * - Verification failure triggers a retry
8
+ * - Retry failure produces explicit error with actual update set name
9
+ * - MultiScopeWatcher's ensureUpdateSetForScope() includes the same verification
10
+ */
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ // --- Mock setup (MultiScopeWatcher tests) ---
13
+ var mockSNClient = {
14
+ getScopeId: jest.fn(),
15
+ getUserSysId: jest.fn(),
16
+ getCurrentAppUserPrefSysId: jest.fn(),
17
+ updateCurrentAppUserPref: jest.fn(),
18
+ createCurrentAppUserPref: jest.fn(),
19
+ changeScope: jest.fn().mockResolvedValue(undefined),
20
+ createUpdateSet: jest.fn(),
21
+ changeUpdateSet: jest.fn(),
22
+ getCurrentUpdateSet: jest.fn(),
23
+ client: {
24
+ get: jest.fn(),
25
+ },
26
+ };
27
+ jest.mock("../snClient", () => ({
28
+ defaultClient: jest.fn(() => mockSNClient),
29
+ unwrapSNResponse: jest.fn((val) => val),
30
+ }));
31
+ jest.mock("../FileUtils", () => ({
32
+ getFileContextFromPath: jest.fn(),
33
+ getFileContextWithSkipReason: jest.fn(),
34
+ }));
35
+ jest.mock("../appUtils", () => ({
36
+ groupAppFiles: jest.fn(),
37
+ pushFiles: jest.fn(),
38
+ }));
39
+ jest.mock("../logMessages", () => ({
40
+ logFilePush: jest.fn(),
41
+ }));
42
+ jest.mock("../recentEdits", () => ({
43
+ writeRecentEdit: jest.fn(),
44
+ }));
45
+ jest.mock("../Logger", () => ({
46
+ logger: {
47
+ info: jest.fn(),
48
+ error: jest.fn(),
49
+ warn: jest.fn(),
50
+ debug: jest.fn(),
51
+ success: jest.fn(),
52
+ getLogLevel: jest.fn().mockReturnValue("info"),
53
+ },
54
+ }));
55
+ jest.mock("../config", () => ({
56
+ loadConfigs: jest.fn().mockResolvedValue(undefined),
57
+ getConfig: jest.fn(),
58
+ getRootDir: jest.fn().mockReturnValue("/project"),
59
+ updateManifest: jest.fn(),
60
+ getManifest: jest.fn(),
61
+ getSourcePath: jest.fn().mockReturnValue("/project/src"),
62
+ getScopeManifestPath: jest.fn((scope) => `/project/sinc.manifest.${scope}.json`),
63
+ getManifestPath: jest.fn().mockReturnValue("/project/sinc.manifest.json"),
64
+ }));
65
+ var mockFsStore = {};
66
+ jest.mock("fs", () => ({
67
+ existsSync: jest.fn((p) => {
68
+ return p in mockFsStore;
69
+ }),
70
+ readFileSync: jest.fn((p) => {
71
+ if (p in mockFsStore)
72
+ return mockFsStore[p];
73
+ throw new Error("ENOENT: " + p);
74
+ }),
75
+ writeFileSync: jest.fn((p, data) => {
76
+ mockFsStore[p] = data;
77
+ }),
78
+ statSync: jest.fn(() => ({ mtimeMs: Date.now() })),
79
+ promises: {
80
+ readFile: jest.fn(),
81
+ writeFile: jest.fn(),
82
+ readdir: jest.fn(),
83
+ mkdir: jest.fn(),
84
+ access: jest.fn(),
85
+ stat: jest.fn(),
86
+ },
87
+ }));
88
+ jest.mock("chokidar", () => ({
89
+ watch: jest.fn(() => ({
90
+ on: jest.fn().mockReturnThis(),
91
+ close: jest.fn(),
92
+ })),
93
+ }));
94
+ jest.mock("lodash", () => {
95
+ var actual = jest.requireActual("lodash");
96
+ return {
97
+ ...actual,
98
+ debounce: jest.fn((fn) => {
99
+ var wrapper = jest.fn();
100
+ wrapper.cancel = jest.fn();
101
+ wrapper.flush = jest.fn(() => fn());
102
+ return wrapper;
103
+ }),
104
+ };
105
+ });
106
+ jest.mock("axios", () => ({
107
+ default: {
108
+ create: jest.fn(() => ({
109
+ get: jest.fn().mockResolvedValue({ data: { result: null } }),
110
+ })),
111
+ },
112
+ }));
113
+ // --- Imports ---
114
+ const Logger_1 = require("../Logger");
115
+ const MultiScopeWatcher_1 = require("../MultiScopeWatcher");
116
+ // --- Tests ---
117
+ describe("US-007: Verify update set is active after creation", () => {
118
+ beforeEach(() => {
119
+ jest.clearAllMocks();
120
+ mockFsStore = {};
121
+ // Default: scope switching succeeds
122
+ mockSNClient.getScopeId.mockResolvedValue([{ sys_id: "scope_sys_id" }]);
123
+ mockSNClient.getUserSysId.mockResolvedValue([{ sys_id: "user_sys_id" }]);
124
+ mockSNClient.getCurrentAppUserPrefSysId.mockResolvedValue([{ sys_id: "pref_sys_id" }]);
125
+ mockSNClient.updateCurrentAppUserPref.mockResolvedValue({});
126
+ mockSNClient.createCurrentAppUserPref.mockResolvedValue({});
127
+ });
128
+ afterEach(() => {
129
+ (0, MultiScopeWatcher_1.stopMultiScopeWatching)();
130
+ });
131
+ it("verifies update set switch by calling getCurrentUpdateSet after changeUpdateSet", async () => {
132
+ // Setup: active task + no existing config
133
+ var taskPath = require("path").resolve(process.cwd(), ".sinc-active-task.json");
134
+ mockFsStore[taskPath] = JSON.stringify({
135
+ taskId: "abc123",
136
+ taskName: "Test Task",
137
+ taskDescription: "Test",
138
+ updateSetName: "CU-abc123 Test Task",
139
+ description: "Test update set",
140
+ taskUrl: "https://example.com",
141
+ scopes: {},
142
+ });
143
+ // Scope exists
144
+ mockSNClient.getScopeId.mockResolvedValue([{ sys_id: "scope_sys_id" }]);
145
+ // Search returns existing update set
146
+ mockSNClient.client.get.mockResolvedValue({
147
+ data: { result: [{ sys_id: "us_123", name: "CU-abc123 Test Task" }] }
148
+ });
149
+ // changeUpdateSet succeeds
150
+ mockSNClient.changeUpdateSet.mockResolvedValue(undefined);
151
+ // getCurrentUpdateSet confirms the correct sys_id
152
+ mockSNClient.getCurrentUpdateSet.mockResolvedValue({
153
+ data: { result: { sysId: "us_123", name: "CU-abc123 Test Task" } }
154
+ });
155
+ await MultiScopeWatcher_1.multiScopeWatcher.ensureUpdateSetForScope("x_test_scope");
156
+ // changeUpdateSet was called
157
+ expect(mockSNClient.changeUpdateSet).toHaveBeenCalledWith({ sysId: "us_123" });
158
+ // getCurrentUpdateSet was called for verification
159
+ expect(mockSNClient.getCurrentUpdateSet).toHaveBeenCalledWith("x_test_scope");
160
+ // Verify debug confirmation logged
161
+ expect(Logger_1.logger.debug).toHaveBeenCalledWith(expect.stringContaining("Update set switch verified"));
162
+ });
163
+ it("retries switch when verification shows wrong update set, succeeds on retry", async () => {
164
+ var taskPath = require("path").resolve(process.cwd(), ".sinc-active-task.json");
165
+ mockFsStore[taskPath] = JSON.stringify({
166
+ taskId: "abc123",
167
+ taskName: "Test Task",
168
+ taskDescription: "Test",
169
+ updateSetName: "CU-abc123 Test Task",
170
+ description: "Test update set",
171
+ taskUrl: "https://example.com",
172
+ scopes: {},
173
+ });
174
+ mockSNClient.getScopeId.mockResolvedValue([{ sys_id: "scope_sys_id" }]);
175
+ mockSNClient.client.get.mockResolvedValue({
176
+ data: { result: [{ sys_id: "us_123", name: "CU-abc123 Test Task" }] }
177
+ });
178
+ mockSNClient.changeUpdateSet.mockResolvedValue(undefined);
179
+ // First verification fails (wrong sys_id), second succeeds
180
+ mockSNClient.getCurrentUpdateSet
181
+ .mockResolvedValueOnce({
182
+ data: { result: { sysId: "wrong_us_id", name: "Default" } }
183
+ })
184
+ .mockResolvedValueOnce({
185
+ data: { result: { sysId: "us_123", name: "CU-abc123 Test Task" } }
186
+ });
187
+ await MultiScopeWatcher_1.multiScopeWatcher.ensureUpdateSetForScope("x_test_scope");
188
+ // changeUpdateSet called twice (initial + retry)
189
+ expect(mockSNClient.changeUpdateSet).toHaveBeenCalledTimes(2);
190
+ // Warning logged about retry
191
+ expect(Logger_1.logger.warn).toHaveBeenCalledWith(expect.stringContaining("verification failed, retrying"));
192
+ // Should succeed — no error about "could not be activated"
193
+ var errorCalls = Logger_1.logger.error.mock.calls.map(function (c) { return c[0]; });
194
+ var hasActivationError = errorCalls.some(function (msg) {
195
+ return typeof msg === "string" && msg.indexOf("could not be activated") !== -1;
196
+ });
197
+ expect(hasActivationError).toBe(false);
198
+ });
199
+ it("logs explicit error when both switch attempts fail verification", async () => {
200
+ var taskPath = require("path").resolve(process.cwd(), ".sinc-active-task.json");
201
+ mockFsStore[taskPath] = JSON.stringify({
202
+ taskId: "abc123",
203
+ taskName: "Test Task",
204
+ taskDescription: "Test",
205
+ updateSetName: "CU-abc123 Test Task",
206
+ description: "Test update set",
207
+ taskUrl: "https://example.com",
208
+ scopes: {},
209
+ });
210
+ mockSNClient.getScopeId.mockResolvedValue([{ sys_id: "scope_sys_id" }]);
211
+ mockSNClient.client.get.mockResolvedValue({
212
+ data: { result: [{ sys_id: "us_123", name: "CU-abc123 Test Task" }] }
213
+ });
214
+ mockSNClient.changeUpdateSet.mockResolvedValue(undefined);
215
+ // Both verifications fail — always returns wrong sys_id
216
+ mockSNClient.getCurrentUpdateSet.mockResolvedValue({
217
+ data: { result: { sysId: "wrong_us_id", name: "Default" } }
218
+ });
219
+ await MultiScopeWatcher_1.multiScopeWatcher.ensureUpdateSetForScope("x_test_scope");
220
+ // changeUpdateSet called twice (initial + retry)
221
+ expect(mockSNClient.changeUpdateSet).toHaveBeenCalledTimes(2);
222
+ // Error message should include both the update set name and the actual active one
223
+ expect(Logger_1.logger.error).toHaveBeenCalledWith(expect.stringContaining("could not be activated"));
224
+ expect(Logger_1.logger.error).toHaveBeenCalledWith(expect.stringContaining("Default"));
225
+ });
226
+ it("does not retry when verification passes on first attempt", async () => {
227
+ var taskPath = require("path").resolve(process.cwd(), ".sinc-active-task.json");
228
+ mockFsStore[taskPath] = JSON.stringify({
229
+ taskId: "abc123",
230
+ taskName: "Test Task",
231
+ taskDescription: "Test",
232
+ updateSetName: "CU-abc123 Test Task",
233
+ description: "Test update set",
234
+ taskUrl: "https://example.com",
235
+ scopes: {},
236
+ });
237
+ mockSNClient.getScopeId.mockResolvedValue([{ sys_id: "scope_sys_id" }]);
238
+ mockSNClient.client.get.mockResolvedValue({
239
+ data: { result: [{ sys_id: "us_123", name: "CU-abc123 Test Task" }] }
240
+ });
241
+ mockSNClient.changeUpdateSet.mockResolvedValue(undefined);
242
+ // Verification passes on first attempt
243
+ mockSNClient.getCurrentUpdateSet.mockResolvedValue({
244
+ data: { result: { sysId: "us_123", name: "CU-abc123 Test Task" } }
245
+ });
246
+ await MultiScopeWatcher_1.multiScopeWatcher.ensureUpdateSetForScope("x_test_scope");
247
+ // changeUpdateSet called only once
248
+ expect(mockSNClient.changeUpdateSet).toHaveBeenCalledTimes(1);
249
+ // getCurrentUpdateSet called once for verification
250
+ expect(mockSNClient.getCurrentUpdateSet).toHaveBeenCalledTimes(1);
251
+ // No retry warning
252
+ expect(Logger_1.logger.warn).not.toHaveBeenCalledWith(expect.stringContaining("verification failed, retrying"));
253
+ });
254
+ it("handles getCurrentUpdateSet throwing during verification gracefully", async () => {
255
+ var taskPath = require("path").resolve(process.cwd(), ".sinc-active-task.json");
256
+ mockFsStore[taskPath] = JSON.stringify({
257
+ taskId: "abc123",
258
+ taskName: "Test Task",
259
+ taskDescription: "Test",
260
+ updateSetName: "CU-abc123 Test Task",
261
+ description: "Test update set",
262
+ taskUrl: "https://example.com",
263
+ scopes: {},
264
+ });
265
+ mockSNClient.getScopeId.mockResolvedValue([{ sys_id: "scope_sys_id" }]);
266
+ mockSNClient.client.get.mockResolvedValue({
267
+ data: { result: [{ sys_id: "us_123", name: "CU-abc123 Test Task" }] }
268
+ });
269
+ mockSNClient.changeUpdateSet.mockResolvedValue(undefined);
270
+ // getCurrentUpdateSet throws (network error etc.)
271
+ mockSNClient.getCurrentUpdateSet.mockRejectedValue(new Error("Network error"));
272
+ // Should not crash — the outer try/catch in ensureUpdateSetForScope handles it
273
+ await MultiScopeWatcher_1.multiScopeWatcher.ensureUpdateSetForScope("x_test_scope");
274
+ // Should have attempted verification
275
+ expect(mockSNClient.getCurrentUpdateSet).toHaveBeenCalled();
276
+ });
277
+ });
@@ -226,7 +226,7 @@ async function createUpdateSetCommand(args) {
226
226
  Logger_1.logger.warn(`Update set "${name}" created but could not be activated automatically`);
227
227
  Logger_1.logger.info(`You can manually switch to it using: npx sinc switchUpdateSet --name "${name}"`);
228
228
  if (switchError instanceof Error) {
229
- Logger_1.logger.debug(`Switch error: ${switchError.message}`);
229
+ Logger_1.logger.warn(`Switch error: ${switchError.message}`);
230
230
  }
231
231
  }
232
232
  Logger_1.logger.info(`Update Set ID: ${updateSetSysId}`);
@@ -400,7 +400,8 @@ async function promptForUpdateSetDetails(args) {
400
400
  };
401
401
  }
402
402
  /**
403
- * Helper function to switch to an update set using the new API endpoint
403
+ * Helper function to switch to an update set using the new API endpoint.
404
+ * Verifies the switch was successful by reading back the current update set.
404
405
  */
405
406
  async function switchToUpdateSet(updateSetSysId, name, scope) {
406
407
  const client = (0, snClient_1.defaultClient)();
@@ -431,6 +432,62 @@ async function switchToUpdateSet(updateSetSysId, name, scope) {
431
432
  if (result.message && !result.message.includes("Success") && !result.message.includes("changed")) {
432
433
  throw new Error(result.message);
433
434
  }
435
+ // Verify the switch was successful
436
+ var verified = await verifyActiveUpdateSet(client, updateSetSysId, scope);
437
+ if (!verified) {
438
+ // Retry once
439
+ Logger_1.logger.warn("Update set verification failed, retrying switch...");
440
+ var retryResponse = await client.changeUpdateSet(params);
441
+ var retryResult = await retryResponse.data;
442
+ if (retryResult && retryResult.result) {
443
+ retryResult = retryResult.result;
444
+ }
445
+ if (retryResult.error) {
446
+ throw new Error(retryResult.error);
447
+ }
448
+ var retryVerified = await verifyActiveUpdateSet(client, updateSetSysId, scope);
449
+ if (!retryVerified) {
450
+ var currentName = await getActiveUpdateSetName(client, scope);
451
+ throw new Error("Update set " + (name || updateSetSysId) + " was created but could not be activated. Current update set is " + (currentName || "unknown") + ".");
452
+ }
453
+ }
454
+ }
455
+ /**
456
+ * Verifies the currently active update set matches the expected sys_id.
457
+ */
458
+ async function verifyActiveUpdateSet(client, expectedSysId, scope) {
459
+ try {
460
+ var response = await client.getCurrentUpdateSet(scope);
461
+ var result = await response.data;
462
+ if (result && result.result) {
463
+ result = result.result;
464
+ }
465
+ if (result && result.sysId === expectedSysId) {
466
+ return true;
467
+ }
468
+ Logger_1.logger.warn("Verification mismatch: expected " + expectedSysId + ", got " + (result && result.sysId ? result.sysId : "null"));
469
+ return false;
470
+ }
471
+ catch (e) {
472
+ Logger_1.logger.warn("Verification check failed: " + (e instanceof Error ? e.message : String(e)));
473
+ return false;
474
+ }
475
+ }
476
+ /**
477
+ * Gets the name of the currently active update set for error messages.
478
+ */
479
+ async function getActiveUpdateSetName(client, scope) {
480
+ try {
481
+ var response = await client.getCurrentUpdateSet(scope);
482
+ var result = await response.data;
483
+ if (result && result.result) {
484
+ result = result.result;
485
+ }
486
+ return (result && result.name) ? result.name : null;
487
+ }
488
+ catch (e) {
489
+ return null;
490
+ }
434
491
  }
435
492
  /**
436
493
  * Helper function to switch to a scope
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenonhq/sincronia-core",
3
- "version": "0.0.78",
3
+ "version": "0.0.80",
4
4
  "description": "Next-gen file syncer",
5
5
  "license": "GPL-3.0",
6
6
  "main": "./dist/index.js",
@@ -44,19 +44,19 @@ For each file type, construct a rule using these templates:
44
44
  match: /\.ts$/,
45
45
  plugins: [
46
46
  {
47
- name: "@sincronia/typescript-plugin",
47
+ name: "@tenonhq/sincronia-typescript-plugin",
48
48
  options: { transpile: false }
49
49
  },
50
50
  {
51
- name: "@sincronia/babel-plugin",
51
+ name: "@tenonhq/sincronia-babel-plugin",
52
52
  options: {
53
53
  presets: [
54
- "@sincronia/servicenow",
54
+ "@tenonhq/sincronia-servicenow",
55
55
  "@babel/env",
56
56
  "@babel/typescript"
57
57
  ],
58
58
  plugins: [
59
- "@sincronia/remove-modules",
59
+ "@tenonhq/sincronia-remove-modules",
60
60
  "@babel/proposal-class-properties",
61
61
  "@babel/proposal-object-rest-spread"
62
62
  ]
@@ -72,14 +72,14 @@ For each file type, construct a rule using these templates:
72
72
  match: /\.js$/,
73
73
  plugins: [
74
74
  {
75
- name: "@sincronia/babel-plugin",
75
+ name: "@tenonhq/sincronia-babel-plugin",
76
76
  options: {
77
77
  presets: [
78
- "@sincronia/servicenow",
78
+ "@tenonhq/sincronia-servicenow",
79
79
  "@babel/env"
80
80
  ],
81
81
  plugins: [
82
- "@sincronia/remove-modules"
82
+ "@tenonhq/sincronia-remove-modules"
83
83
  ]
84
84
  }
85
85
  }
@@ -93,7 +93,7 @@ For each file type, construct a rule using these templates:
93
93
  match: /\.wp\.js$/,
94
94
  plugins: [
95
95
  {
96
- name: "@sincronia/webpack-plugin",
96
+ name: "@tenonhq/sincronia-webpack-plugin",
97
97
  options: {
98
98
  configGenerator: (context) => ({
99
99
  mode: "production",
@@ -110,7 +110,7 @@ For each file type, construct a rule using these templates:
110
110
  {
111
111
  match: /\.scss$/,
112
112
  plugins: [
113
- { name: "@sincronia/sass-plugin", options: {} }
113
+ { name: "@tenonhq/sincronia-sass-plugin", options: {} }
114
114
  ]
115
115
  }
116
116
  ```
@@ -135,24 +135,24 @@ Provide a single `npm i -D` command with all required packages.
135
135
 
136
136
  | Plugin | Purpose | npm Package |
137
137
  |--------|---------|-------------|
138
- | TypeScript | Type-check and/or transpile `.ts` | `@sincronia/typescript-plugin` |
139
- | Babel | Run Babel transforms | `@sincronia/babel-plugin` |
140
- | ESLint | Lint before sync (blocks on errors) | `@sincronia/eslint-plugin` |
141
- | Prettier | Format output code | `@sincronia/prettier-plugin` |
142
- | SASS | Compile SCSS to CSS | `@sincronia/sass-plugin` |
143
- | Webpack | Bundle frontend JS | `@sincronia/webpack-plugin` |
138
+ | TypeScript | Type-check and/or transpile `.ts` | `@tenonhq/sincronia-typescript-plugin` |
139
+ | Babel | Run Babel transforms | `@tenonhq/sincronia-babel-plugin` |
140
+ | ESLint | Lint before sync (blocks on errors) | `@tenonhq/sincronia-eslint-plugin` |
141
+ | Prettier | Format output code | `@tenonhq/sincronia-prettier-plugin` |
142
+ | SASS | Compile SCSS to CSS | `@tenonhq/sincronia-sass-plugin` |
143
+ | Webpack | Bundle frontend JS | `@tenonhq/sincronia-webpack-plugin` |
144
144
 
145
145
  ### Babel Sub-Packages (used inside babel-plugin options)
146
146
 
147
147
  | Package | Purpose | Config Key |
148
148
  |---------|---------|------------|
149
- | `@sincronia/babel-plugin-remove-modules` | Strip import/export for ServiceNow | `plugins: ["@sincronia/remove-modules"]` |
150
- | `@sincronia/babel-preset-servicenow` | Sanitize for Rhino engine | `presets: ["@sincronia/servicenow"]` |
149
+ | `@tenonhq/sincronia-babel-plugin-remove-modules` | Strip import/export for ServiceNow | `plugins: ["@tenonhq/sincronia-remove-modules"]` |
150
+ | `@tenonhq/sincronia-babel-preset-servicenow` | Sanitize for Rhino engine | `presets: ["@tenonhq/sincronia-servicenow"]` |
151
151
 
152
152
  ### Critical Warnings
153
153
 
154
154
  - **Never use `useBuiltIns`** with `@babel/env` -- ServiceNow's Rhino engine locks base class prototypes, so polyfills will fail.
155
- - **Always include `@sincronia/servicenow`** as the FIRST Babel preset listed (runs last) for server-side code -- it handles `__proto__` and reserved word issues.
156
- - **Always include `@sincronia/remove-modules`** as a Babel plugin for server-side code -- ServiceNow does not support ES modules.
155
+ - **Always include `@tenonhq/sincronia-servicenow`** as the FIRST Babel preset listed (runs last) for server-side code -- it handles `__proto__` and reserved word issues.
156
+ - **Always include `@tenonhq/sincronia-remove-modules`** as a Babel plugin for server-side code -- ServiceNow does not support ES modules.
157
157
  - If using TypeScript plugin for type-checking only (`transpile: false`), Babel must handle the actual transpilation.
158
158
  - Webpack rules MUST come before generic `.js` rules in the rules array.
@@ -54,27 +54,27 @@ For a typical TypeScript server-side pipeline (`typescript-plugin` + `babel-plug
54
54
  **Stage 2: Babel Plugin** (with presets and plugins)
55
55
 
56
56
  Babel plugins run first (in order):
57
- 1. `@sincronia/remove-modules` -- Strips `import`/`export` statements
57
+ 1. `@tenonhq/sincronia-remove-modules` -- Strips `import`/`export` statements
58
58
  2. `@babel/proposal-class-properties` -- Transforms class property syntax
59
59
  3. `@babel/proposal-object-rest-spread` -- Transforms `...spread` syntax
60
60
 
61
61
  Babel presets run in reverse order:
62
62
  1. `@babel/typescript` (listed last, runs first) -- Strips type annotations
63
63
  2. `@babel/env` -- Transpiles ES6+ to ES5
64
- 3. `@sincronia/servicenow` (listed first, runs last) -- Sanitizes for Rhino: replaces `__proto__` with `__proto_sn__`, converts `obj.default` to `obj["default"]`
64
+ 3. `@tenonhq/sincronia-servicenow` (listed first, runs last) -- Sanitizes for Rhino: replaces `__proto__` with `__proto_sn__`, converts `obj.default` to `obj["default"]`
65
65
 
66
66
  ### Step 4: Diagnose Common Issues
67
67
 
68
68
  **"Cannot read property 'X' of undefined" in ServiceNow**
69
- - Likely: `import` statements were not removed. Check that `@sincronia/remove-modules` is in Babel plugins.
69
+ - Likely: `import` statements were not removed. Check that `@tenonhq/sincronia-remove-modules` is in Babel plugins.
70
70
  - The import variable becomes `undefined` because ServiceNow has no module system.
71
71
 
72
72
  **"Illegal access to reserved word" in ServiceNow**
73
- - Likely: Missing `@sincronia/servicenow` preset. Code like `obj.default` or `obj.class` crashes Rhino.
74
- - Fix: Ensure `@sincronia/servicenow` is the FIRST preset listed (= runs LAST).
73
+ - Likely: Missing `@tenonhq/sincronia-servicenow` preset. Code like `obj.default` or `obj.class` crashes Rhino.
74
+ - Fix: Ensure `@tenonhq/sincronia-servicenow` is the FIRST preset listed (= runs LAST).
75
75
 
76
76
  **"__proto__" security error in ServiceNow**
77
- - Likely: Missing `@sincronia/servicenow` preset. Babel's class transpilation generates `__proto__` references.
77
+ - Likely: Missing `@tenonhq/sincronia-servicenow` preset. Babel's class transpilation generates `__proto__` references.
78
78
 
79
79
  **"TypeError: Cannot extend a non-class" or prototype errors**
80
80
  - Likely: Using `useBuiltIns` with `@babel/env`. Rhino locks base class prototypes.
@@ -85,7 +85,7 @@ Babel presets run in reverse order:
85
85
 
86
86
  ### Special Comment Tags
87
87
 
88
- The `@sincronia/remove-modules` Babel plugin supports these comment tags:
88
+ The `@tenonhq/sincronia-remove-modules` Babel plugin supports these comment tags:
89
89
 
90
90
  **`@keepModule`** -- Preserve an import (for actual ServiceNow modules):
91
91
  ```javascript
@@ -28,14 +28,14 @@ Help the user set up a new Sincronia project or add a new scope to an existing p
28
28
  ### Scenario 1: New Project
29
29
 
30
30
  #### Prerequisites check
31
- - Node.js v16+ installed (`node -v`)
31
+ - Node.js v20 LTS installed (`node -v`)
32
32
  - The Sincronia server scoped app is installed on the target ServiceNow instance
33
33
 
34
34
  #### Initialize the project
35
35
  ```bash
36
36
  mkdir my-servicenow-app && cd my-servicenow-app
37
37
  npm init -y
38
- npm i -D @sincronia/core
38
+ npm i -D @tenonhq/sincronia-core
39
39
  ```
40
40
 
41
41
  #### Run the init wizard
@@ -68,16 +68,16 @@ Sincronia checks that your local manifest scope matches the active scope on the
68
68
 
69
69
  1. **Run a local build to see errors:** `npx sinc build`
70
70
  2. **Common Babel errors:**
71
- - "Cannot find module '@sincronia/remove-modules'" -- Need `npm i -D @sincronia/babel-plugin-remove-modules`
72
- - "Cannot find module '@sincronia/servicenow'" -- Need `npm i -D @sincronia/babel-preset-servicenow`
73
- - Note: Babel package names differ from sinc.config.js names. In Babel config, `@sincronia/remove-modules` refers to npm package `@sincronia/babel-plugin-remove-modules`.
71
+ - "Cannot find module '@tenonhq/sincronia-remove-modules'" -- Need `npm i -D @tenonhq/sincronia-babel-plugin-remove-modules`
72
+ - "Cannot find module '@tenonhq/sincronia-servicenow'" -- Need `npm i -D @tenonhq/sincronia-babel-preset-servicenow`
73
+ - Note: Babel package names differ from sinc.config.js names. In Babel config, `@tenonhq/sincronia-remove-modules` refers to npm package `@tenonhq/sincronia-babel-plugin-remove-modules`.
74
74
 
75
75
  3. **Common TypeScript errors:**
76
76
  - Type errors block the build. Fix the types or set `transpile: true` to skip type checking.
77
77
  - Missing `tsconfig.json` -- Plugin works without it but may produce unexpected output.
78
78
 
79
79
  4. **Rhino engine errors (code works locally but fails in ServiceNow):**
80
- - Missing `@sincronia/servicenow` preset -- `__proto__` references and reserved word property access crash Rhino.
80
+ - Missing `@tenonhq/sincronia-servicenow` preset -- `__proto__` references and reserved word property access crash Rhino.
81
81
  - Using `useBuiltIns` with `@babel/env` -- Polyfills fail because Rhino locks base class prototypes.
82
82
  - Using `for...of`, `Map`, `Set`, `WeakMap` -- These require prototype extensions that Rhino blocks.
83
83
  - Using arrow functions in class properties without `@babel/proposal-class-properties`.
@@ -110,4 +110,4 @@ Sincronia checks that your local manifest scope matches the active scope on the
110
110
  - Always `npx sinc refresh` before starting work to catch new records.
111
111
  - Use `npx sinc status` to verify connectivity.
112
112
  - Check `sincronia-debug-*.log` for detailed error information.
113
- - Node.js v16.x is required -- check with `node -v`.
113
+ - Node.js v20 LTS is required -- check with `node -v`.