@synopackageland/cli 0.12.0 → 0.12.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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.12.2 (2026-10-16)
4
+
5
+ - Generated Apps use templates 0.12.2, SDK 0.12.2 and UI 0.10.3. SDK 0.12.2 types `ctx.launchParams` and `ctx.onLaunch` and adds `followLaunchRoute`, `launchRoute` and `dsmLaunchURL`.
6
+ - Pin the SDK 0.12.2 executor and lifecycle digests, identical to 0.12.1 for all three architectures.
7
+ - The DSM window forwards launch parameters to the App. `ctx.launchParams` holds the App's own string values from the launch that opened the window, from a desktop URL `?launchApp=SYNO.SDS.<package>.Instance&launchParam=<url-encoded key=value&...>` or `SYNO.SDS.AppLaunch(id, {key: value})`; `ctx.onLaunch(fn)` runs fn with those of each later launch of the open window. DSM's own keys, non-string values, keys outside `[A-Za-z][A-Za-z0-9_.-]{0,63}`, values over 2048 characters or with control characters, and keys past 32 are left out.
8
+ - `ctx.reopen` callbacks now run when DSM launches the already open window again (main menu, desktop URL or AppLaunch); DSM calls the window's onRequest then, never onOpen again.
9
+
10
+ 繁中摘要:新專案使用 templates 0.12.2、SDK 0.12.2 與 UI 0.10.3,並加入 SDK 0.12.2 的 executor digest(與 0.12.1 相同)。DSM 視窗把啟動參數轉給 App:`ctx.launchParams` 是開啟視窗那次的 App 字串參數(桌面 URL `launchParam` 或 `SYNO.SDS.AppLaunch`),`ctx.onLaunch(fn)` 在已開視窗再次被帶參數啟動時呼叫;DSM 自有欄位、非字串、不合格的 key、超過 2048 字元或含控制字元的值與第 32 個之後的 key 會被略過。已開視窗再次啟動時 `ctx.reopen` 現在會觸發(DSM 呼叫 onRequest,不會再呼叫 onOpen)。
11
+
12
+ ## 0.12.1 (2026-10-16)
13
+
14
+ - Preserve optional `raw_max_bytes` in WebAPI profiles and administrator policy digests. Reject invalid limits and require raw-capable SDK 0.12.1; zero/omitted profiles stay JSON-only. Generated approval details receive the byte limit.
15
+ - Pin the SDK 0.12.1 three-architecture executor and lifecycle digests. Templates remain 0.12.0; no template change is needed for an optional consumer-declared profile.
16
+
17
+ 繁中摘要:WebAPI profile 支援 raw_max_bytes 明確啟用原始回應與大小上限;驗證及核准 digest 保留此設定,需搭配 SDK 0.12.1。更新 executor digest,templates 維持 0.12.0。
18
+
3
19
  ## 0.12.0 (2026-10-16)
4
20
 
5
21
  - Generated Apps use templates 0.12.0, SDK 0.12.0 and UI 0.10.1. Vue pages and sections stack at the UI section gap, and SDK dialogs size to their content.
package/README.md CHANGED
@@ -204,3 +204,15 @@ normal executor approval digest; a profile change needs administrator approval.
204
204
  Users grant the named profile separately. The permanent denylist, current-user
205
205
  permission checks, bounded JSON and uncertain-outcome rules are in the SDK's
206
206
  executor/README.md. Binary downloads require later SDK support.
