@sleekcms/sync 1.2.3 → 1.4.0
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 +5 -17
- package/dist/AGENT.md +148 -4
- package/dist/watcher.js +20 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
**Build complete websites with AI. Edit them locally. Deploy instantly.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
SleekCMS Sync brings your CMS to your code editor — with full AI-agent support baked in. Spin up an entirely new site by describing it in plain English, or drop into any existing site and edit templates, content, and styles the way you'd expect: locally, with real files, in your editor of choice.
|
|
6
6
|
|
|
7
7
|
Every save auto-syncs. Every change goes live. No build steps. No Git hooks. No infrastructure to manage.
|
|
8
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## How does it work
|
|
12
12
|
|
|
13
13
|
- **AI generates your entire site from a description** — models, templates, layouts, styles, and content, all wired up and ready to go.
|
|
14
14
|
- **AI-generated sites are plain files** — EJS templates, JSON content, CSS, JS. You own every line. Open in VS Code, Cursor, or any editor.
|
|
@@ -24,7 +24,7 @@ Every save auto-syncs. Every change goes live. No build steps. No Git hooks. No
|
|
|
24
24
|
npx @sleekcms/sync --token <YOUR_AUTH_TOKEN>
|
|
25
25
|
```
|
|
26
26
|
|
|
27
|
-
That's it. The CLI fetches your site, opens an editor prompt, and starts watching for changes. Grab a token from your [SleekCMS
|
|
27
|
+
That's it. The CLI fetches your site, opens an editor prompt, and starts watching for changes. Grab a token from your [SleekCMS > Builder](https://app.sleekcms.com).
|
|
28
28
|
|
|
29
29
|
---
|
|
30
30
|
|
|
@@ -47,24 +47,10 @@ sleekcms --token <YOUR_AUTH_TOKEN>
|
|
|
47
47
|
|
|
48
48
|
## Usage
|
|
49
49
|
|
|
50
|
-
```bash
|
|
51
|
-
npx @sleekcms/sync [OPTIONS]
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
| Option | Alias | Description | Default |
|
|
55
|
-
|--------------------|-------|--------------------------------------------------------|---------------|
|
|
56
|
-
| `--token <token>` | `-t` | Your SleekCMS CLI auth token (required) | — |
|
|
57
|
-
| `--path <path>` | `-p` | Parent directory for the local workspace | `~/.sleekcms` |
|
|
58
|
-
| `--help` | `-h` | Show help | — |
|
|
59
|
-
|
|
60
|
-
> The actual workspace folder is created at `<path>/<site-name>-<site-id>/`.
|
|
61
|
-
|
|
62
50
|
```bash
|
|
63
51
|
# Basic
|
|
64
52
|
npx @sleekcms/sync --token abc123-xxxx
|
|
65
53
|
|
|
66
|
-
# Custom workspace folder
|
|
67
|
-
npx @sleekcms/sync -t abc123-xxxx -p ~/Sites
|
|
68
54
|
```
|
|
69
55
|
|
|
70
56
|
Once running, press `r` to re-fetch all files or `x` / `Ctrl+C` to exit.
|
|
@@ -272,12 +258,14 @@ Models describe the shape of your content. They're JSON-like files with unquoted
|
|
|
272
258
|
| `markdown` | Markdown string |
|
|
273
259
|
| `number` | Number |
|
|
274
260
|
| `boolean` | `true` / `false` |
|
|
261
|
+
| `emoji` | Single Unicode emoji character, e.g. `"😀"` |
|
|
275
262
|
| `date` | `YYYY-MM-DD` |
|
|
276
263
|
| `image` | `{ url, alt }` |
|
|
277
264
|
| `video` | `{ url, embed }` |
|
|
278
265
|
| `link` | URL string |
|
|
279
266
|
| `json` | Object or array |
|
|
280
267
|
| `block(key)` | Embedded block object |
|
|
268
|
+
| `stack` | Array of heterogeneous block objects; each item carries `_block: "<block_key>"` to name its block |
|
|
281
269
|
| `entry(key)` | Entry object or slug reference |
|
|
282
270
|
|
|
283
271
|
---
|
package/dist/AGENT.md
CHANGED
|
@@ -52,6 +52,7 @@ Examples:
|
|
|
52
52
|
|
|
53
53
|
/content/pages/<key>.json Content for a single (non-list) page
|
|
54
54
|
/content/pages/<key>/<slug>.json Content for one item of a collection page (<key> ends with +)
|
|
55
|
+
/content/pages/<key>/<slug>.md Same as above, but as Markdown with YAML frontmatter — used when the model qualifies (see "Markdown content files" below)
|
|
55
56
|
/content/entries/<key>.json Content for a single entry (object)
|
|
56
57
|
/content/entries/<key+>.json Content for a collection entry (array of objects; <key>+ matches the model filename)
|
|
57
58
|
|
|
@@ -188,6 +189,62 @@ Block models cannot contain other blocks. Use groups (`{ ... }`) or collections
|
|
|
188
189
|
|
|
189
190
|
Repeatable blocks use the same collection syntax as groups and entries: `[block(cta)]`. Use this sparingly, only when each repeated item should use the same reusable block template. For simple one-page repeated content, prefer a repeating group (`[{ ... }]`). For reusable content selected across pages, prefer a collection entry plus `[entry(key)]`.
|
|
190
191
|
|
|
192
|
+
### Stacks
|
|
193
|
+
|
|
194
|
+
A **stack** is a heterogeneous list of blocks: each item picks its own block schema. Use a stack when a page section can contain a mix of different reusable components in an editor-chosen order. If every item would use the same block, prefer `[block(key)]` instead — stacks are for "section A could be a hero, a CTA, a quote, or a gallery."
|
|
195
|
+
|
|
196
|
+
**Model syntax** — bare `stack`, no parentheses and no `[]`. Stacks are always multiple by definition:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
{
|
|
200
|
+
title: text,
|
|
201
|
+
sections: stack
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
Any block defined in `models/blocks/` may appear in any stack — the allowed set is the full block library, not a per-field allow-list.
|
|
206
|
+
|
|
207
|
+
**Content shape** — an array of objects. Each item is shaped like the block model it uses, plus a `_block` marker naming that block by key (as a plain string):
|
|
208
|
+
|
|
209
|
+
**`content/pages/about.json`**
|
|
210
|
+
```json
|
|
211
|
+
{
|
|
212
|
+
"title": "About us",
|
|
213
|
+
"sections": [
|
|
214
|
+
{
|
|
215
|
+
"_block": "hero",
|
|
216
|
+
"heading": "Care that feels personal",
|
|
217
|
+
"subheading": "A small team focused on long-term relationships.",
|
|
218
|
+
"background": "pexels:doctor"
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
"_block": "cta",
|
|
222
|
+
"label": "Book a visit",
|
|
223
|
+
"link": "/contact"
|
|
224
|
+
}
|
|
225
|
+
]
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The `_block` value is the block's key (e.g., `"hero"`, `"cta"`) — never a numeric id. The CMS resolves it server-side on save.
|
|
230
|
+
|
|
231
|
+
**Rendering** — each item flows through its own `blocks/<key>.ejs` template. Pass the whole array to `render()` and it dispatches per `_block`:
|
|
232
|
+
|
|
233
|
+
**`pages/about.ejs`**
|
|
234
|
+
```ejs
|
|
235
|
+
<h1><%= item.title %></h1>
|
|
236
|
+
<%- render(item.sections) %>
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
**Stack vs block vs collection** — same decision tree as blocks, with one extra rung:
|
|
240
|
+
|
|
241
|
+
- One-off shape used on one page → group `{ ... }` or `[{ ... }]`.
|
|
242
|
+
- Reusable shape, always the same per item → `block(key)` or `[block(key)]`.
|
|
243
|
+
- Reusable shape, but items can be different blocks chosen by the editor → `stack`.
|
|
244
|
+
- Shared content referenced from many pages → `entry(key)` / `[entry(key)]`.
|
|
245
|
+
|
|
246
|
+
Like blocks, stacks cannot be nested inside block models. Stack items themselves may contain groups, collections, and entry references, but not other stacks or blocks.
|
|
247
|
+
|
|
191
248
|
**Entry reference** — Use `entry(key)` for one, `[entry(key)]` for many:
|
|
192
249
|
```
|
|
193
250
|
{
|
|
@@ -206,16 +263,19 @@ Model fields declare both the editor type and the shape expected in content JSON
|
|
|
206
263
|
| `text`, `paragraph`, `richtext`, `markdown`, `code`, `color`, `link` | String |
|
|
207
264
|
| `number` | Number |
|
|
208
265
|
| `boolean` | `true` / `false` |
|
|
266
|
+
| `emoji` | Single Unicode emoji character string, e.g. `"😀"` or `"👋🏽"` (one emoji per record; not free text) |
|
|
209
267
|
| `date` | `"YYYY-MM-DD"` string |
|
|
210
268
|
| `datetime` | ISO 8601 string |
|
|
211
269
|
| `time` | `"HH:mm"` string |
|
|
212
270
|
| `image` | Either a resolved `{ "url": "...", "alt": "..." }` object, **or** a shortcut string `"<source>:<search>"` (e.g., `"pexels:doctor"`, `"url:https://picsum.photos/200.jpg"`, `"cms:logo"`). Supported sources: `unsplash`, `pexels`, `pixabay`, `iconify`, `url`, `cms` (reuses an image declared in `/images.json` by handle). Append `\|<alt text>` to set the image's alt, e.g. `"pexels:doctor\|Smiling doctor with stethoscope"`. On save, the sync engine resolves the shortcut/link to a full image object automatically. |
|
|
213
|
-
| `video` | `{ "
|
|
271
|
+
| `video` | Object of shape `{ "source": "BUNNY", "video_id": "...", "embed_url": "...", "title": "..." }`. **Do not write or invent this value.** Video uploads are user-driven in the CMS UI — the user uploads to Bunny.net and the sync engine stores the resolved object. In templates, always render videos with the `embed()` helper (e.g. `<%- embed(item.intro_video, { size: '1280x720' }) %>`), never by hand-rolling an iframe. |
|
|
272
|
+
| `file` | Object of shape `{ "name": "...", "url": "...", "size": 0, "type": "...", "ext": "..." }` describing a downloadable document (PDF, CSV, DOC/DOCX, XLS/XLSX, PPT/PPTX, TSV, TXT — max 10 MB). **Do not write or invent this value.** File uploads are user-driven in the CMS UI — the user attaches the file via the editor and the sync engine stores the resolved object. In templates, render a link with `item.file.url` (download URL on `files.sleekcms.com`) and `item.file.name` (display name), e.g. `<a href="<%= item.brochure.url %>" download><%= item.brochure.name %></a>`. |
|
|
214
273
|
| `json` | Object or array |
|
|
215
274
|
| `sheet` | Array of arrays |
|
|
216
275
|
| `location` | `{ "markers": [{ "lat": n, "lng": n }], "img": "..." }`; `img` is a Google Maps Static Image URL that already includes the markers and can include formatting parameters such as `size`, `zoom`, `scale`, `maptype`, and styling |
|
|
217
276
|
| `block(key)` | Object matching that block's model; stored inline in the parent page/entry content and rendered with `blocks/<key>.ejs` |
|
|
218
277
|
| `[block(key)]` | Array of block objects; each item matches the block model and renders with `blocks/<key>.ejs` |
|
|
278
|
+
| `stack` | Array of heterogeneous block-shaped objects. Each item is `{ "_block": "<block_key>", ...fields_of_that_block }`. `_block` is the target block's key as a plain string; the remaining keys must match that block's model. Different items in the same stack may use different blocks. Stored inline; each item renders through its own `blocks/<key>.ejs`. The CMS resolves block keys to ids server-side — never write numeric ids. |
|
|
219
279
|
| `entry(key)` / `[entry(key)]` | Slug string / array of slug strings in content JSON; entry object / array of entry objects in templates |
|
|
220
280
|
| Group `{ ... }` | Nested object |
|
|
221
281
|
| Collection `[{ ... }]` | Array of nested objects |
|
|
@@ -232,10 +292,11 @@ Content files are JSON records under `/content/` that hold the actual values for
|
|
|
232
292
|
|
|
233
293
|
### File layout
|
|
234
294
|
|
|
235
|
-
| Model shape | File path |
|
|
295
|
+
| Model shape | File path | Top-level shape |
|
|
236
296
|
|---|---|---|
|
|
237
297
|
| Single page (e.g., `about`) | `content/pages/about.json` | Object |
|
|
238
298
|
| Collection page (e.g., `blog+`) | `content/pages/blog+/<slug>.json` (the `+` is part of the key, not an extra suffix) | Object; one file per slug |
|
|
299
|
+
| Collection page with one markdown body (see below) | `content/pages/blog+/<slug>.md` | YAML frontmatter + markdown body |
|
|
239
300
|
| Single entry (e.g., `header`) | `content/entries/header.json` | Object |
|
|
240
301
|
| Collection entry (e.g., `authors`) | `content/entries/authors+.json` (the `+` is part of the key — same as the model filename) | Array of objects |
|
|
241
302
|
|
|
@@ -266,6 +327,69 @@ The content file at `content/pages/about.json`:
|
|
|
266
327
|
|
|
267
328
|
Here `hero` is embedded block data stored directly in the page content file. `image` and `hero.background` use the shortcut form — on save, the sync engine replaces each shortcut with a real image object (`{ "url": "...", "alt": "..." }`). Write the object form directly when you have a specific asset URL.
|
|
268
329
|
|
|
330
|
+
### Markdown content files
|
|
331
|
+
|
|
332
|
+
For a **collection page** whose model is shaped like "metadata + one long-form body", the sync engine stores each record as a **Markdown file with JSON frontmatter** (`.md`) instead of `.json`. The body is plain Markdown; the metadata is a JSON object between `---` markers.
|
|
333
|
+
|
|
334
|
+
A model qualifies when **all** of the following hold:
|
|
335
|
+
|
|
336
|
+
1. It is a **page** model (lives in `models/pages/`).
|
|
337
|
+
2. It is a **collection** (key ends with `+`, so each record is its own file).
|
|
338
|
+
3. It has **exactly one** top-level field of type `markdown`.
|
|
339
|
+
4. It has **no** `richtext` or `stack` fields anywhere (top-level or nested).
|
|
340
|
+
|
|
341
|
+
When the model qualifies, content lives at `content/pages/<key+>/<slug>.md`. All non-markdown fields go into the JSON frontmatter; the single `markdown` field's value becomes the body.
|
|
342
|
+
|
|
343
|
+
Example — given the model `models/pages/blog+.model`:
|
|
344
|
+
|
|
345
|
+
```
|
|
346
|
+
{
|
|
347
|
+
title: text,
|
|
348
|
+
date: date,
|
|
349
|
+
summary: paragraph,
|
|
350
|
+
body: markdown
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
The record `content/pages/blog+/welcome-post.md` looks like:
|
|
355
|
+
|
|
356
|
+
```markdown
|
|
357
|
+
---
|
|
358
|
+
{
|
|
359
|
+
"title": "Welcome to the blog",
|
|
360
|
+
"date": "2026-05-19",
|
|
361
|
+
"summary": "A short post about how the new blog works."
|
|
362
|
+
}
|
|
363
|
+
---
|
|
364
|
+
# Hello
|
|
365
|
+
|
|
366
|
+
This is the **first** post.
|
|
367
|
+
|
|
368
|
+
- one
|
|
369
|
+
- two
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
Notes:
|
|
373
|
+
- The body field's handle (`body` above) is whatever the single `markdown` field is called in the model — its key never appears in the frontmatter; its value is the markdown content.
|
|
374
|
+
- Image shortcut conventions still apply inside the markdown body (``) and inside frontmatter image fields (`"pexels:doctor"`).
|
|
375
|
+
- If the model doesn't qualify (e.g., it has two markdown fields, or a `stack`), the content stays JSON at `content/pages/<key+>/<slug>.json` — no change.
|
|
376
|
+
- The server still accepts `.json` for qualifying models — sending `.md` is preferred but not required. The next pull will re-emit the canonical `.md` form.
|
|
377
|
+
|
|
378
|
+
**YAML frontmatter is also accepted as input.** The server detects whichever shape you write between the `---` markers and parses it correctly. The canonical form emitted by the server is JSON — on the next pull, YAML frontmatter is rewritten to JSON.
|
|
379
|
+
|
|
380
|
+
```markdown
|
|
381
|
+
---
|
|
382
|
+
title: Welcome to the blog
|
|
383
|
+
date: '2026-05-19'
|
|
384
|
+
summary: A short post.
|
|
385
|
+
---
|
|
386
|
+
# Hello
|
|
387
|
+
|
|
388
|
+
This is the **first** post.
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
JSON5 features (trailing commas, `//` or `/* */` comments, unquoted keys) are accepted in JSON frontmatter. Choose JSON for programmatic generation (and to match canonical output); YAML is fine for hand-edits.
|
|
392
|
+
|
|
269
393
|
### Reusable images (`/images.json`)
|
|
270
394
|
|
|
271
395
|
For images used in more than one place (logos, recurring icons, hero art), declare them once in `/images.json` as a flat map of `handle → shortcut` using the same shortcut convention as `image` fields:
|
|
@@ -356,6 +480,25 @@ Page records include: `item._path`, `item._slug` (collections), `item._meta.upda
|
|
|
356
480
|
|
|
357
481
|
`attr` can be `"WxH"` or `{ w, h, size, zoom, maptype, scale, class, style }`.
|
|
358
482
|
|
|
483
|
+
### Video
|
|
484
|
+
|
|
485
|
+
`video` field content is created by the user via the CMS UI (uploading to Bunny.net). **The agent must never write a value into a `video` field, build an `<iframe>` tag by hand, or guess a `video_id` / `embed_url`.** Always render a video by passing the field object to the `embed()` helper.
|
|
486
|
+
|
|
487
|
+
| Function | Returns | Description |
|
|
488
|
+
|---|---|---|
|
|
489
|
+
| `embed(video, attr?)` | HTML string | Provider-aware `<iframe>` for the video |
|
|
490
|
+
|
|
491
|
+
`attr` is `"WxH"` or `{ w, h, size, autoplay, muted, loop, preload, t, class, style }`.
|
|
492
|
+
|
|
493
|
+
```ejs
|
|
494
|
+
<%- embed(item.intro_video) %>
|
|
495
|
+
<%- embed(item.intro_video, '1280x720') %>
|
|
496
|
+
<%- embed(item.intro_video, { autoplay: true, muted: true, loop: true }) %>
|
|
497
|
+
<%- embed(item.intro_video, { size: '800x450', class: 'video', style: 'border-radius:8px' }) %>
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
Most browsers block autoplay unless `muted: true` is also set. `autoplay` is added to the iframe's `allow` attribute only when the caller opts in.
|
|
501
|
+
|
|
359
502
|
### Head Injection
|
|
360
503
|
|
|
361
504
|
Call from **any template** (page, block, entry, or layout). Deduplicated automatically.
|
|
@@ -564,5 +707,6 @@ Template:
|
|
|
564
707
|
13. Block models cannot contain `block(...)` fields. Use groups, collections, or `entry(key)` references instead.
|
|
565
708
|
14. For `image` fields in content JSON, prefer the shortcut form `"<source>:<search>"` (sources: `unsplash`, `pexels`, `pixabay`, `iconify`) — e.g., `"pexels:doctor"`. Add alt text by appending `|<alt>` to the shortcut: `"pexels:doctor|Smiling doctor with stethoscope"`. The sync engine resolves it to a full `{ url, alt }` object on save. When the same image is reused across pages (logos, shared icons, recurring art), declare it once in `/images.json` and reference it via `"cms:<handle>"`.
|
|
566
709
|
15. Inside `markdown` fields, embed images with `` — same sources as image fields. Append `|<alt>` to store alt on the image record (e.g. ``). Including a `<W>x<H>` token in the description (e.g. `|hero shot 1200x600`) sets the rendered URL's `w` and `h` query params; otherwise the default is `600x400`. On save, refs are rewritten to actual CDN URLs and the underlying image record is created automatically.
|
|
567
|
-
16.
|
|
568
|
-
17.
|
|
710
|
+
16. **Collection pages whose model qualifies as "markdown content" — page model, collection (key ends with `+`), exactly one top-level `markdown` field, no `richtext` or `stack` anywhere — are stored as `.md` files with JSON frontmatter at `content/pages/<key+>/<slug>.md` (not `.json`).** All non-markdown fields go into the JSON frontmatter object between the `---` markers; the single `markdown` field's value is the body. When creating or editing such a record, prefer `.md`; the server also accepts `.json` for the same model. Inside the `.md` file, frontmatter may be written as JSON (canonical), JSON5 (trailing commas, `//` or `/* */` comments, unquoted keys), or YAML — all three are parsed correctly on save. Pulls always re-emit JSON as the canonical form.
|
|
711
|
+
17. Always create RSS feed for blogs and link them in meta so it is discoverable. Use "rss.xml" as the key.
|
|
712
|
+
18. Make the sites extremely SEO friendly and sharing friendly.
|
package/dist/watcher.js
CHANGED
|
@@ -19,10 +19,15 @@ const path_1 = __importDefault(require("path"));
|
|
|
19
19
|
const chokidar_1 = __importDefault(require("chokidar"));
|
|
20
20
|
const DEBOUNCE_DELAY = 5000;
|
|
21
21
|
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
22
|
+
// Poll on a short interval and compare wall-clock time so the timeout still
|
|
23
|
+
// fires correctly after the system has been asleep (Node's setTimeout runs on
|
|
24
|
+
// a monotonic clock that pauses during sleep).
|
|
25
|
+
const IDLE_CHECK_INTERVAL_MS = 60 * 1000;
|
|
22
26
|
let watcher = null;
|
|
23
27
|
let isShuttingDown = false;
|
|
24
28
|
let debounceTimer = null;
|
|
25
|
-
let
|
|
29
|
+
let idleCheckTimer = null;
|
|
30
|
+
let lastActivityAt = 0;
|
|
26
31
|
let dirty = false;
|
|
27
32
|
let syncInFlight = false;
|
|
28
33
|
let viewsDir = null;
|
|
@@ -34,17 +39,23 @@ function init(options) {
|
|
|
34
39
|
onIdle = options.onIdle ?? null;
|
|
35
40
|
}
|
|
36
41
|
function resetIdleTimer() {
|
|
37
|
-
|
|
38
|
-
clearTimeout(idleTimer);
|
|
42
|
+
lastActivityAt = Date.now();
|
|
39
43
|
if (isShuttingDown || !onIdle)
|
|
40
44
|
return;
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
if (idleCheckTimer)
|
|
46
|
+
return;
|
|
47
|
+
idleCheckTimer = setInterval(() => {
|
|
43
48
|
if (isShuttingDown)
|
|
44
49
|
return;
|
|
50
|
+
if (Date.now() - lastActivityAt < IDLE_TIMEOUT_MS)
|
|
51
|
+
return;
|
|
52
|
+
if (idleCheckTimer) {
|
|
53
|
+
clearInterval(idleCheckTimer);
|
|
54
|
+
idleCheckTimer = null;
|
|
55
|
+
}
|
|
45
56
|
console.log(`\n💤 No changes for ${IDLE_TIMEOUT_MS / 60000} minutes. Terminating.`);
|
|
46
57
|
onIdle?.();
|
|
47
|
-
},
|
|
58
|
+
}, IDLE_CHECK_INTERVAL_MS);
|
|
48
59
|
}
|
|
49
60
|
function setShuttingDown(value) {
|
|
50
61
|
isShuttingDown = value;
|
|
@@ -100,9 +111,9 @@ function monitorFiles() {
|
|
|
100
111
|
resetIdleTimer();
|
|
101
112
|
}
|
|
102
113
|
async function stopWatching() {
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
|
|
114
|
+
if (idleCheckTimer) {
|
|
115
|
+
clearInterval(idleCheckTimer);
|
|
116
|
+
idleCheckTimer = null;
|
|
106
117
|
}
|
|
107
118
|
if (watcher) {
|
|
108
119
|
await watcher.close();
|