@synopackageland/templates 0.16.0 → 0.17.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 +9 -3
- package/package.json +1 -1
- package/templates/_shared/AGENTS.md +24 -20
- package/templates/compose/dsm-package.yaml +1 -1
- package/templates/native-go/README.md +15 -17
- package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +1 -1
- package/templates/native-go/backend/go.mod +1 -1
- package/templates/native-go/backend/go.sum +2 -2
- package/templates/native-go-vue/frontend/package-lock.json +7 -7
- package/templates/native-go-vue/frontend/package.json +2 -2
- package/templates/native-go-vue/frontend/src/App.vue +17 -3
- package/templates/native-go-vue/frontend/test/apps.test.ts +26 -0
- package/templates/native-node/dsm-package.yaml +1 -1
- package/templates/native-python/dsm-package.yaml +1 -1
- package/templates/native-rust/backend/Cargo.lock +2 -2
- package/templates/native-rust/backend/Cargo.toml +1 -1
package/README.md
CHANGED
|
@@ -38,12 +38,12 @@ calls each one from the generated script of the same name (`synopkgland skill sh
|
|
|
38
38
|
The placeholders `__PACKAGE__` (also the Go module path), `__DISPLAY_NAME__` and
|
|
39
39
|
`__BACKGROUND_ENABLED__` are substituted by `init`.
|
|
40
40
|
|
|
41
|
-
`backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.
|
|
41
|
+
`backend/go.mod` requires `github.com/synopackageland/syno-pkg-land-sdk v0.17.0`; that line is
|
|
42
42
|
the single source of the SDK version and `frontend/package.json` pins
|
|
43
43
|
`@synopackageland/sdk` to the same version. Python and Node.js name it in
|
|
44
44
|
`backend.sdk`, Rust in the `syno-pkg-land-app` tag of `backend/Cargo.toml`
|
|
45
|
-
(and `Cargo.lock`); all of them pin 0.
|
|
46
|
-
`@synopackageland/ui` 0.12.0. SDK 0.
|
|
45
|
+
(and `Cargo.lock`); all of them pin 0.17.0. The Vue frontend pins
|
|
46
|
+
`@synopackageland/ui` 0.12.0. SDK 0.17.0 accepts UI `>=0.10.1 <0.13.0`,
|
|
47
47
|
so no UI peer override is needed. Tests use jsdom 30 for UI floating-layer styles.
|
|
48
48
|
|
|
49
49
|
## Notice writer
|
|
@@ -149,3 +149,9 @@ The CLI preserves upstream bytes and the SDK planner generates an override and o
|
|
|
149
149
|
## AI commands
|
|
150
150
|
|
|
151
151
|
All four native templates explicitly declare status and greeting commands in `ctl.commands`. Build packages a private `synopkgland-api.json`; only the authenticated SDK description endpoint returns it. Internal routes are not automatically commands. Read `synopkgland skill show ctl` before adding command schemas or workflow bindings.
|
|
152
|
+
|
|
153
|
+
The Vue scaffold batches independent startup status/greeting reads when the
|
|
154
|
+
window already identifies an administrator and supplies `ctx.batch`. Rebuild
|
|
155
|
+
with the current CLI and SDK to get that client. Older generated contexts keep
|
|
156
|
+
the single-call path. Each batch result is handled separately; the shell stays
|
|
157
|
+
visible while reads run. The backend permissions remain authoritative.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synopackageland/templates",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Project templates for synopkgland init: native-go, native-python, native-node and native-rust (plain or Vue, with jobs, delegate and root-ops overlays), binary and compose.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {"type": "git", "url": "git+https://github.com/synopackageland/syno-pkg-land-templates.git"},
|
|
@@ -62,26 +62,30 @@ call `greeting.set`.
|
|
|
62
62
|
|
|
63
63
|
## DSM Application Portal (`portal`)
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
DSM
|
|
84
|
-
|
|
65
|
+
From DSM 7.2-64570, Apps can declare `portal` without an executor. DSM's
|
|
66
|
+
web-config resource registers the entry, and INFO
|
|
67
|
+
`instuninst_restart_services="nginx.service"` asks DSM to reload nginx after
|
|
68
|
+
install, upgrade and uninstall. The reload is asynchronous; package hooks stay
|
|
69
|
+
unprivileged and never reload nginx themselves. Schema-2 Compose/gateway Apps
|
|
70
|
+
may also use a top-level `portal`; legacy `integration.web.portal` is unsupported.
|
|
71
|
+
|
|
72
|
+
`synopkgland add portal` adds `portal: {}` (or set
|
|
73
|
+
`portal: {alias: my-app}` in dsm-package.yaml). Without an executor, set
|
|
74
|
+
`os_min_ver` to 7.2-64570 or later before adding Portal; the command refuses a
|
|
75
|
+
lower target and never raises it silently. With an executor, a portal needs
|
|
76
|
+
`os_min_ver` 7.1-42661 or later. Below DSM 7.2 the executor registers the entry:
|
|
77
|
+
DSM 7.1 rejects the unsigned web-config worker, there is no alias wizard step,
|
|
78
|
+
and an administrator enables it through `ctx.portal.register()` after executor
|
|
79
|
+
approval. From DSM 7.2, the SPK registers it as a DSM resource.
|
|
80
|
+
|
|
81
|
+
Read `synopkgland skill show portal` before changing it. The install wizard only
|
|
82
|
+
proposes the alias; an administrator activates it in the App's portal panel
|
|
83
|
+
(`ctx.portal`, DSM's own `SYNO.Core.AppPortal` in the admin session) or Control
|
|
84
|
+
Panel > Login Portal > Applications. Other signed-in users get the link from
|
|
85
|
+
read-only `portal.status`, else "ask an administrator". Never write
|
|
86
|
+
`AppPortal.json` or `@appconf/<package>`, never add a backend route that sets
|
|
87
|
+
the alias, and never relay QuickConnect responses or build QuickConnect links.
|
|
88
|
+
Beyond the packaging floor, support is detected with SYNO.API.Info.
|
|
85
89
|
|
|
86
90
|
### Login page look (`login_style`)
|
|
87
91
|
|
|
@@ -14,7 +14,7 @@ compose:
|
|
|
14
14
|
web: {service: web, port: 8080, health: {path: /, status: 200}}
|
|
15
15
|
network: {egress: isolated}
|
|
16
16
|
identity_gateway:
|
|
17
|
-
sdk: 0.
|
|
17
|
+
sdk: 0.17.0
|
|
18
18
|
mode: none
|
|
19
19
|
listen: {http: 28000}
|
|
20
20
|
# Web Apps default to new-page through the shared app-launch resolver.
|
|
@@ -297,23 +297,21 @@ refusal for unknown methods.
|
|
|
297
297
|
|
|
298
298
|
## DSM Application Portal link
|
|
299
299
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
`
|
|
315
|
-
package that registers a portal (Package Center error 320); 7.3.2 and 7.4
|
|
316
|
-
install it; 7.2 is untested. To keep supporting older DSM, remove `portal:`.
|
|
300
|
+
From DSM 7.2-64570, an App can declare `portal` without an executor. Set
|
|
301
|
+
`os_min_ver` to that floor or later before `synopkgland add portal`; the command
|
|
302
|
+
refuses a lower no-privilege target instead of silently raising it. The command
|
|
303
|
+
adds `portal: {}` so `https://<nas>/<alias>/` opens the App after DSM sign-in.
|
|
304
|
+
The install wizard proposes the alias; an administrator applies it in the App
|
|
305
|
+
panel or Control Panel > Login Portal > Applications. Other users see the link
|
|
306
|
+
or "ask an administrator". DSM owns the setting; `portal.status` only reads it.
|
|
307
|
+
|
|
308
|
+
Every Portal SPK asks DSM to reload nginx through INFO
|
|
309
|
+
`instuninst_restart_services="nginx.service"` after install, upgrade and
|
|
310
|
+
uninstall. The reload is asynchronous; package hooks stay unprivileged.
|
|
311
|
+
DSM 7.1-42661 still needs an approved executor to register the entry, since its
|
|
312
|
+
web-config worker rejects unsigned packages. For that route, declare an
|
|
313
|
+
executor capability, for example `synopkgland add root-ops portal`. See
|
|
314
|
+
`synopkgland skill show portal` before changing Portal settings.
|
|
317
315
|
|
|
318
316
|
The build also gives the portal a login page look: a neutral default
|
|
319
317
|
wallpaper (its thumbnail is what Control Panel > Login Portal > Applications
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.
|
|
2
|
-
github.com/synopackageland/syno-pkg-land-sdk v0.
|
|
1
|
+
github.com/synopackageland/syno-pkg-land-sdk v0.17.0 h1:DaYDHCDlukkXtTm+3m26p7pU86r298MTasKDrV/tRl4=
|
|
2
|
+
github.com/synopackageland/syno-pkg-land-sdk v0.17.0/go.mod h1:vixf5Lo4PssQDGU5zam0Mb5sFu9wPUP4Ck36BMAVbNc=
|
|
3
3
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
|
4
4
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"name": "native-dsm-vue-app",
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"@lucide/vue": "1.48.0",
|
|
10
|
-
"@synopackageland/sdk": "0.
|
|
11
|
-
"@synopackageland/ui": "0.12.
|
|
10
|
+
"@synopackageland/sdk": "0.17.0",
|
|
11
|
+
"@synopackageland/ui": "0.12.1",
|
|
12
12
|
"vue": "3.5.43"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
@@ -1152,8 +1152,8 @@
|
|
|
1152
1152
|
}
|
|
1153
1153
|
},
|
|
1154
1154
|
"node_modules/@synopackageland/sdk": {
|
|
1155
|
-
"version": "0.
|
|
1156
|
-
"resolved": "https://registry.npmjs.org/@synopackageland/sdk/-/sdk-0.
|
|
1155
|
+
"version": "0.17.0",
|
|
1156
|
+
"resolved": "https://registry.npmjs.org/@synopackageland/sdk/-/sdk-0.17.0.tgz",
|
|
1157
1157
|
"integrity": "sha512-3h7PjF55kJjKDJHZE9tbR0UxOgmxfGEnY9eKkpKPStdy8OiTOGjOlB298+YvJtkRBLiOZ13En10sa/ADbU9DnA==",
|
|
1158
1158
|
"license": "MIT",
|
|
1159
1159
|
"peerDependencies": {
|
|
@@ -1171,9 +1171,9 @@
|
|
|
1171
1171
|
}
|
|
1172
1172
|
},
|
|
1173
1173
|
"node_modules/@synopackageland/ui": {
|
|
1174
|
-
"version": "0.12.
|
|
1175
|
-
"resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.12.
|
|
1176
|
-
"integrity": "sha512-
|
|
1174
|
+
"version": "0.12.1",
|
|
1175
|
+
"resolved": "https://registry.npmjs.org/@synopackageland/ui/-/ui-0.12.1.tgz",
|
|
1176
|
+
"integrity": "sha512-nyHKefcyKWiaXpjsjFfxrgsH4RSDHgO71bOV8uGRf89Ch2kDapNTfv42RtR2idIfNTt7A3/gKw8eUlyRn9VJDQ==",
|
|
1177
1177
|
"license": "MIT",
|
|
1178
1178
|
"dependencies": {
|
|
1179
1179
|
"@internationalized/date": "3.12.4",
|
|
@@ -43,10 +43,24 @@ async function refresh() {
|
|
|
43
43
|
const early = !login.ready.value
|
|
44
44
|
const call: typeof login.call = early ? (method, params) => props.ctx.call(method, params) : login.call
|
|
45
45
|
busy.value = true; error.value = ''
|
|
46
|
+
// An administrator DSM names up front has the greeting read beside the status.
|
|
47
|
+
const startupBatch = props.ctx.user?.isAdmin === true ? props.ctx.batch : undefined
|
|
48
|
+
const read = !startupBatch && props.ctx.user?.isAdmin ? call<{text:string}>('greeting.get') : undefined
|
|
49
|
+
read?.catch(() => {})
|
|
46
50
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
51
|
+
if(startupBatch) {
|
|
52
|
+
const batch = early ? startupBatch : login.batch
|
|
53
|
+
const results = await batch([{method:'app.status'},{method:'greeting.get'}])
|
|
54
|
+
const status = results[0]!, greeting = results[1]!
|
|
55
|
+
if(!status.success) throw status.error
|
|
56
|
+
if(!disposed) app.value = status.data as AppStatus
|
|
57
|
+
if(!greeting.success) throw greeting.error
|
|
58
|
+
if(!disposed) text.value = (greeting.data as {text:string}).text
|
|
59
|
+
} else {
|
|
60
|
+
const a = await call<AppStatus>('app.status')
|
|
61
|
+
if(!disposed) app.value = a
|
|
62
|
+
if(a.admin) {const greeting = await (read ?? call<{text:string}>('greeting.get')); if(!disposed) text.value = greeting.text}
|
|
63
|
+
}
|
|
50
64
|
} catch(e) {if(!disposed && !(early && login.enabled.value)) error.value = message(e)}
|
|
51
65
|
finally {if(!disposed) {busy.value=false; if(again) {again=false; void refresh()}}}
|
|
52
66
|
}
|
|
@@ -88,6 +88,19 @@ test('the app renders and loads status while the session check is still pending'
|
|
|
88
88
|
expect([...root.querySelectorAll('[data-app-status] [data-app-value]')].map(node=>node.textContent)).toEqual(['Eager','admin','1'])
|
|
89
89
|
}finally{app.unmount();root.remove()}
|
|
90
90
|
})
|
|
91
|
+
test('an administrator DSM names up front has the greeting read beside the status',async()=>{
|
|
92
|
+
const call=vi.fn(async(method:string)=>{
|
|
93
|
+
if(method==='app.status')return new Promise(()=>{})
|
|
94
|
+
if(method==='greeting.get')return {text:'Hello Early'}
|
|
95
|
+
throw {code:'unknown_method'}
|
|
96
|
+
})
|
|
97
|
+
const root=document.createElement('div');document.body.append(root)
|
|
98
|
+
const app=mount(root,dsmWindow({call,user:{name:'admin',isAdmin:true}}))
|
|
99
|
+
try{
|
|
100
|
+
await settle()
|
|
101
|
+
expect(call.mock.calls.map(([method])=>method)).toContain('greeting.get')
|
|
102
|
+
}finally{app.unmount();root.remove()}
|
|
103
|
+
})
|
|
91
104
|
test('a status request that raced the session check is retried once the session is confirmed',async()=>{
|
|
92
105
|
let session=false,rejectEarly:(e:unknown)=>void=()=>{}
|
|
93
106
|
const call=vi.fn(async(method:string)=>{
|
|
@@ -239,3 +252,16 @@ test('an administrator sees a pending Shared Folder with the grant step; a plain
|
|
|
239
252
|
expect(root.textContent).toContain('Ask an administrator')
|
|
240
253
|
}finally{app.unmount();root.remove();plain.close()}
|
|
241
254
|
})
|
|
255
|
+
|
|
256
|
+
test('batches startup reads for a known administrator', async () => {
|
|
257
|
+
const calls: string[]=[]
|
|
258
|
+
const batch=vi.fn(async () => [{success:true as const,data:{package:'Batch',version:'1',user:'admin',admin:true}},{success:true as const,data:{text:'Hello Batch'}}])
|
|
259
|
+
const ctx=dsmWindow({user:{name:'admin',isAdmin:true},batch:batch as NonNullable<Context['batch']>,call:async <T,>(method:string)=>{calls.push(method);if(method==='identity.status')return {enabled:false} as T;throw {code:'unknown_method'}}})
|
|
260
|
+
const root=document.createElement('div');document.body.append(root)
|
|
261
|
+
const app=mount(root,ctx)
|
|
262
|
+
await settle()
|
|
263
|
+
expect(batch).toHaveBeenCalledWith([{method:'app.status'},{method:'greeting.get'}])
|
|
264
|
+
expect(calls).not.toContain('app.status');expect(calls).not.toContain('greeting.get')
|
|
265
|
+
expect((root.querySelector('textarea') as HTMLTextAreaElement).value).toBe('Hello Batch')
|
|
266
|
+
app.unmount();root.remove()
|
|
267
|
+
})
|
|
@@ -104,8 +104,8 @@ dependencies = [
|
|
|
104
104
|
|
|
105
105
|
[[package]]
|
|
106
106
|
name = "syno-pkg-land-app"
|
|
107
|
-
version = "0.
|
|
108
|
-
source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.
|
|
107
|
+
version = "0.17.0"
|
|
108
|
+
source = "git+https://github.com/synopackageland/syno-pkg-land-sdk?tag=v0.17.0#91fa899beec935af9668a04c24ccd64b0d03b00b"
|
|
109
109
|
dependencies = [
|
|
110
110
|
"libc",
|
|
111
111
|
"serde",
|
|
@@ -9,7 +9,7 @@ libc = "0.2"
|
|
|
9
9
|
serde = { version = "1", features = ["derive"] }
|
|
10
10
|
serde_json = "1"
|
|
11
11
|
# The SDK version is the tag; `synopkgland check` reads it from Cargo.lock.
|
|
12
|
-
syno-pkg-land-app = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.
|
|
12
|
+
syno-pkg-land-app = { git = "https://github.com/synopackageland/syno-pkg-land-sdk", tag = "v0.17.0" }
|
|
13
13
|
|
|
14
14
|
[profile.release]
|
|
15
15
|
# A handler panic becomes an "internal" reply; abort would end the App without one.
|