@tenonhq/sincronia-core 0.0.74 → 0.0.76
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/dist/index.js +0 -0
- package/dist/initSystem/orchestrator.js +25 -0
- package/package.json +1 -1
- package/dist/Watcher.js +0 -51
- package/dist/tests/watcher.test.js +0 -228
package/dist/index.js
CHANGED
|
File without changes
|
|
@@ -113,6 +113,31 @@ async function runLoginPhase(plugin, context) {
|
|
|
113
113
|
return;
|
|
114
114
|
// Core plugin: retry loop with specific error messages
|
|
115
115
|
if (plugin.name === "core") {
|
|
116
|
+
// If .env already has all required credentials, offer to skip login
|
|
117
|
+
const hasAllCreds = context.env.SN_INSTANCE && context.env.SN_USER && context.env.SN_PASSWORD;
|
|
118
|
+
if (hasAllCreds) {
|
|
119
|
+
Logger_1.logger.info("Found existing credentials:");
|
|
120
|
+
Logger_1.logger.info(" Instance: " + context.env.SN_INSTANCE);
|
|
121
|
+
Logger_1.logger.info(" User: " + context.env.SN_USER);
|
|
122
|
+
Logger_1.logger.info("");
|
|
123
|
+
const useExisting = await inquirer_1.default.prompt([{
|
|
124
|
+
type: "confirm",
|
|
125
|
+
name: "confirmed",
|
|
126
|
+
message: "Use these credentials?",
|
|
127
|
+
default: true,
|
|
128
|
+
}]);
|
|
129
|
+
if (useExisting.confirmed) {
|
|
130
|
+
Logger_1.logger.info("Validating credentials...");
|
|
131
|
+
const result = await (0, corePlugin_1.validateCoreLogin)(context);
|
|
132
|
+
if (result === true) {
|
|
133
|
+
Logger_1.logger.success(chalk_1.default.green("✓ Connected to " + context.env.SN_INSTANCE));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
Logger_1.logger.error(chalk_1.default.red("✗ " + result));
|
|
137
|
+
Logger_1.logger.info("Please re-enter your credentials.");
|
|
138
|
+
context.env.SN_PASSWORD = "";
|
|
139
|
+
}
|
|
140
|
+
}
|
|
116
141
|
while (true) {
|
|
117
142
|
await collectLoginHooks(hooks, context);
|
|
118
143
|
// Run per-hook validation
|
package/package.json
CHANGED
package/dist/Watcher.js
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.startWatching = startWatching;
|
|
7
|
-
exports.stopWatching = stopWatching;
|
|
8
|
-
const chokidar_1 = __importDefault(require("chokidar"));
|
|
9
|
-
const logMessages_1 = require("./logMessages");
|
|
10
|
-
const Logger_1 = require("./Logger");
|
|
11
|
-
const lodash_1 = require("lodash");
|
|
12
|
-
const FileUtils_1 = require("./FileUtils");
|
|
13
|
-
const appUtils_1 = require("./appUtils");
|
|
14
|
-
const recentEdits_1 = require("./recentEdits");
|
|
15
|
-
const DEBOUNCE_MS = 300;
|
|
16
|
-
let pushQueue = [];
|
|
17
|
-
let watcher = undefined;
|
|
18
|
-
const processQueue = (0, lodash_1.debounce)(async () => {
|
|
19
|
-
if (pushQueue.length > 0) {
|
|
20
|
-
//dedupe pushes
|
|
21
|
-
const toProcess = Array.from(new Set([...pushQueue]));
|
|
22
|
-
pushQueue = [];
|
|
23
|
-
const fileContexts = toProcess
|
|
24
|
-
.map(FileUtils_1.getFileContextFromPath)
|
|
25
|
-
.filter((ctx) => !!ctx);
|
|
26
|
-
const buildables = (0, appUtils_1.groupAppFiles)(fileContexts);
|
|
27
|
-
const updateResults = await (0, appUtils_1.pushFiles)(buildables);
|
|
28
|
-
updateResults.forEach((res, index) => {
|
|
29
|
-
(0, logMessages_1.logFilePush)(fileContexts[index], res);
|
|
30
|
-
if (res.success) {
|
|
31
|
-
(0, recentEdits_1.writeRecentEdit)(fileContexts[index]);
|
|
32
|
-
}
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
}, DEBOUNCE_MS);
|
|
36
|
-
function startWatching(directory) {
|
|
37
|
-
watcher = chokidar_1.default.watch(directory);
|
|
38
|
-
watcher.on("change", fileChanged);
|
|
39
|
-
watcher.on("error", (error) => {
|
|
40
|
-
Logger_1.logger.error(`Watcher error: ${error.message}`);
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
async function fileChanged(path) {
|
|
44
|
-
pushQueue.push(path);
|
|
45
|
-
processQueue();
|
|
46
|
-
}
|
|
47
|
-
function stopWatching() {
|
|
48
|
-
if (watcher) {
|
|
49
|
-
watcher.close();
|
|
50
|
-
}
|
|
51
|
-
}
|
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
function createMockWatcher() {
|
|
7
|
-
const handlers = {};
|
|
8
|
-
const mock = {
|
|
9
|
-
_handlers: handlers,
|
|
10
|
-
on: jest.fn((event, handler) => {
|
|
11
|
-
if (!handlers[event])
|
|
12
|
-
handlers[event] = [];
|
|
13
|
-
handlers[event].push(handler);
|
|
14
|
-
return mock;
|
|
15
|
-
}),
|
|
16
|
-
close: jest.fn(),
|
|
17
|
-
_emit: (event, ...args) => {
|
|
18
|
-
(handlers[event] || []).forEach((h) => h(...args));
|
|
19
|
-
},
|
|
20
|
-
};
|
|
21
|
-
return mock;
|
|
22
|
-
}
|
|
23
|
-
let latestMockWatcher;
|
|
24
|
-
jest.mock("chokidar", () => ({
|
|
25
|
-
watch: jest.fn(() => {
|
|
26
|
-
latestMockWatcher = createMockWatcher();
|
|
27
|
-
return latestMockWatcher;
|
|
28
|
-
}),
|
|
29
|
-
}));
|
|
30
|
-
// Controllable debounce — captures fn, tests trigger manually
|
|
31
|
-
let capturedProcessQueue;
|
|
32
|
-
jest.mock("lodash", () => {
|
|
33
|
-
const actual = jest.requireActual("lodash");
|
|
34
|
-
return {
|
|
35
|
-
...actual,
|
|
36
|
-
debounce: jest.fn((fn) => {
|
|
37
|
-
capturedProcessQueue = fn;
|
|
38
|
-
const wrapper = jest.fn((...args) => {
|
|
39
|
-
// Don't call automatically — tests call capturedProcessQueue()
|
|
40
|
-
});
|
|
41
|
-
wrapper.cancel = jest.fn();
|
|
42
|
-
wrapper.flush = jest.fn(() => fn());
|
|
43
|
-
return wrapper;
|
|
44
|
-
}),
|
|
45
|
-
};
|
|
46
|
-
});
|
|
47
|
-
jest.mock("../FileUtils", () => ({
|
|
48
|
-
getFileContextFromPath: jest.fn(),
|
|
49
|
-
}));
|
|
50
|
-
jest.mock("../appUtils", () => ({
|
|
51
|
-
groupAppFiles: jest.fn(),
|
|
52
|
-
pushFiles: jest.fn(),
|
|
53
|
-
}));
|
|
54
|
-
jest.mock("../logMessages", () => ({
|
|
55
|
-
logFilePush: jest.fn(),
|
|
56
|
-
}));
|
|
57
|
-
jest.mock("../Logger", () => ({
|
|
58
|
-
logger: {
|
|
59
|
-
info: jest.fn(),
|
|
60
|
-
error: jest.fn(),
|
|
61
|
-
warn: jest.fn(),
|
|
62
|
-
debug: jest.fn(),
|
|
63
|
-
success: jest.fn(),
|
|
64
|
-
},
|
|
65
|
-
}));
|
|
66
|
-
// --- Imports (after mocks) ---
|
|
67
|
-
const chokidar_1 = __importDefault(require("chokidar"));
|
|
68
|
-
const Watcher_1 = require("../Watcher");
|
|
69
|
-
const FileUtils_1 = require("../FileUtils");
|
|
70
|
-
const appUtils_1 = require("../appUtils");
|
|
71
|
-
const logMessages_1 = require("../logMessages");
|
|
72
|
-
const Logger_1 = require("../Logger");
|
|
73
|
-
// --- Test Fixtures ---
|
|
74
|
-
const MOCK_FILE_PATH = "/project/src/sys_script_include/TestScript/script.js";
|
|
75
|
-
const MOCK_FILE_PATH_2 = "/project/src/sys_script_include/OtherScript/script.js";
|
|
76
|
-
const makeFileContext = (overrides = {}) => ({
|
|
77
|
-
filePath: MOCK_FILE_PATH,
|
|
78
|
-
ext: ".js",
|
|
79
|
-
sys_id: "abc123",
|
|
80
|
-
name: "TestScript",
|
|
81
|
-
scope: "x_test",
|
|
82
|
-
tableName: "sys_script_include",
|
|
83
|
-
targetField: "script",
|
|
84
|
-
...overrides,
|
|
85
|
-
});
|
|
86
|
-
const MOCK_BUILDABLE = {
|
|
87
|
-
table: "sys_script_include",
|
|
88
|
-
sysId: "abc123",
|
|
89
|
-
fields: {},
|
|
90
|
-
};
|
|
91
|
-
const MOCK_PUSH_SUCCESS = {
|
|
92
|
-
success: true,
|
|
93
|
-
message: "Pushed successfully",
|
|
94
|
-
};
|
|
95
|
-
// --- Tests ---
|
|
96
|
-
describe("Watcher", () => {
|
|
97
|
-
beforeEach(() => {
|
|
98
|
-
jest.clearAllMocks();
|
|
99
|
-
});
|
|
100
|
-
afterEach(() => {
|
|
101
|
-
(0, Watcher_1.stopWatching)();
|
|
102
|
-
});
|
|
103
|
-
describe("startWatching", () => {
|
|
104
|
-
it("calls chokidar.watch with the given directory", () => {
|
|
105
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
106
|
-
expect(chokidar_1.default.watch).toHaveBeenCalledWith("/project/src");
|
|
107
|
-
});
|
|
108
|
-
it("registers change event handler", () => {
|
|
109
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
110
|
-
expect(latestMockWatcher.on).toHaveBeenCalledWith("change", expect.any(Function));
|
|
111
|
-
});
|
|
112
|
-
it("registers error event handler", () => {
|
|
113
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
114
|
-
expect(latestMockWatcher.on).toHaveBeenCalledWith("error", expect.any(Function));
|
|
115
|
-
});
|
|
116
|
-
});
|
|
117
|
-
describe("file change processing", () => {
|
|
118
|
-
it("processes a changed file through the full pipeline", async () => {
|
|
119
|
-
const ctx = makeFileContext();
|
|
120
|
-
FileUtils_1.getFileContextFromPath.mockReturnValue(ctx);
|
|
121
|
-
appUtils_1.groupAppFiles.mockReturnValue([MOCK_BUILDABLE]);
|
|
122
|
-
appUtils_1.pushFiles.mockResolvedValue([MOCK_PUSH_SUCCESS]);
|
|
123
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
124
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH);
|
|
125
|
-
// Manually trigger the debounced processQueue
|
|
126
|
-
await capturedProcessQueue();
|
|
127
|
-
expect(FileUtils_1.getFileContextFromPath).toHaveBeenCalledWith(MOCK_FILE_PATH, 0, [MOCK_FILE_PATH]);
|
|
128
|
-
expect(appUtils_1.groupAppFiles).toHaveBeenCalledWith([ctx]);
|
|
129
|
-
expect(appUtils_1.pushFiles).toHaveBeenCalledWith([MOCK_BUILDABLE]);
|
|
130
|
-
expect(logMessages_1.logFilePush).toHaveBeenCalledWith(ctx, MOCK_PUSH_SUCCESS);
|
|
131
|
-
});
|
|
132
|
-
it("deduplicates multiple changes to the same file", async () => {
|
|
133
|
-
const ctx = makeFileContext();
|
|
134
|
-
FileUtils_1.getFileContextFromPath.mockReturnValue(ctx);
|
|
135
|
-
appUtils_1.groupAppFiles.mockReturnValue([MOCK_BUILDABLE]);
|
|
136
|
-
appUtils_1.pushFiles.mockResolvedValue([MOCK_PUSH_SUCCESS]);
|
|
137
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
138
|
-
// Emit the same file path three times before processing
|
|
139
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH);
|
|
140
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH);
|
|
141
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH);
|
|
142
|
-
await capturedProcessQueue();
|
|
143
|
-
// Should only process the file once due to Set dedup
|
|
144
|
-
expect(FileUtils_1.getFileContextFromPath).toHaveBeenCalledTimes(1);
|
|
145
|
-
});
|
|
146
|
-
it("batches changes to multiple files", async () => {
|
|
147
|
-
const ctx1 = makeFileContext();
|
|
148
|
-
const ctx2 = makeFileContext({
|
|
149
|
-
filePath: MOCK_FILE_PATH_2,
|
|
150
|
-
sys_id: "def456",
|
|
151
|
-
name: "OtherScript",
|
|
152
|
-
});
|
|
153
|
-
FileUtils_1.getFileContextFromPath
|
|
154
|
-
.mockReturnValueOnce(ctx1)
|
|
155
|
-
.mockReturnValueOnce(ctx2);
|
|
156
|
-
appUtils_1.groupAppFiles.mockReturnValue([MOCK_BUILDABLE]);
|
|
157
|
-
appUtils_1.pushFiles.mockResolvedValue([MOCK_PUSH_SUCCESS]);
|
|
158
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
159
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH);
|
|
160
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH_2);
|
|
161
|
-
await capturedProcessQueue();
|
|
162
|
-
expect(FileUtils_1.getFileContextFromPath).toHaveBeenCalledTimes(2);
|
|
163
|
-
expect(appUtils_1.groupAppFiles).toHaveBeenCalledWith([ctx1, ctx2]);
|
|
164
|
-
});
|
|
165
|
-
it("filters out files where getFileContextFromPath returns undefined", async () => {
|
|
166
|
-
const ctx = makeFileContext();
|
|
167
|
-
FileUtils_1.getFileContextFromPath
|
|
168
|
-
.mockReturnValueOnce(undefined)
|
|
169
|
-
.mockReturnValueOnce(ctx);
|
|
170
|
-
appUtils_1.groupAppFiles.mockReturnValue([MOCK_BUILDABLE]);
|
|
171
|
-
appUtils_1.pushFiles.mockResolvedValue([MOCK_PUSH_SUCCESS]);
|
|
172
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
173
|
-
latestMockWatcher._emit("change", "/project/src/unknown/file.txt");
|
|
174
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH);
|
|
175
|
-
await capturedProcessQueue();
|
|
176
|
-
// groupAppFiles should only receive the valid context
|
|
177
|
-
expect(appUtils_1.groupAppFiles).toHaveBeenCalledWith([ctx]);
|
|
178
|
-
});
|
|
179
|
-
it("does not process when queue is empty", async () => {
|
|
180
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
181
|
-
// Trigger processQueue without emitting any events
|
|
182
|
-
await capturedProcessQueue();
|
|
183
|
-
expect(FileUtils_1.getFileContextFromPath).not.toHaveBeenCalled();
|
|
184
|
-
expect(appUtils_1.groupAppFiles).not.toHaveBeenCalled();
|
|
185
|
-
expect(appUtils_1.pushFiles).not.toHaveBeenCalled();
|
|
186
|
-
});
|
|
187
|
-
it("calls logFilePush for each result paired with its file context", async () => {
|
|
188
|
-
const ctx1 = makeFileContext();
|
|
189
|
-
const ctx2 = makeFileContext({
|
|
190
|
-
filePath: MOCK_FILE_PATH_2,
|
|
191
|
-
sys_id: "def456",
|
|
192
|
-
name: "OtherScript",
|
|
193
|
-
});
|
|
194
|
-
const result1 = { success: true, message: "ok" };
|
|
195
|
-
const result2 = { success: false, message: "failed" };
|
|
196
|
-
FileUtils_1.getFileContextFromPath
|
|
197
|
-
.mockReturnValueOnce(ctx1)
|
|
198
|
-
.mockReturnValueOnce(ctx2);
|
|
199
|
-
appUtils_1.groupAppFiles.mockReturnValue([MOCK_BUILDABLE, MOCK_BUILDABLE]);
|
|
200
|
-
appUtils_1.pushFiles.mockResolvedValue([result1, result2]);
|
|
201
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
202
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH);
|
|
203
|
-
latestMockWatcher._emit("change", MOCK_FILE_PATH_2);
|
|
204
|
-
await capturedProcessQueue();
|
|
205
|
-
expect(logMessages_1.logFilePush).toHaveBeenCalledTimes(2);
|
|
206
|
-
expect(logMessages_1.logFilePush).toHaveBeenCalledWith(ctx1, result1);
|
|
207
|
-
expect(logMessages_1.logFilePush).toHaveBeenCalledWith(ctx2, result2);
|
|
208
|
-
});
|
|
209
|
-
});
|
|
210
|
-
describe("error handling", () => {
|
|
211
|
-
it("logs error when chokidar emits error event", () => {
|
|
212
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
213
|
-
const error = new Error("Watch failed");
|
|
214
|
-
latestMockWatcher._emit("error", error);
|
|
215
|
-
expect(Logger_1.logger.error).toHaveBeenCalledWith("Watcher error: Watch failed");
|
|
216
|
-
});
|
|
217
|
-
});
|
|
218
|
-
describe("stopWatching", () => {
|
|
219
|
-
it("calls watcher.close() when watcher exists", () => {
|
|
220
|
-
(0, Watcher_1.startWatching)("/project/src");
|
|
221
|
-
(0, Watcher_1.stopWatching)();
|
|
222
|
-
expect(latestMockWatcher.close).toHaveBeenCalled();
|
|
223
|
-
});
|
|
224
|
-
it("does not throw when called before startWatching", () => {
|
|
225
|
-
expect(() => (0, Watcher_1.stopWatching)()).not.toThrow();
|
|
226
|
-
});
|
|
227
|
-
});
|
|
228
|
-
});
|