@speedkit/cli 4.4.0 → 4.4.2
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/CHANGELOG.md +14 -0
- package/README.md +1 -1
- package/dist/commands/onboarding.js +13 -1
- package/dist/helpers/evaluate-speed-kit-config.js +12 -4
- package/dist/services/integration-api/integration-api-model.d.ts +3 -1
- package/dist/services/integration-api/struct/file.d.ts +3 -1
- package/dist/services/integration-api/struct/file.js +26 -8
- package/dist/services/onboarding/file-events/file-watcher.d.ts +1 -1
- package/dist/services/onboarding/file-events/file-watcher.js +10 -10
- package/dist/services/onboarding/onboarding-service.d.ts +5 -2
- package/dist/services/onboarding/onboarding-service.js +28 -10
- package/oclif.manifest.json +1 -1
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [4.4.2](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.4.1...v4.4.2) (2026-05-13)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **onboarding:** detect atomic file writes and shut down gracefully ([a62f5a9](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/a62f5a9c134cd675ced842920e418b0a935f76fc))
|
|
7
|
+
|
|
8
|
+
## [4.4.1](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.4.0...v4.4.1) (2026-05-13)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **sk-config:** mock methods ([d479dcf](https://gitlab.orestes.info/baqend/speed-kit-cli/commit/d479dcff73b0251ced9c160623b92831cc39648e))
|
|
14
|
+
|
|
1
15
|
# [4.4.0](https://gitlab.orestes.info/baqend/speed-kit-cli/compare/v4.3.0...v4.4.0) (2026-05-13)
|
|
2
16
|
|
|
3
17
|
|
package/README.md
CHANGED
|
@@ -13,6 +13,18 @@ export default class Onboarding extends Command {
|
|
|
13
13
|
async run() {
|
|
14
14
|
const { args: { customerPath }, flags: { configName, domain, local, verboseLevel, ignoreContentSecurityPolicy, useTestConfig, debuggingPort, headless, }, } = await this.parse(Onboarding);
|
|
15
15
|
const service = await new OnboardingServiceFactory(new OnboardingContext(customerPath, configName, domain, local, ignoreContentSecurityPolicy, this.config.root, verboseLevel, useTestConfig, debuggingPort, headless), new CliConfig(this.config).load(customerPath)).getService();
|
|
16
|
-
|
|
16
|
+
const shutdown = new AbortController();
|
|
17
|
+
const onSignal = () => shutdown.abort();
|
|
18
|
+
// `once`: a second Ctrl+C falls through to Node's default handler and
|
|
19
|
+
// force-quits if cleanup hangs.
|
|
20
|
+
process.once("SIGINT", onSignal);
|
|
21
|
+
process.once("SIGTERM", onSignal);
|
|
22
|
+
try {
|
|
23
|
+
await service.run(shutdown.signal);
|
|
24
|
+
}
|
|
25
|
+
finally {
|
|
26
|
+
process.off("SIGINT", onSignal);
|
|
27
|
+
process.off("SIGTERM", onSignal);
|
|
28
|
+
}
|
|
17
29
|
}
|
|
18
30
|
}
|
|
@@ -56,10 +56,18 @@ function buildBrowserMock(origin, app) {
|
|
|
56
56
|
serviceWorkerUrl: "",
|
|
57
57
|
screen: { width: 0, height: 0 },
|
|
58
58
|
devicePixelRatio: 1,
|
|
59
|
-
setTimeout: (
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
59
|
+
setTimeout: () => {
|
|
60
|
+
/* empty */
|
|
61
|
+
},
|
|
62
|
+
clearTimeout: () => {
|
|
63
|
+
/* empty */
|
|
64
|
+
},
|
|
65
|
+
addEventListener: () => {
|
|
66
|
+
/* empty */
|
|
67
|
+
},
|
|
68
|
+
removeEventListener: () => {
|
|
69
|
+
/* empty */
|
|
70
|
+
},
|
|
63
71
|
navigator: {
|
|
64
72
|
userAgent: "",
|
|
65
73
|
connection: {},
|
|
@@ -70,7 +70,9 @@ export interface ConfigFileInterface extends FileInterface {
|
|
|
70
70
|
updateFile(): Promise<void>;
|
|
71
71
|
updateIfChanged(): Promise<void>;
|
|
72
72
|
isUpdated(): boolean;
|
|
73
|
-
onChange(callback?: (file: FileInterface) => Promise<void
|
|
73
|
+
onChange(callback?: (file: FileInterface) => Promise<void>, options?: {
|
|
74
|
+
signal?: AbortSignal;
|
|
75
|
+
}): void;
|
|
74
76
|
delete(): Promise<void>;
|
|
75
77
|
}
|
|
76
78
|
export interface ModuleInterface extends ConfigFileInterface, FileInterface {
|
|
@@ -14,6 +14,8 @@ export declare class File implements ConfigFileInterface, FileInterface {
|
|
|
14
14
|
isUpdated(): boolean;
|
|
15
15
|
updateFile(): Promise<void>;
|
|
16
16
|
updateIfChanged(): Promise<void>;
|
|
17
|
-
onChange(callback: (file: FileInterface) => Promise<void
|
|
17
|
+
onChange(callback: (file: FileInterface) => Promise<void>, options?: {
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
}): void;
|
|
18
20
|
delete(): Promise<void>;
|
|
19
21
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { readFile, unlink,
|
|
1
|
+
import { readFile, unlink, writeFile } from "node:fs/promises";
|
|
2
|
+
import { watch as chokidarWatch } from "chokidar";
|
|
2
3
|
import { FILE_TYPE, } from "../integration-api-model.js";
|
|
3
4
|
export class File {
|
|
4
5
|
name;
|
|
@@ -41,15 +42,32 @@ export class File {
|
|
|
41
42
|
await this.updateFile();
|
|
42
43
|
}
|
|
43
44
|
}
|
|
44
|
-
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
45
|
+
onChange(callback, options = {}) {
|
|
46
|
+
const { signal } = options;
|
|
47
|
+
if (signal?.aborted) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// chokidar over node's fs.watch: handles atomic write-then-rename
|
|
51
|
+
// (Claude Code, VS Code's atomicSave, vim writebackup, …) which fs.watch
|
|
52
|
+
// misses because inotify is inode-bound and the new file gets a new inode.
|
|
53
|
+
const watcher = chokidarWatch(this.path, {
|
|
54
|
+
ignoreInitial: true,
|
|
55
|
+
atomic: true,
|
|
56
|
+
awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },
|
|
57
|
+
});
|
|
58
|
+
const handle = async () => {
|
|
59
|
+
const content = await readFile(this.path, "utf-8");
|
|
60
|
+
if (content === this.content) {
|
|
61
|
+
return;
|
|
49
62
|
}
|
|
50
|
-
this.setContent(
|
|
63
|
+
this.setContent(content);
|
|
51
64
|
await callback(this);
|
|
52
|
-
}
|
|
65
|
+
};
|
|
66
|
+
watcher.on("change", handle);
|
|
67
|
+
watcher.on("add", handle);
|
|
68
|
+
signal?.addEventListener("abort", () => void watcher.close(), {
|
|
69
|
+
once: true,
|
|
70
|
+
});
|
|
53
71
|
}
|
|
54
72
|
async delete() {
|
|
55
73
|
await unlink(this.path);
|
|
@@ -21,7 +21,7 @@ export declare class FileWatcher {
|
|
|
21
21
|
* @param page
|
|
22
22
|
* @param serviceWorkerReinstallCallback
|
|
23
23
|
*/
|
|
24
|
-
addCallbackOnFileChange(page: Page, serviceWorkerReinstallCallback: (file: ConfigFileInterface) => Promise<void
|
|
24
|
+
addCallbackOnFileChange(page: Page, serviceWorkerReinstallCallback: (file: ConfigFileInterface) => Promise<void>, signal: AbortSignal): void;
|
|
25
25
|
/**
|
|
26
26
|
* changes on these files will trigger
|
|
27
27
|
* - a rebuild of the serviceWorker
|
|
@@ -26,11 +26,11 @@ export class FileWatcher {
|
|
|
26
26
|
* @param page
|
|
27
27
|
* @param serviceWorkerReinstallCallback
|
|
28
28
|
*/
|
|
29
|
-
addCallbackOnFileChange(page, serviceWorkerReinstallCallback) {
|
|
29
|
+
addCallbackOnFileChange(page, serviceWorkerReinstallCallback, signal) {
|
|
30
30
|
const installResource = this.files.getByName(INTEGRATION_FILES.BUILD.INSTALL);
|
|
31
|
-
this.handleServiceWorkerDependencies(page, installResource, serviceWorkerReinstallCallback);
|
|
32
|
-
this.handleGenericConfigFiles(page, installResource);
|
|
33
|
-
this.handleDynamicStyles(page);
|
|
31
|
+
this.handleServiceWorkerDependencies(page, installResource, serviceWorkerReinstallCallback, signal);
|
|
32
|
+
this.handleGenericConfigFiles(page, installResource, signal);
|
|
33
|
+
this.handleDynamicStyles(page, signal);
|
|
34
34
|
}
|
|
35
35
|
/**
|
|
36
36
|
* changes on these files will trigger
|
|
@@ -44,7 +44,7 @@ export class FileWatcher {
|
|
|
44
44
|
* @param serviceWorkerReinstallCallback
|
|
45
45
|
* @private
|
|
46
46
|
*/
|
|
47
|
-
handleServiceWorkerDependencies(page, installResource, serviceWorkerReinstallCallback) {
|
|
47
|
+
handleServiceWorkerDependencies(page, installResource, serviceWorkerReinstallCallback, signal) {
|
|
48
48
|
// add watcher to serviceWorker related files
|
|
49
49
|
const serviceWorkerDependencies = [
|
|
50
50
|
INTEGRATION_FILES.CONFIG.SPEED_KIT,
|
|
@@ -65,7 +65,7 @@ export class FileWatcher {
|
|
|
65
65
|
if (result.success === false) {
|
|
66
66
|
this.cli.writeError(`error after fileChange: ${file.name}: ${result.error}`);
|
|
67
67
|
}
|
|
68
|
-
});
|
|
68
|
+
}, { signal });
|
|
69
69
|
}
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
@@ -78,7 +78,7 @@ export class FileWatcher {
|
|
|
78
78
|
* @param installResource
|
|
79
79
|
* @private
|
|
80
80
|
*/
|
|
81
|
-
handleGenericConfigFiles(page, installResource) {
|
|
81
|
+
handleGenericConfigFiles(page, installResource, signal) {
|
|
82
82
|
// add watcher to configFiles
|
|
83
83
|
const generalConfigFiles = [
|
|
84
84
|
INTEGRATION_FILES.CONFIG.DYNAMIC_BLOCKS,
|
|
@@ -99,7 +99,7 @@ export class FileWatcher {
|
|
|
99
99
|
if (result.success === false) {
|
|
100
100
|
this.cli.writeError(`error after fileChange: ${file.name}: ${result.error}`);
|
|
101
101
|
}
|
|
102
|
-
});
|
|
102
|
+
}, { signal });
|
|
103
103
|
}
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
@@ -112,7 +112,7 @@ export class FileWatcher {
|
|
|
112
112
|
* @param page
|
|
113
113
|
* @private
|
|
114
114
|
*/
|
|
115
|
-
handleDynamicStyles(page) {
|
|
115
|
+
handleDynamicStyles(page, signal) {
|
|
116
116
|
if (!this.files.hasFile(INTEGRATION_FILES.CONFIG.DYNAMIC_STYLES)) {
|
|
117
117
|
return;
|
|
118
118
|
}
|
|
@@ -124,7 +124,7 @@ export class FileWatcher {
|
|
|
124
124
|
if (result.success === false) {
|
|
125
125
|
this.cli.writeError(`error after fileChange: ${file.name}: ${result.error}`);
|
|
126
126
|
}
|
|
127
|
-
});
|
|
127
|
+
}, { signal });
|
|
128
128
|
}
|
|
129
129
|
/**
|
|
130
130
|
* fileWatcherCallback that will reinstall the serviceWorker
|
|
@@ -17,9 +17,12 @@ export declare class OnboardingService {
|
|
|
17
17
|
private reinstallOnNextNavigate;
|
|
18
18
|
constructor(browserContext: BrowserContext, customerConfig: CustomerConfig, fetchEventHandler: FetchEventHandler, fileWatcher: FileWatcher, cli: CliService, developmentToolsMessages: DevtoolsExtensionApi);
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
20
|
+
* Prepare the puppeteer browser, wire event listeners, navigate to the
|
|
21
|
+
* install page, then block until either the caller aborts the signal
|
|
22
|
+
* (e.g. SIGINT in the command) or the browser disconnects. On exit, close
|
|
23
|
+
* any still-open resources. File watchers close themselves via `signal`.
|
|
21
24
|
*/
|
|
22
|
-
run(): Promise<void>;
|
|
25
|
+
run(externalSignal: AbortSignal): Promise<void>;
|
|
23
26
|
private prepareBrowser;
|
|
24
27
|
private preparePage;
|
|
25
28
|
private findPage;
|
|
@@ -21,16 +21,26 @@ export class OnboardingService {
|
|
|
21
21
|
this.developmentToolsMessages = developmentToolsMessages;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
|
-
*
|
|
24
|
+
* Prepare the puppeteer browser, wire event listeners, navigate to the
|
|
25
|
+
* install page, then block until either the caller aborts the signal
|
|
26
|
+
* (e.g. SIGINT in the command) or the browser disconnects. On exit, close
|
|
27
|
+
* any still-open resources. File watchers close themselves via `signal`.
|
|
25
28
|
*/
|
|
26
|
-
async run() {
|
|
29
|
+
async run(externalSignal) {
|
|
27
30
|
this.cli.startAction("ONBOARDING:PREPARE:BROWSER", `prepare browser ${this.cli.style.bold(this.browserContext.browserVersionString)}`);
|
|
28
|
-
const
|
|
29
|
-
const
|
|
31
|
+
const localController = new AbortController();
|
|
32
|
+
const shutdownSignal = AbortSignal.any([
|
|
33
|
+
externalSignal,
|
|
34
|
+
localController.signal,
|
|
35
|
+
]);
|
|
36
|
+
const browser = await this.prepareBrowser(localController);
|
|
37
|
+
const page = await this.preparePage(browser, shutdownSignal);
|
|
30
38
|
this.cli.successAction("ONBOARDING:PREPARE:BROWSER");
|
|
31
39
|
await this.navigate(page, `${this.customerConfig.scope}speed-kit-install.html?url=${this.customerConfig.scope}`);
|
|
40
|
+
await waitForAbort(shutdownSignal);
|
|
41
|
+
await safe(browser.close());
|
|
32
42
|
}
|
|
33
|
-
async prepareBrowser() {
|
|
43
|
+
async prepareBrowser(localController) {
|
|
34
44
|
const browser = await puppeteerExtra.default.launch(this.browserContext.getPuppeteerLaunchOptions());
|
|
35
45
|
for (const target of browser.targets()) {
|
|
36
46
|
if (target.type() === "browser") {
|
|
@@ -39,14 +49,14 @@ export class OnboardingService {
|
|
|
39
49
|
}
|
|
40
50
|
browser.on(BROWSER_EVENTS.DISCONNECTED, () => {
|
|
41
51
|
this.cli.writeError("Browser disconnected, shutting down");
|
|
42
|
-
|
|
52
|
+
localController.abort();
|
|
43
53
|
});
|
|
44
54
|
return browser;
|
|
45
55
|
}
|
|
46
|
-
async preparePage(browser) {
|
|
56
|
+
async preparePage(browser, shutdownSignal) {
|
|
47
57
|
const page = await this.findPage(browser);
|
|
48
58
|
await safe(this.prepareCleanState(page));
|
|
49
|
-
this.registerFileWatcher(page);
|
|
59
|
+
this.registerFileWatcher(page, shutdownSignal);
|
|
50
60
|
this.registerReinstallSpeedKitOnNavigate(page);
|
|
51
61
|
this.developmentToolsMessages.addMessage({
|
|
52
62
|
id: "cli",
|
|
@@ -106,10 +116,10 @@ export class OnboardingService {
|
|
|
106
116
|
this.reinstallOnNextNavigate = false;
|
|
107
117
|
await this.navigate(page, target);
|
|
108
118
|
}
|
|
109
|
-
registerFileWatcher(page) {
|
|
119
|
+
registerFileWatcher(page, signal) {
|
|
110
120
|
this.fileWatcher.addCallbackOnFileChange(page, async () => {
|
|
111
121
|
this.reinstallOnNextNavigate = true;
|
|
112
|
-
});
|
|
122
|
+
}, signal);
|
|
113
123
|
}
|
|
114
124
|
registerReinstallSpeedKitOnNavigate(page) {
|
|
115
125
|
page.on("framenavigated", async (frame) => {
|
|
@@ -127,3 +137,11 @@ export class OnboardingService {
|
|
|
127
137
|
await safe(page.goto(goto.toString()));
|
|
128
138
|
}
|
|
129
139
|
}
|
|
140
|
+
function waitForAbort(signal) {
|
|
141
|
+
if (signal.aborted) {
|
|
142
|
+
return Promise.resolve();
|
|
143
|
+
}
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
signal.addEventListener("abort", () => resolve(), { once: true });
|
|
146
|
+
});
|
|
147
|
+
}
|
package/oclif.manifest.json
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@speedkit/cli",
|
|
3
3
|
"description": "Speed Kit CLI",
|
|
4
|
-
"version": "4.4.
|
|
4
|
+
"version": "4.4.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Baqend.com",
|
|
7
7
|
"email": "info@baqend.com"
|
|
@@ -94,6 +94,7 @@
|
|
|
94
94
|
"@oclif/plugin-warn-if-update-available": "^3.1.61",
|
|
95
95
|
"baqend": "^4.2.5",
|
|
96
96
|
"chalk": "^5.6.2",
|
|
97
|
+
"chokidar": "^5.0.0",
|
|
97
98
|
"cli-progress": "^3.12.0",
|
|
98
99
|
"clipboardy": "^5.3.1",
|
|
99
100
|
"deepmerge": "^4.3.1",
|