@tomorrowos/sdk 0.3.0 → 0.3.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/dist/cli.js CHANGED
@@ -11,6 +11,27 @@ function getSdkVersion() {
11
11
  const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
12
12
  return pkg.version ?? "0.0.0";
13
13
  }
14
+ const STARTER_SKIP_DIRS = new Set(["node_modules"]);
15
+ /** Never ship local install artifacts into a new CMS project. */
16
+ function shouldCopyStarterEntry(starterRoot, source) {
17
+ const rel = path.relative(starterRoot, source);
18
+ if (!rel)
19
+ return true;
20
+ const parts = rel.split(path.sep).filter(Boolean);
21
+ if (parts.some((p) => STARTER_SKIP_DIRS.has(p)))
22
+ return false;
23
+ if (path.basename(source) === "package-lock.json")
24
+ return false;
25
+ return true;
26
+ }
27
+ function removeStarterArtifacts(destDir) {
28
+ const lockPath = path.join(destDir, "package-lock.json");
29
+ const modulesPath = path.join(destDir, "node_modules");
30
+ if (fs.existsSync(lockPath))
31
+ fs.rmSync(lockPath, { force: true });
32
+ if (fs.existsSync(modulesPath))
33
+ fs.rmSync(modulesPath, { recursive: true, force: true });
34
+ }
14
35
  /** Align generated cms-starter package.json with the installed SDK version. */
15
36
  function patchStarterPackageJson(destDir) {
16
37
  const pkgPath = path.join(path.resolve(destDir), "package.json");
@@ -41,12 +62,16 @@ function copyStarter(destDir, force) {
41
62
  else {
42
63
  fs.mkdirSync(resolved, { recursive: true });
43
64
  }
44
- fs.cpSync(src, resolved, { recursive: true });
65
+ fs.cpSync(src, resolved, {
66
+ recursive: true,
67
+ filter: (source) => shouldCopyStarterEntry(src, source),
68
+ });
45
69
  patchStarterPackageJson(resolved);
70
+ removeStarterArtifacts(resolved);
46
71
  const sdkVer = getSdkVersion();
47
72
  console.log(`[tomorrowos] Created CMS project at ${resolved}`);
48
73
  console.log(`[tomorrowos] @tomorrowos/sdk dependency: ^${sdkVer}`);
49
- console.log("Next: cd there, run npm install, then npm run dev");
74
+ console.log("Next: cd there, run npm install, then npm start");
50
75
  }
51
76
  function cmdBuild(argv) {
52
77
  const platformIdx = argv.indexOf("--platform");
@@ -794,6 +794,7 @@ export class TomorrowOS extends EventEmitter {
794
794
  createdAt: Date.now()
795
795
  });
796
796
  ws.deviceId = deviceId;
797
+ this.sendBrandSnapshot(ws);
797
798
  ws.send(JSON.stringify({
798
799
  type: "pairing.code",
799
800
  method: "tomorrowos.pairing.createCode",
@@ -801,7 +802,6 @@ export class TomorrowOS extends EventEmitter {
801
802
  deviceId,
802
803
  serialNumber
803
804
  }));
804
- this.sendBrandSnapshot(ws);
805
805
  this.emit("device.online", { deviceId });
806
806
  }
807
807
  catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "description": "TomorrowOS CMS server SDK — WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.2.4",
3
+ "version": "0.3.2",
4
4
  "description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
5
5
  "private": true,
6
6
  "type": "module",
@@ -10,7 +10,7 @@
10
10
  "build-player": "tomorrowos build --platform tizen"
11
11
  },
