@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.
- package/README.md +1 -1
- package/dist/FileUtils.js +19 -13
- package/dist/MultiScopeWatcher.js +212 -84
- package/dist/allScopesCommands.js +4 -2
- package/dist/appUtils.js +23 -12
- package/dist/commander.js +17 -0
- package/dist/commands.js +38 -1
- package/dist/config.js +4 -4
- package/dist/snClient.js +81 -1
- package/dist/tests/ensureUpdateSetWarnings.test.js +218 -0
- package/dist/tests/errorLogLevels.test.js +273 -0
- package/dist/tests/fileContextSkipReason.test.js +116 -0
- package/dist/tests/globalDebounce.test.js +307 -0
- package/dist/tests/multi-scope-watcher.test.js +109 -7
- package/dist/tests/pushFiles.test.js +162 -0
- package/dist/tests/rateLimitCoordination.test.js +271 -0
- package/dist/tests/retryOnHttpErr.test.js +154 -0
- package/dist/tests/scopeCaching.test.js +124 -0
- package/dist/tests/serializeUpdateSetConfig.test.js +325 -0
- package/dist/tests/taskClear.test.js +170 -0
- package/dist/tests/taskStaleness.test.js +220 -0
- package/dist/tests/validateTaskId.test.js +304 -0
- package/dist/tests/verifyUpdateSetSwitch.test.js +277 -0
- package/dist/updateSetCommands.js +59 -2
- package/package.json +1 -1
- package/skills/sinc-configure-pipeline.md +19 -19
- package/skills/sinc-debug-build.md +7 -7
- package/skills/sinc-setup-project.md +2 -2
- package/skills/sinc-troubleshoot-sync.md +5 -5
|
@@ -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.
|
|
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
|
@@ -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
|
|
47
|
+
name: "@tenonhq/sincronia-typescript-plugin",
|
|
48
48
|
options: { transpile: false }
|
|
49
49
|
},
|
|
50
50
|
{
|
|
51
|
-
name: "@sincronia
|
|
51
|
+
name: "@tenonhq/sincronia-babel-plugin",
|
|
52
52
|
options: {
|
|
53
53
|
presets: [
|
|
54
|
-
"@sincronia
|
|
54
|
+
"@tenonhq/sincronia-servicenow",
|
|
55
55
|
"@babel/env",
|
|
56
56
|
"@babel/typescript"
|
|
57
57
|
],
|
|
58
58
|
plugins: [
|
|
59
|
-
"@sincronia
|
|
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
|
|
75
|
+
name: "@tenonhq/sincronia-babel-plugin",
|
|
76
76
|
options: {
|
|
77
77
|
presets: [
|
|
78
|
-
"@sincronia
|
|
78
|
+
"@tenonhq/sincronia-servicenow",
|
|
79
79
|
"@babel/env"
|
|
80
80
|
],
|
|
81
81
|
plugins: [
|
|
82
|
-
"@sincronia
|
|
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
|
|
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
|
|
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
|
|
139
|
-
| Babel | Run Babel transforms | `@sincronia
|
|
140
|
-
| ESLint | Lint before sync (blocks on errors) | `@sincronia
|
|
141
|
-
| Prettier | Format output code | `@sincronia
|
|
142
|
-
| SASS | Compile SCSS to CSS | `@sincronia
|
|
143
|
-
| Webpack | Bundle frontend JS | `@sincronia
|
|
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
|
|
150
|
-
| `@sincronia
|
|
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
|
|
156
|
-
- **Always include `@sincronia
|
|
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
|
|
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
|
|
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
|
|
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
|
|
74
|
-
- Fix: Ensure `@sincronia
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
72
|
-
- "Cannot find module '@sincronia
|
|
73
|
-
- Note: Babel package names differ from sinc.config.js names. In Babel config, `@sincronia
|
|
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
|
|
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
|
|
113
|
+
- Node.js v20 LTS is required -- check with `node -v`.
|