207
+
208
+ ## Raw WebAPI profiles
209
+
210
+ SDK 0.12.1 add opt-in raw responses. In `capabilities.webapi.<profile>`,
211
+ set `raw_max_bytes: 26214400` for a 25 MiB MIME limit and explicitly allow
212
+ `SYNO.MailClient.Message.get_original` v10. Omitting the limit preserves JSON-only
213
+ profiles. Changing it changes the approved policy and requires fresh user consent.
214
+ Call `webapi.call` with `response_type: "raw"`; Go Apps use
215
+ `JobContext.WebAPI().CallRaw`. Small bodies use base64; large bodies use the
216
+ package's private executor-staging folder with size/SHA-256 and immediate cleanup.
217
+ Do not use Attachment.download or follow X-Accel-Redirect. See the SDK raw response
218
+ contract for the complete transport and CGI validation rules.
package/dist/init.js CHANGED
@@ -305,8 +305,17 @@ function assertLocalSdkReady(sdkDir, vue) {
305
305
  */
306
306
  function useLocalSdk(targetDir, sdkDir) {
307
307
  const goMod = join(targetDir, "backend/go.mod");
308
- if (existsSync(goMod))
309
- writeFileSync(goMod, `${readFileSync(goMod, "utf8")}\nreplace ${SDK_MODULE} => ${sdkDir}\n`);
308
+ if (existsSync(goMod)) {
309
+ let text = readFileSync(goMod, "utf8");
310
+ const sdkPackage = join(sdkDir, "ts/package.json");
311
+ if (existsSync(sdkPackage)) {
312
+ const version = JSON.parse(readFileSync(sdkPackage, "utf8")).version;
313
+ if (typeof version !== "string" || !/^\d+\.\d+\.\d+$/.test(version))
314
+ throw new Error('[SDK_DIR_NOT_READY] Local SDK package version must be X.Y.Z.');
315
+ text = text.replace(/(github\.com\/synopackageland\/syno-pkg-land-sdk v)\d+\.\d+\.\d+\b/g, `$1${version}`);
316
+ }
317
+ writeFileSync(goMod, `${text}\nreplace ${SDK_MODULE} => ${sdkDir}\n`);
318
+ }
310
319
  const frontend = join(targetDir, "frontend/package.json");
311
320
  if (!existsSync(frontend))
312
321
  return;
@@ -9,5 +9,27 @@
9
9
  "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
10
10
  },
11
11
  "goToolchain": "go1.26.9"
12
+ },
13
+ "0.12.1": {
14
+ "x86_64": "efe409287770f6b70e7f8465e8767eeb2413b9ab8775020cf4dffb1555d7bf1f",
15
+ "aarch64": "d68c9889ed2391e918b9c278679ea0a01cd04425528c11090700a3b6341fc543",
16
+ "armv7": "9149a05efb9f2d7d86a400707099b3d657382b1c0dbad6588be8c500e90445cc",
17
+ "lifecycle": {
18
+ "x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
19
+ "aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
20
+ "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
21
+ },
22
+ "goToolchain": "go1.26.9"
23
+ },
24
+ "0.12.2": {
25
+ "x86_64": "efe409287770f6b70e7f8465e8767eeb2413b9ab8775020cf4dffb1555d7bf1f",
26
+ "aarch64": "d68c9889ed2391e918b9c278679ea0a01cd04425528c11090700a3b6341fc543",
27
+ "armv7": "9149a05efb9f2d7d86a400707099b3d657382b1c0dbad6588be8c500e90445cc",
28
+ "lifecycle": {
29
+ "x86_64": "d65500c4f04713a32b45d360059281391acfa5e00ee24895fdd2de7b82735372",
30
+ "aarch64": "94f6cd119186ff482b8aea56541dd1f9d53c147566bc1efdca6f5a1c86198331",
31
+ "armv7": "cafc97c89f5645c14a8cf31d6d9f258394ed6f2d086478a7aebd04d57c274b4e"
32
+ },
33
+ "goToolchain": "go1.26.9"
12
34
  }
13
35
  }
@@ -1,7 +1,8 @@
1
- /** JSON WebAPI profiles, independently enforced again by the pinned executor. */
1
+ /** WebAPI profiles, independently enforced again by the pinned executor. */
2
2
  export interface WebAPIProfile {
3
3
  purpose: string;
4
4
  apis: Record<string, Record<string, number[]>>;
5
+ raw_max_bytes?: number;
5
6
  }
6
7
  export type WebAPIProfiles = Record<string, WebAPIProfile>;
7
8
  export declare function validWebAPIProfiles(value: unknown): value is WebAPIProfiles;