12
12
  "dependencies": {
13
- "@tomorrowos/sdk": "^0.2.4"
13
+ "@tomorrowos/sdk": "^0.3.2"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^20.0.0",
@@ -9,6 +9,9 @@ let devicesCache = [];
9
9
  /** @type {string|null} */
10
10
  let selectedPlaylistId = null;
11
11
 
12
+ /** True while creating a new playlist locally (assets allowed before Save). */
13
+ let playlistDraftActive = false;
14
+
12
15
  /** @type {{ id: string, url: string, name: string, type: string, durationMs: number }[]} */
13
16
  let editorItems = [];
14
17
 
@@ -171,15 +174,36 @@ function getSelectedPlaylist() {
171
174
  }
172
175
 
173
176
  async function fetchPlaylists() {
174
- const res = await fetch("/playlists");
175
- const data = await res.json();
176
- if (Array.isArray(data.playlists)) {
177
- playlistsCatalog = data.playlists;
178
- renderPlaylistCatalog();
179
- if (selectedPlaylistId && !getSelectedPlaylist()) {
180
- selectedPlaylistId = playlistsCatalog[0]?.id || null;
181
- loadEditorFromSelection();
177
+ try {
178
+ const res = await fetch("/playlists");
179
+ let data = {};
180
+ try {
181
+ data = await res.json();
182
+ } catch {
183
+ data = {};
184
+ }
185
+ if (!res.ok) {
186
+ console.warn("[CMS] GET /playlists failed", res.status, data);
187
+ if (res.status === 404) {
188
+ showResult({
189
+ status: "failed",
190
+ error:
191
+ "CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.2 or newer."
192
+ });
193
+ }
194
+ return;
182
195
  }
196
+ if (Array.isArray(data.playlists)) {
197
+ playlistsCatalog = data.playlists;
198
+ renderPlaylistCatalog();
199
+ if (selectedPlaylistId && !getSelectedPlaylist()) {
200
+ selectedPlaylistId = playlistsCatalog[0]?.id || null;
201
+ loadEditorFromSelection();
202
+ }
203
+ }
204
+ } catch (err) {
205
+ console.error("[CMS] fetchPlaylists:", err);
206
+ showResult({ status: "failed", error: err.message });
183
207
  }
184
208
  }
185
209
 
@@ -202,6 +226,7 @@ function renderPlaylistCatalog() {
202
226
  if (pl.id === selectedPlaylistId) li.classList.add("playlist-catalog-item--active");
203
227
  li.innerHTML = `<strong>${escapeHtml(pl.name)}</strong><small>v${pl.version} · ${(pl.items || []).length} items</small>`;
204
228
  li.addEventListener("click", () => {
229
+ playlistDraftActive = false;
205
230
  selectedPlaylistId = pl.id;
206
231
  loadEditorFromSelection();
207
232
  renderPlaylistCatalog();
@@ -210,12 +235,22 @@ function renderPlaylistCatalog() {
210
235
  }
211
236
  }
212
237
 
238
+ function isPlaylistEditorOpen() {
239
+ return playlistDraftActive || !!selectedPlaylistId;
240
+ }
241
+
213
242
  function loadEditorFromSelection() {
214
243
  const pl = getSelectedPlaylist();
215
244
  const nameInput = document.getElementById("playlistName");
216
245
  const editorTitle = document.getElementById("editorTitle");
217
246
 
218
247
  if (!pl) {
248
+ if (playlistDraftActive) {
249
+ if (editorTitle) editorTitle.textContent = "New playlist";
250
+ renderEditorAssets();
251
+ return;
252
+ }
253
+ playlistDraftActive = false;
219
254
  if (editorTitle) editorTitle.textContent = "Playlist editor";
220
255
  if (nameInput) nameInput.value = "";
221
256
  editorItems = [];
@@ -224,6 +259,7 @@ function loadEditorFromSelection() {
224
259
  return;
225
260
  }
226
261
 
262
+ playlistDraftActive = false;
227
263
  if (editorTitle) editorTitle.textContent = `Edit: ${pl.name}`;
228
264
  if (nameInput) nameInput.value = pl.name || "";
229
265
  loadScheduleIntoForm(pl.schedule);
@@ -242,11 +278,15 @@ function renderEditorAssets() {
242
278
  const empty = document.getElementById("playlistEmpty");
243
279
  list.querySelectorAll(".playlist-item").forEach((el) => el.remove());
244
280
 
245
- if (!selectedPlaylistId || editorItems.length === 0) {
281
+ if (!isPlaylistEditorOpen()) {
282
+ empty.classList.remove("hidden");
283
+ empty.textContent = "Select or create a playlist (Playlists +).";
284
+ return;
285
+ }
286
+
287
+ if (editorItems.length === 0) {
246
288
  empty.classList.remove("hidden");
247
- empty.textContent = selectedPlaylistId
248
- ? "No assets yet. Tap +."
249
- : "Select or create a playlist.";
289
+ empty.textContent = "No assets yet. Tap + to upload.";
250
290
  return;
251
291
  }
252
292
 
@@ -352,6 +392,7 @@ async function saveCurrentPlaylist() {
352
392
  return;
353
393
  }
354
394
 
395
+ playlistDraftActive = false;
355
396
  selectedPlaylistId = data.playlist?.id || selectedPlaylistId;
356
397
  await fetchPlaylists();
357
398
  loadEditorFromSelection();
@@ -382,6 +423,7 @@ async function deleteCurrentPlaylist() {
382
423
  }
383
424
 
384
425
  selectedPlaylistId = null;
426
+ playlistDraftActive = false;
385
427
  editorItems = [];
386
428
  await fetchPlaylists();
387
429
  loadEditorFromSelection();
@@ -389,12 +431,23 @@ async function deleteCurrentPlaylist() {
389
431
 
390
432
  function newPlaylistDraft() {
391
433
  selectedPlaylistId = null;
392
- document.getElementById("playlistName").value = "";
434
+ playlistDraftActive = true;
435
+ const nameInput = document.getElementById("playlistName");
436
+ if (nameInput) {
437
+ nameInput.value = "";
438
+ nameInput.focus();
439
+ }
393
440
  loadScheduleIntoForm(null);
394
441
  editorItems = [];
395
442
  renderPlaylistCatalog();
396
443
  renderEditorAssets();
397
- document.getElementById("editorTitle").textContent = "New playlist";
444
+ const editorTitle = document.getElementById("editorTitle");
445
+ if (editorTitle) editorTitle.textContent = "New playlist";
446
+ document.getElementById("playlistEditorSection")?.scrollIntoView({ behavior: "smooth", block: "start" });
447
+ showResult({
448
+ status: "draft",
449
+ message: "New playlist — enter a name, add assets with + (right), then Save playlist."
450
+ });
398
451
  }
399
452
 
400
453
  async function fetchDevices() {
@@ -621,9 +674,8 @@ async function uploadFile(file) {
621
674
  }
622
675
 
623
676
  async function addAssetFromFile(file) {
624
- if (!selectedPlaylistId) {
625
- alert("Select or create a playlist first, then Save.");
626
- return;
677
+ if (!isPlaylistEditorOpen()) {
678
+ newPlaylistDraft();
627
679
  }
628
680
  const data = await uploadFile(file);
629
681
  const type = inferMediaType(file.name, file.type);
@@ -711,7 +763,10 @@ document.addEventListener("DOMContentLoaded", () => {
711
763
  });
712
764
 
713
765
  document.getElementById("addAssetBtn")?.addEventListener("click", () => {
714
- document.getElementById("fileInput").click();
766
+ if (!isPlaylistEditorOpen()) {
767
+ newPlaylistDraft();
768
+ }
769
+ document.getElementById("fileInput")?.click();
715
770
  });
716
771
 
717
772
  document.getElementById("fileInput")?.addEventListener("change", async (ev) => {