@@ -6,7 +6,9 @@ export function validWebAPIProfiles(value) {
6
6
  return false;
7
7
  let total = 0;
8
8
  for (const [name, p] of Object.entries(value)) {
9
- if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || name === 'home' || !record(p) || Object.keys(p).some(k => !['purpose', 'apis'].includes(k)) || typeof p.purpose !== 'string' || !p.purpose.trim() || Buffer.byteLength(p.purpose) > 1024 || /[\x00-\x1f\x7f]/.test(p.purpose) || !record(p.apis) || !Object.keys(p.apis).length)
9
+ if (!/^[a-z][a-z0-9_-]{0,63}$/.test(name) || name === 'home' || !record(p) || Object.keys(p).some(k => !['purpose', 'apis', 'raw_max_bytes'].includes(k)) || typeof p.purpose !== 'string' || !p.purpose.trim() || Buffer.byteLength(p.purpose) > 1024 || /[\x00-\x1f\x7f]/.test(p.purpose) || !record(p.apis) || !Object.keys(p.apis).length)
10
+ return false;
11
+ if (p.raw_max_bytes !== undefined && (!Number.isSafeInteger(p.raw_max_bytes) || Number(p.raw_max_bytes) < 0 || Number(p.raw_max_bytes) > 67108864))
10
12
  return false;
11
13
  for (const [api, methods] of Object.entries(p.apis)) {
12
14
  if (!/^SYNO\.[A-Za-z][A-Za-z0-9]*(\.[A-Za-z][A-Za-z0-9]*)*$/.test(api) || api.toLowerCase().split('.').slice(1).some(x => blocked.has(x)) || !record(methods) || !Object.keys(methods).length)
@@ -23,5 +25,5 @@ export function validWebAPIProfiles(value) {
23
25
  return true;
24
26
  }
25
27
  export function sortedWebAPIProfiles(profiles) {
26
- return Object.fromEntries(Object.entries(profiles).sort(([a], [b]) => a < b ? -1 : 1).map(([name, p]) => [name, { purpose: p.purpose, apis: Object.fromEntries(Object.entries(p.apis).sort(([a], [b]) => a < b ? -1 : 1).map(([api, methods]) => [api, Object.fromEntries(Object.entries(methods).sort(([a], [b]) => a < b ? -1 : 1).map(([m, versions]) => [m, [...versions].sort((a, b) => a - b)]))])) }]));
28
+ return Object.fromEntries(Object.entries(profiles).sort(([a], [b]) => a < b ? -1 : 1).map(([name, p]) => [name, { purpose: p.purpose, apis: Object.fromEntries(Object.entries(p.apis).sort(([a], [b]) => a < b ? -1 : 1).map(([api, methods]) => [api, Object.fromEntries(Object.entries(methods).sort(([a], [b]) => a < b ? -1 : 1).map(([m, versions]) => [m, [...versions].sort((a, b) => a - b)]))])), ...(p.raw_max_bytes ? { raw_max_bytes: p.raw_max_bytes } : {}) }]));
27
29
  }
@@ -42,5 +42,8 @@ export declare function renderUiConfig(manifest: StandaloneManifest, icons?: Pac
42
42
  * (and add a device-width viewport), and `ctx.appIcon` is the package icon a
43
43
  * full-page shell shows beside the name, where a DSM window has a title bar.
44
44
  * A portal App's `ctx.portal` shows `portalText`, the SDK's panel text.
45
+ * `ctx.launchParams` holds the App's own values from the launch that opened
46
+ * the window (a `launchParam` desktop URL or `SYNO.SDS.AppLaunch(id, param)`),
47
+ * and `ctx.onLaunch(fn)` hands fn those of each later launch with values.
45
48
  */
46
49
  export declare function renderWindowScript(manifest: StandaloneManifest, assetVersion: string, icons?: PackageIcons, portalText?: PortalCatalogs): string;
@@ -1,4 +1,5 @@
1
1
  import { REOPEN_EVENTS_RUNTIME } from "./reopen-events.js";
2
+ import { LAUNCH_PARAMS_RUNTIME } from "./launch-params.js";
2
3
  import { DSM_LANGS } from "./i18n.js";
3
4
  import { PASSWORD_CONFIRM, ROOT_PANEL, ROOT_RUNTIME, rootPanelCatalogs } from "./root-window.js";
4
5
  import { HOST_ADAPTER_RUNTIME } from "./host-adapter.js";
@@ -128,6 +129,9 @@ export function renderUiConfig(manifest, icons = packageIcons(manifest.icon), lo
128
129
  * (and add a device-width viewport), and `ctx.appIcon` is the package icon a
129
130
  * full-page shell shows beside the name, where a DSM window has a title bar.
130
131
  * A portal App's `ctx.portal` shows `portalText`, the SDK's panel text.
132
+ * `ctx.launchParams` holds the App's own values from the launch that opened
133
+ * the window (a `launchParam` desktop URL or `SYNO.SDS.AppLaunch(id, param)`),
134
+ * and `ctx.onLaunch(fn)` hands fn those of each later launch with values.
131
135
  */
132
136
  export function renderWindowScript(manifest, assetVersion, icons = packageIcons(manifest.icon), portalText) {
133
137
  const pkg = manifest.package;
@@ -270,6 +274,11 @@ ${REOPEN_EVENTS_RUNTIME}
270
274
  return createReopenEvents;
271
275
  })();
272
276
 
277
+ ${ns}.launchParams = (function () {
278
+ ${LAUNCH_PARAMS_RUNTIME}
279
+ return launchParams;
280
+ })();
281
+
273
282
  ${ns}.createHost = (function () {
274
283
  ${HOST_ADAPTER_RUNTIME}
275
284
  return createHostAdapter;
@@ -332,7 +341,7 @@ Ext.define(${JSON.stringify(`${ns}.MainWindow`)}, {
332
341
  getRestoreSizePos: function (config) {
333
342
  return ${ns}.fitDesktop(Ext.apply({ width: config.width, height: config.height }, this.callParent(arguments)));
334
343
  },
335
- onOpen: function () {
344
+ onOpen: function (param) {
336
345
  this.callParent(arguments);
337
346
  var me = this;
338
347
  if (me.mounted) {
@@ -341,9 +350,11 @@ Ext.define(${JSON.stringify(`${ns}.MainWindow`)}, {
341
350
  }
342
351
  if (me.mountPending) return;
343
352
  me.mountPending = true;
353
+ me.launchParams = ${ns}.launchParams(param);
344
354
  var mountToken = me.mountToken = {};
345
355
  me.reopenEvents = ${ns}.createReopenEvents();
346
356
  me.reopenHandlers = me.reopenEvents.handlers;
357
+ me.launchEvents = ${ns}.createReopenEvents();
347
358
  var lang = "enu";
348
359
  try { if (typeof _S === "function") lang = _S("lang"); } catch (error) {}
349
360
  var ctx = { requestTimeout: ${requestTimeout} };
@@ -362,6 +373,8 @@ ${root ? root.mount : ""} ${ns}.loadApp(function (app) {
362
373
  call: call,
363
374
  files: {endpoint: ${JSON.stringify(cgiUrl(pkg))}, token: function () { return SYNO.SDS.Session && SYNO.SDS.Session.SynoToken; }},
364
375
  reopen: me.reopenEvents.subscribe,
376
+ launchParams: me.launchParams,
377
+ onLaunch: me.launchEvents.subscribe,
365
378
  user: ${ns}.sessionUser(),${manifest.portal ? `\n portal: ${renderPortalAccess(ns, manifest)},` : ""}
366
379
  host: me.hostAccess,
367
380
  appWindow: me,
@@ -378,17 +391,33 @@ ${root ? root.mount : ""} ${ns}.loadApp(function (app) {
378
391
  me.reopenEvents.dispose();
379
392
  me.reopenEvents = null;
380
393
  me.reopenHandlers = [];
394
+ me.launchEvents.dispose();
395
+ me.launchEvents = null;
381
396
  throw error;
382
397
  }
383
398
  }
384
399
  });
385
400
  },
401
+ // DSM opens a running App's window again through onRequest, not onOpen,
402
+ // with that launch's param. Before the App mounts, it mounts with the latest.
403
+ onRequest: function (param) {
404
+ this.callParent(arguments);
405
+ var params = ${ns}.launchParams(param);
406
+ if (!this.mounted) {
407
+ if (this.mountPending && Object.keys(params).length) this.launchParams = params;
408
+ return;
409
+ }
410
+ this.reopenEvents.emit();
411
+ if (Object.keys(params).length) this.launchEvents.emit(params);
412
+ },
386
413
  onDestroy: function () {
387
414
  this.mountToken = null;
388
415
  this.mountPending = false;
389
416
  if (this.reopenEvents) this.reopenEvents.dispose();
390
417
  this.reopenEvents = null;
391
418
  this.reopenHandlers = [];
419
+ if (this.launchEvents) this.launchEvents.dispose();
420
+ this.launchEvents = null;
392
421
  if (this.hostAccess) this.hostAccess.dispose();
393
422
  this.hostAccess = null;
394
423
  var mounted = this.mounted;
@@ -0,0 +1,11 @@
1
+ /** At most this many App values, each at most this long, reach the App from one launch. */
2
+ export declare const MAX_LAUNCH_PARAMS = 32;
3
+ export declare const MAX_LAUNCH_VALUE = 2048;
4
+ /**
5
+ * `launchParams(param)`: the App's own values from the param DSM hands the
6
+ * window's onOpen and onRequest, a `launchParam` desktop URL decoded by DSM
7
+ * into an object or AppLaunch's second argument. Only short single-line string
8
+ * values under plain keys pass; the result is a frozen, prototype-free copy.
9
+ * The values stay untrusted text for the App.
10
+ */
11
+ export declare const LAUNCH_PARAMS_RUNTIME: string;
@@ -0,0 +1,33 @@
1
+ /** At most this many App values, each at most this long, reach the App from one launch. */
2
+ export const MAX_LAUNCH_PARAMS = 32;
3
+ export const MAX_LAUNCH_VALUE = 2048;
4
+ /**
5
+ * DSM's own string keys in a launch: AppLaunch reads className and
6
+ * instanceName, AppWindow.onOpen sets the window title from title, and desktop
7
+ * restore replays openConfig with dsm_version. Its other keys (fromRestore,
8
+ * windowState, cms_id, ...) are not strings and are left out with the rest.
9
+ */
10
+ const DSM_KEYS = ["className", "instanceName", "dsm_version", "title"];
11
+ /**
12
+ * `launchParams(param)`: the App's own values from the param DSM hands the
13
+ * window's onOpen and onRequest, a `launchParam` desktop URL decoded by DSM
14
+ * into an object or AppLaunch's second argument. Only short single-line string
15
+ * values under plain keys pass; the result is a frozen, prototype-free copy.
16
+ * The values stay untrusted text for the App.
17
+ */
18
+ export const LAUNCH_PARAMS_RUNTIME = `
19
+ function launchParams(param) {
20
+ var dsmKeys = ${JSON.stringify(DSM_KEYS)};
21
+ var params = Object.create(null), count = 0;
22
+ if (param && typeof param === "object" && !Array.isArray(param)) {
23
+ Object.keys(param).forEach(function (key) {
24
+ var value = param[key];
25
+ if (count >= ${MAX_LAUNCH_PARAMS} || dsmKeys.indexOf(key) >= 0 || !/^[A-Za-z][A-Za-z0-9_.-]{0,63}$/.test(key)) return;
26
+ if (typeof value !== "string" || value.length > ${MAX_LAUNCH_VALUE} || /[\\u0000-\\u001f\\u007f]/.test(value)) return;
27
+ params[key] = value;
28
+ count++;
29
+ });
30
+ }
31
+ return Object.freeze(params);
32
+ }
33
+ `;
@@ -217,6 +217,10 @@ function readManifest(raw, c) {
217
217
  const sdk = sdkVersionOrUndefined(backend);
218
218
  if (!sdk || Number(sdk.split('.')[0]) === 0 && Number(sdk.split('.')[1]) < 11)
219
219
  c.fail("SDK_UNSUPPORTED", "capabilities.webapi requires SDK 0.11.0 or later; update backend and frontend together.");
220
+ const raw = Object.values(capabilities.webapi).some(profile => (profile.raw_max_bytes ?? 0) > 0);
221
+ const [major, minor, patch] = (sdk ?? '0.0.0').split('.').map(Number);
222
+ if (raw && (!sdk || major === 0 && (minor < 12 || minor === 12 && patch < 1)))
223
+ c.fail("SDK_UNSUPPORTED", "Raw WebAPI profiles require SDK 0.12.1 or later; update backend and frontend together.");
220
224
  }
221
225
  const uninstall = readUninstall(raw.uninstall, { rootOps: capabilities.rootOps === true, ops: rootOps, en: i18n?.catalogs?.en }, e => c.fail(e.code, e.message));
222
226
  const appScripts = readAppScripts(c.root, e => c.fail(e.code, e.message));
@@ -1,2 +1,2 @@
1
- /** Wrapper-owned subscriptions; no dependency on native event names or Ext. */
2
- export declare const REOPEN_EVENTS_RUNTIME = "\nfunction createReopenEvents() {\n var active = true, handlers = [];\n function subscribe(fn) {\n if (!active || typeof fn !== \"function\") return function () {};\n function handler() { if (active && fn) fn(); }\n function unsubscribe() {\n if (!fn) return;\n fn = null;\n var index = handlers.indexOf(handler);\n if (index !== -1) handlers.splice(index, 1);\n }\n handler.unsubscribe = unsubscribe;\n handlers.push(handler);\n return unsubscribe;\n }\n return {\n handlers: handlers,\n subscribe: subscribe,\n emit: function () { handlers.slice().forEach(function (handler) { handler(); }); },\n dispose: function () {\n active = false;\n handlers.slice().forEach(function (handler) { handler.unsubscribe(); });\n }\n };\n}\n";
1
+ /** Wrapper-owned subscriptions; no dependency on native event names or Ext. emit(value) hands value to each fn. */
2
+ export declare const REOPEN_EVENTS_RUNTIME = "\nfunction createReopenEvents() {\n var active = true, handlers = [];\n function subscribe(fn) {\n if (!active || typeof fn !== \"function\") return function () {};\n function handler(value) { if (active && fn) fn(value); }\n function unsubscribe() {\n if (!fn) return;\n fn = null;\n var index = handlers.indexOf(handler);\n if (index !== -1) handlers.splice(index, 1);\n }\n handler.unsubscribe = unsubscribe;\n handlers.push(handler);\n return unsubscribe;\n }\n return {\n handlers: handlers,\n subscribe: subscribe,\n emit: function (value) { handlers.slice().forEach(function (handler) { handler(value); }); },\n dispose: function () {\n active = false;\n handlers.slice().forEach(function (handler) { handler.unsubscribe(); });\n }\n };\n}\n";
@@ -1,10 +1,10 @@
1
- /** Wrapper-owned subscriptions; no dependency on native event names or Ext. */
1
+ /** Wrapper-owned subscriptions; no dependency on native event names or Ext. emit(value) hands value to each fn. */
2
2
  export const REOPEN_EVENTS_RUNTIME = `
3
3
  function createReopenEvents() {
4
4
  var active = true, handlers = [];
5
5
  function subscribe(fn) {
6
6
  if (!active || typeof fn !== "function") return function () {};
7
- function handler() { if (active && fn) fn(); }
7
+ function handler(value) { if (active && fn) fn(value); }
8
8
  function unsubscribe() {
9
9
  if (!fn) return;
10
10
  fn = null;
@@ -18,7 +18,7 @@ function createReopenEvents() {
18
18
  return {
19
19
  handlers: handlers,
20
20
  subscribe: subscribe,
21
- emit: function () { handlers.slice().forEach(function (handler) { handler(); }); },
21
+ emit: function (value) { handlers.slice().forEach(function (handler) { handler(value); }); },
22
22
  dispose: function () {
23
23
  active = false;
24
24
  handlers.slice().forEach(function (handler) { handler.unsubscribe(); });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@synopackageland/cli",
3
- "version": "0.12.0",
3
+ "version": "0.12.2",
4
4
  "description": "Syno Package Land Developer Kit CLI",
5
5
  "type": "module",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "prepublishOnly": "npm run build"
26
26
  },
27
27
  "dependencies": {
28
- "@synopackageland/templates": "0.12.0",
28
+ "@synopackageland/templates": "0.12.2",
29
29
  "ajv": "^8.17.1",
30
30
  "commander": "^13.1.0",
31
31
  "tar": "^7.4.3",