@synopackageland/templates 0.40.1 → 0.40.3
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 +609 -0
- package/README.md +14 -1
- package/package.json +3 -2
- package/templates/_shared/AGENTS.md +51 -6
- package/templates/_shared/e2e.ai.config.ts +5 -0
- package/templates/_shared/package.json +9 -5
- package/templates/_shared/tests/ctl/status.test.mjs +17 -0
- package/templates/_shared/tests/e2e/action.ai.e2e.ts +14 -0
- package/templates/_shared/tests/e2e/action.e2e.ts +14 -0
- package/templates/_shared/tests/e2e/support/action.ts +30 -0
- package/templates/_shared/tests/local/package.test.mjs +16 -0
- package/templates/_shared/tests/support/cli.mjs +11 -0
- package/templates/_shared/tests/unit/registry.test.mjs +16 -0
- package/templates/binary/README.md +16 -0
- package/templates/compose/README.md +16 -0
- package/templates/native-go/README.md +29 -1
- package/templates/native-go/backend/internal/app/background_test.go +17 -2
- package/templates/native-go-jobs/backend/internal/app/background_routes_test.go +12 -7
- package/templates/native-go-vue/README.md +16 -1
- package/templates/native-node/README.md +17 -2
- package/templates/native-node/dsm-package.yaml +1 -1
- package/templates/native-node-vue/README.md +16 -1
- package/templates/native-python/README.md +16 -1
- package/templates/native-python-vue/README.md +16 -1
- package/templates/native-rust/README.md +16 -1
- package/templates/native-rust-vue/README.md +16 -1
|
@@ -22,9 +22,52 @@ sections at the end name each language's files, types and test commands.
|
|
|
22
22
|
|
|
23
23
|
From the project root, run `synopkgland check --json`,
|
|
24
24
|
`synopkgland build --json`, then `synopkgland inspect <spk> --json` on the emitted SPK.
|
|
25
|
-
For Vue, first run `npm ci
|
|
25
|
+
For Vue, first run `npm ci --include=dev` and `npm run build` in frontend. Run its tests only if the user chose tests.
|
|
26
26
|
Do not connect to or change a NAS without explicit task authorization.
|
|
27
27
|
|
|
28
|
+
## Optional testing
|
|
29
|
+
|
|
30
|
+
Ask once whether the user wants tests: none, local/non-AI, or optional AI too.
|
|
31
|
+
Accept no tests, remember the choice, do not nag and never block a build or release
|
|
32
|
+
on tests. Scaffolding is included by default; writing and running tests are optional.
|
|
33
|
+
Read `synopkgland skill show testing` when setting up or extending tests.
|
|
34
|
+
|
|
35
|
+
The default `npm test` runs only fast local unit/ctl registry contracts.
|
|
36
|
+
At the project root, run `npm install --include=dev` first; after committing its
|
|
37
|
+
lockfile, use `npm ci --include=dev`.
|
|
38
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI and frontend builds need dev tools.
|
|
39
|
+
Before `test:local`, prepare the backend/frontend as described below and run
|
|
40
|
+
`synopkgland ctl generate` for a native App.
|
|
41
|
+
`npm run test:local` opts into check/build/inspect. Only on explicit user request,
|
|
42
|
+
run `npm run test:nas:ctl`, `npm run e2e:smoke`, `npm run e2e:ui` or `npm run e2e:ai`.
|
|
43
|
+
The full suite is slow. Never call these from build, install, DSM hooks or release.
|
|
44
|
+
For non-AI use, no model/key is required; AI dependencies are optional installs.
|
|
45
|
+
|
|
46
|
+
Assert meaningful ctl values and the UI primary action's real result, not only
|
|
47
|
+
schemas or page rendering. Adapt the generated examples to actual App operations.
|
|
48
|
+
Long jobs use start -> progress -> pause -> resume without reset -> cancel with
|
|
49
|
+
bounded polling and fixture cleanup; never wait for full completion.
|
|
50
|
+
|
|
51
|
+
AI steps require a user-selected endpoint, key location and model. If no profile
|
|
52
|
+
is configured and setup is unknown, ask the user for those three values. Choose
|
|
53
|
+
the cheapest adequate model and low effort; never default to an expensive model.
|
|
54
|
+
Use E2E_LLM_BASE_URL, E2E_LLM_MODEL, E2E_LLM_API_KEY_FILE (mode 600) or
|
|
55
|
+
E2E_LLM_API_KEY, and optional E2E_LLM_REASONING_EFFORT (default low).
|
|
56
|
+
|
|
57
|
+
## Upstream web UI only
|
|
58
|
+
|
|
59
|
+
For an `--upstream-fork` wrapper whose only UI is the upstream web panel, set
|
|
60
|
+
`ui: false` in `synology/dsm-package.yaml` and configure `identity_gateway`
|
|
61
|
+
for that panel. `--ui plain` selects `ui/app.js`; it does not mean `ui: false`.
|
|
62
|
+
The current scaffold still creates UI and browser-test examples: remove
|
|
63
|
+
`synology/ui/` (or `synology/frontend/` for Vue), `synology/tests/e2e/`,
|
|
64
|
+
`synology/e2e.config.ts` and `synology/e2e.ai.config.ts`, and remove the
|
|
65
|
+
`e2e:*` scripts from `synology/package.json`.
|
|
66
|
+
Keep `synology/backend/`, `synology/i18n/messages/`, `synology/operations.json` and
|
|
67
|
+
the local/ctl tests: the entry gate still needs the backend and message catalogs.
|
|
68
|
+
Read `synopkgland skill show upstream-fork` and
|
|
69
|
+
`synopkgland skill show orchestrator` for the wrapper workflow.
|
|
70
|
+
|
|
28
71
|
## Lifecycle scripts (`dsm-scripts/`)
|
|
29
72
|
|
|
30
73
|
`dsm-scripts/` holds DSM's own lifecycle scripts (preinst, postinst, preupgrade,
|
|
@@ -274,8 +317,8 @@ Do not force HTTPS for ordinary CRUD/settings/file Apps. Only secure context
|
|
|
274
317
|
features, pairing or a strict secure Origin policy justify it. Return
|
|
275
318
|
`https_required`, not a generic authentication error, and render the SDK's
|
|
276
319
|
`identityCopy(locale).httpsTitle` / `errors.https_required` with reopening
|
|
277
|
-
instructions.
|
|
278
|
-
|
|
320
|
+
instructions. Secure-origin enforcement is opt-in and does not replace
|
|
321
|
+
authentication, origin or permission checks.
|
|
279
322
|
|
|
280
323
|
Call `readDsmPorts` for `dms_https` only when `ctx.user?.isAdmin === true`;
|
|
281
324
|
`readDsmHttpsURL(ctx, location.href)` applies this guard. Ordinary users see
|
|
@@ -287,12 +330,14 @@ For an old tab's stale `X-SYNO-TOKEN`, SDK `createClient` can opt in through
|
|
|
287
330
|
`effect: read`, `kind: rpc` calls retry once on `session_required`. Writes, AI,
|
|
288
331
|
streams, batches and unknown methods never retry. Existing `ctx.call` is not
|
|
289
332
|
automatically wrapped. Verify the installed SDK exports these APIs before
|
|
290
|
-
using them. Follow the [CLI skill](https://github.com/synopackageland/syno-pkg-land-cli/blob/main/skills/SKILL.md
|
|
333
|
+
using them. Follow the [CLI skill](https://github.com/synopackageland/syno-pkg-land-cli/blob/main/skills/SKILL.md)
|
|
291
334
|
and [SDK guide](https://github.com/synopackageland/syno-pkg-land-sdk/blob/main/docs/https-and-session-recovery.md).
|
|
292
|
-
|
|
335
|
+
When the user requests NAS acceptance, test both HTTP/HTTPS entries, both user roles and cross-tab session recovery.
|
|
293
336
|
|
|
294
337
|
## Go backend
|
|
295
338
|
|
|
339
|
+
- `api.RequireSecureOrigin(request)` opts into secure-origin enforcement; it does
|
|
340
|
+
not replace authentication, origin or permission checks.
|
|
296
341
|
- Routes: `backend/internal/app` (`routes.go` registers them); App
|
|
297
342
|
permissions: `backend/internal/api/permissions.json`. Test with
|
|
298
343
|
`(cd backend && go test ./...)`, then `go vet ./...` and `gofmt -l .`.
|
|
@@ -345,7 +390,7 @@ their capabilities with `synopkgland init --with`.
|
|
|
345
390
|
`new AppError(code, message)`, `AppError.badRequest(message)`. Call services
|
|
346
391
|
through `service(ctx, op, params)` (`service.ts`), which passes the runtime's
|
|
347
392
|
refusals on; a bare `ctx.runtime` refusal would reach the browser as `internal`.
|
|
348
|
-
- Test with `(cd backend && npm ci && npm test && npm run typecheck)`; `test/support.ts`'s
|
|
393
|
+
- Test with `(cd backend && npm ci --include=dev && npm test && npm run typecheck)`; `test/support.ts`'s
|
|
349
394
|
`call` plays the runtime.
|
|
350
395
|
|
|
351
396
|
## Rust backend
|
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
"private": true,
|
|
3
3
|
"type": "module",
|
|
4
4
|
"scripts": {
|
|
5
|
-
"
|
|
5
|
+
"test": "npm run test:unit",
|
|
6
|
+
"test:unit": "node --test tests/unit/*.test.mjs",
|
|
7
|
+
"test:local": "node --test tests/local/*.test.mjs",
|
|
8
|
+
"test:nas:ctl": "node --env-file-if-exists=.env --test tests/ctl/*.test.mjs",
|
|
9
|
+
"e2e:smoke": "node --env-file-if-exists=.env node_modules/@synopackageland/cli/dist/e2e/dsm.js tunnel -- e2e run tests/e2e/smoke.e2e.ts",
|
|
10
|
+
"e2e:ui": "node --env-file-if-exists=.env node_modules/@synopackageland/cli/dist/e2e/dsm.js tunnel -- e2e run tests/e2e/action.e2e.ts",
|
|
11
|
+
"e2e:ai": "node --env-file-if-exists=.env node_modules/@synopackageland/cli/dist/e2e/dsm.js tunnel -- e2e run --config e2e.ai.config.ts tests/e2e/action.ai.e2e.ts"
|
|
6
12
|
},
|
|
7
13
|
"devDependencies": {
|
|
8
|
-
"@synopackageland/cli": "
|
|
14
|
+
"@synopackageland/cli": "~0.48.20",
|
|
9
15
|
"@e2e-dev/web": "^0.14.0",
|
|
10
|
-
"e2e": "^0.19.0"
|
|
11
|
-
"@ai-sdk/openai": "^4.0.91",
|
|
12
|
-
"ai": "^7.0.137"
|
|
16
|
+
"e2e": "^0.19.0"
|
|
13
17
|
}
|
|
14
18
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import { cli } from '../support/cli.mjs';
|
|
4
|
+
|
|
5
|
+
// Invoke only on user request, after ctl discovery/approval for this installed App.
|
|
6
|
+
// Replace this starter command and assertions when the App's contract changes.
|
|
7
|
+
test('installed status reports the expected package and administrator', {
|
|
8
|
+
skip: !process.env.APP_TEST_HOST && 'Set APP_TEST_HOST to an authorized registered host',
|
|
9
|
+
}, () => {
|
|
10
|
+
const command = JSON.parse(process.env.APP_TEST_CTL_COMMAND || '["app","status"]');
|
|
11
|
+
assert.ok(Array.isArray(command) && command.length && command.every(x => typeof x === 'string'));
|
|
12
|
+
const data = cli('ctl', process.env.APP_TEST_APP || '__PACKAGE__', ...command,
|
|
13
|
+
'--host', process.env.APP_TEST_HOST);
|
|
14
|
+
assert.equal(data.package, '__PACKAGE__');
|
|
15
|
+
assert.equal(data.admin, true);
|
|
16
|
+
assert.ok(data.process.uid > 0, 'the App runs as a nonroot package account');
|
|
17
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from '@e2e-dev/web';
|
|
3
|
+
import { actionFixture, assertOutcome, openFixture } from './support/action.ts';
|
|
4
|
+
|
|
5
|
+
// Opt-in semantic step; keep the final result assertion deterministic.
|
|
6
|
+
test('an AI step performs the authorized fixture action', async ({app, browser, agent}) => {
|
|
7
|
+
const {result, expected} = actionFixture();
|
|
8
|
+
const instruction = process.env.APP_TEST_AI_ACTION;
|
|
9
|
+
assert.ok(instruction, 'Set APP_TEST_AI_ACTION to one bounded authorized fixture action');
|
|
10
|
+
const root = await openFixture(app, browser);
|
|
11
|
+
assert.notEqual((await root.locator(result).textContent())?.trim(), expected);
|
|
12
|
+
await agent.act(instruction);
|
|
13
|
+
await assertOutcome(root, result, expected);
|
|
14
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { test } from '@e2e-dev/web';
|
|
3
|
+
import { actionFixture, assertOutcome, openFixture } from './support/action.ts';
|
|
4
|
+
|
|
5
|
+
// An action and its observable result, without a model or AI key.
|
|
6
|
+
// For CRUD, prepare a private fixture first and assert saved/read-back data.
|
|
7
|
+
// For long jobs, replace this with the bounded lifecycle in the testing guide.
|
|
8
|
+
test('the primary action changes the fixture outcome', async ({app, browser}) => {
|
|
9
|
+
const {action, result, expected} = actionFixture();
|
|
10
|
+
const root = await openFixture(app, browser);
|
|
11
|
+
assert.notEqual((await root.locator(result).textContent())?.trim(), expected);
|
|
12
|
+
await root.locator(action).click();
|
|
13
|
+
await assertOutcome(root, result, expected);
|
|
14
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { setTimeout } from 'node:timers/promises';
|
|
3
|
+
import { openApp, openPortal, readSmokeEntry, signIn } from '@synopackageland/cli/e2e';
|
|
4
|
+
|
|
5
|
+
// Configure an authorized fixture action with a known, initially different result.
|
|
6
|
+
export function actionFixture() {
|
|
7
|
+
const action = process.env.APP_TEST_ACTION;
|
|
8
|
+
const result = process.env.APP_TEST_RESULT;
|
|
9
|
+
const expected = process.env.APP_TEST_EXPECTED;
|
|
10
|
+
assert.ok(action && result && expected, 'Set APP_TEST_ACTION, APP_TEST_RESULT and APP_TEST_EXPECTED');
|
|
11
|
+
return {action, result, expected};
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export async function openFixture(app: Parameters<typeof signIn>[0], browser: NonNullable<Parameters<typeof openApp>[3]>) {
|
|
15
|
+
const entry = readSmokeEntry({entry: process.env.APP_TEST_ENTRY});
|
|
16
|
+
await signIn(app);
|
|
17
|
+
return entry.window ? openApp(app, entry.appId, 30_000, browser) : openPortal(app, entry.entry, browser);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
type Root = Awaited<ReturnType<typeof openFixture>>;
|
|
21
|
+
export async function assertOutcome(root: Root, result: string, expected: string) {
|
|
22
|
+
const deadline = Date.now() + 10_000;
|
|
23
|
+
let actual;
|
|
24
|
+
do {
|
|
25
|
+
actual = (await root.locator(result).textContent({timeout: 1000}))?.trim();
|
|
26
|
+
if (actual === expected) break;
|
|
27
|
+
await setTimeout(100);
|
|
28
|
+
} while (Date.now() < deadline);
|
|
29
|
+
assert.equal(actual, expected, 'the primary action changed its real outcome');
|
|
30
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
import { cli } from '../support/cli.mjs';
|
|
5
|
+
|
|
6
|
+
// Explicit local build tier; no deployment or NAS connection.
|
|
7
|
+
test('the App checks, builds and passes package inspection', {timeout: 300_000}, () => {
|
|
8
|
+
cli('check');
|
|
9
|
+
const built = cli('build', '--arch', process.env.APP_TEST_ARCH || 'x86_64');
|
|
10
|
+
assert.ok(built.outputs.length > 0);
|
|
11
|
+
for (const spk of built.outputs) {
|
|
12
|
+
assert.equal(existsSync(spk), true);
|
|
13
|
+
const inspected = cli('inspect', spk);
|
|
14
|
+
assert.equal(inspected.spk, spk);
|
|
15
|
+
}
|
|
16
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
|
|
4
|
+
// Use the actual CLI and its registered host/session, never a direct HTTP shortcut.
|
|
5
|
+
export function cli(...args) {
|
|
6
|
+
const result = JSON.parse(execFileSync('synopkgland', [...args, '--json'], {
|
|
7
|
+
encoding: 'utf8', timeout: 120_000, stdio: ['ignore', 'pipe', 'pipe'],
|
|
8
|
+
}));
|
|
9
|
+
assert.equal(result.ok, true);
|
|
10
|
+
return result.data;
|
|
11
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { test } from 'node:test';
|
|
4
|
+
|
|
5
|
+
// Pin meaningful command behavior, not just JSON schema validity.
|
|
6
|
+
const registry = existsSync('operations.json')
|
|
7
|
+
? JSON.parse(readFileSync('operations.json', 'utf8')) : undefined;
|
|
8
|
+
test('the starter status command remains a read of app.status', {
|
|
9
|
+
skip: !registry?.operations?.['app.status'] && 'No starter status operation; add your own unit contract',
|
|
10
|
+
}, () => {
|
|
11
|
+
const status = registry.operations['app.status'];
|
|
12
|
+
assert.equal(status.effect, 'read');
|
|
13
|
+
assert.deepEqual(status.cli, ['app', 'status']);
|
|
14
|
+
assert.equal(status.flow.type, 'invoke');
|
|
15
|
+
assert.equal(status.flow.method, 'app.status');
|
|
16
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# __DISPLAY_NAME__
|
|
2
|
+
|
|
3
|
+
## Local tests
|
|
4
|
+
|
|
5
|
+
From the project root, install the test tools before running the optional tests:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install --include=dev
|
|
9
|
+
npm test
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
13
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
14
|
+
The scaffold provides example prebuilt binaries; replace them with your own
|
|
15
|
+
Linux binaries before packaging. See `AGENTS.md` for the package workflow.
|
|
16
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# __DISPLAY_NAME__
|
|
2
|
+
|
|
3
|
+
## Local tests
|
|
4
|
+
|
|
5
|
+
From the project root, install the test tools before running the optional tests:
|
|
6
|
+
|
|
7
|
+
```sh
|
|
8
|
+
npm install --include=dev
|
|
9
|
+
npm test
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
13
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
14
|
+
Configure the upstream image and its real readiness endpoint before
|
|
15
|
+
`test:local`; see `AGENTS.md` for the Compose packaging workflow.
|
|
16
|
+
|
|
@@ -25,10 +25,25 @@ independent package-account worker and report phase/progress. Authenticate
|
|
|
25
25
|
status/result polling by that ID; a slow NAS or failed status read leaves the
|
|
26
26
|
last queued/running state intact. After a lost admission reply, reuse the same
|
|
27
27
|
idempotency key and input instead of replaying a mutation with a new key.
|
|
28
|
-
Jobs default
|
|
28
|
+
Jobs use the SDK's default operation allowance; approved executor limits remain separate. ctl keeps
|
|
29
29
|
its 62-second transport default; use an explicit `--timeout` or an existing
|
|
30
30
|
nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/main/docs/request-deadlines.md).
|
|
31
31
|
|
|
32
|
+
## Local tests
|
|
33
|
+
|
|
34
|
+
From the project root, install the test tools before running the optional tests:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm install --include=dev
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
42
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
43
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
44
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
45
|
+
run `synopkgland ctl generate`.
|
|
46
|
+
|
|
32
47
|
## Quick start
|
|
33
48
|
|
|
34
49
|
Needs Node.js with `synopkgland` installed, and Go 1.26.10.
|
|
@@ -443,3 +458,16 @@ Draft writes require a non-mutating preview and explicit human confirmation.
|
|
|
443
458
|
Long operations use the existing job flow; tools own polling and pagination.
|
|
444
459
|
|
|
445
460
|
Use a matching SDK/CLI/templates release containing the registry support.
|
|
461
|
+
|
|
462
|
+
## Upstream web UI only
|
|
463
|
+
|
|
464
|
+
`init --upstream-fork --ui plain` still creates a plain SDK UI; it does not
|
|
465
|
+
select `ui: false`. For an upstream web panel with no SDK UI, set `ui: false`
|
|
466
|
+
in `synology/dsm-package.yaml` and configure `identity_gateway` for the panel.
|
|
467
|
+
Remove `synology/ui/` (or `synology/frontend/` for Vue),
|
|
468
|
+
`synology/tests/e2e/`, `synology/e2e.config.ts` and
|
|
469
|
+
`synology/e2e.ai.config.ts`, and remove the `e2e:*` npm scripts.
|
|
470
|
+
Keep `synology/backend/`, `synology/i18n/messages/`, `synology/operations.json` and
|
|
471
|
+
the local/ctl tests: the entry gate still needs the backend and message catalogs.
|
|
472
|
+
Read `synopkgland skill show upstream-fork` and
|
|
473
|
+
`synopkgland skill show orchestrator` for the wrapper workflow.
|
|
@@ -34,6 +34,10 @@ func newBgEnv(t *testing.T) *bgEnv {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
func (e *bgEnv) call(auth api.Authenticator, method, params string) (string, map[string]any) {
|
|
37
|
+
return e.callContext(context.Background(), auth, method, params)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func (e *bgEnv) callContext(ctx context.Context, auth api.Authenticator, method, params string) (string, map[string]any) {
|
|
37
41
|
e.t.Helper()
|
|
38
42
|
// SDK policy provides owner routes immediately.
|
|
39
43
|
routes := newBackgroundRoutes(e.store, e.dataDir)
|
|
@@ -42,6 +46,7 @@ func (e *bgEnv) call(auth api.Authenticator, method, params string) (string, map
|
|
|
42
46
|
e.t.Fatal(err)
|
|
43
47
|
}
|
|
44
48
|
req := httptest.NewRequest(http.MethodPost, "/api.cgi", strings.NewReader(`{"method":"`+method+`","params":`+params+`}`))
|
|
49
|
+
req = req.WithContext(ctx)
|
|
45
50
|
req.Header.Set("Content-Type", "application/json")
|
|
46
51
|
req.Header.Set("Sec-Fetch-Site", "same-origin")
|
|
47
52
|
rec := httptest.NewRecorder()
|
|
@@ -112,8 +117,18 @@ func TestBackgroundStatusReportsLimitsAndWorker(t *testing.T) {
|
|
|
112
117
|
t.Fatalf("%s %v", code, out)
|
|
113
118
|
}
|
|
114
119
|
limits := d["limits"].(map[string]any)
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
for name, want := range map[string]float64{
|
|
121
|
+
"maxInputBytes": background.MaxInputBytes,
|
|
122
|
+
"maxQueued": background.MaxQueued,
|
|
123
|
+
"maxSchedules": background.MaxSchedules,
|
|
124
|
+
"maxTotalBytes": background.MaxTotalBytes,
|
|
125
|
+
"maxOwnerPending": background.MaxOwnerPending,
|
|
126
|
+
"maxOwnerBytes": background.MaxOwnerBytes,
|
|
127
|
+
"jobTimeoutSeconds": background.JobTimeout.Seconds(),
|
|
128
|
+
} {
|
|
129
|
+
if got := limits[name]; got != want {
|
|
130
|
+
t.Errorf("limits[%q] = %v, want %v", name, got, want)
|
|
131
|
+
}
|
|
117
132
|
}
|
|
118
133
|
info := background.WorkerInfo{PID: os.Getpid(), UID: os.Getuid(), Username: "pkg"}
|
|
119
134
|
if err := e.store.Heartbeat(context.Background(), info); err != nil {
|
|
@@ -52,7 +52,7 @@ func TestBackgroundBusyErrorCode(t *testing.T) {
|
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
55
|
-
func
|
|
55
|
+
func TestBackgroundRequestSubmitDeadlineAndProductionConstruction(t *testing.T) {
|
|
56
56
|
e := newBgEnv(t)
|
|
57
57
|
dir := filepath.Join(t.TempDir(), "background")
|
|
58
58
|
e.store = background.NewRequestStore(dir)
|
|
@@ -68,12 +68,17 @@ func TestBackgroundRequestSubmitBusyAndProductionConstruction(t *testing.T) {
|
|
|
68
68
|
t.Fatal(err)
|
|
69
69
|
}
|
|
70
70
|
defer syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
|
|
71
|
+
// Use an earlier caller deadline instead of waiting for the SDK's full
|
|
72
|
+
// operation allowance. The API reports that deadline, not jobs_busy.
|
|
73
|
+
budget := min(50*time.Millisecond, background.RequestLockTimeout/2)
|
|
74
|
+
ctx, cancel := context.WithTimeout(context.Background(), budget)
|
|
75
|
+
defer cancel()
|
|
71
76
|
start := time.Now()
|
|
72
|
-
code, out := e.
|
|
73
|
-
if code != "
|
|
74
|
-
t.Fatalf("
|
|
77
|
+
code, out := e.callContext(ctx, alice, "bg.jobs.submit", e.submitParams("k1"))
|
|
78
|
+
if code != "request_timeout" || time.Since(start) > budget+time.Second {
|
|
79
|
+
t.Fatalf("submit deadline: %s %+v", code, out)
|
|
75
80
|
}
|
|
76
|
-
// The
|
|
81
|
+
// The production route assembly must also honor an earlier request deadline.
|
|
77
82
|
srv, err := api.NewServerWithPermissions(alice, []byte(`{"schema":1,"default":"administrators","methods":{}}`), BackgroundRoutes(filepath.Dir(dir))...)
|
|
78
83
|
if err != nil {
|
|
79
84
|
t.Fatal(err)
|
|
@@ -82,8 +87,8 @@ func TestBackgroundRequestSubmitBusyAndProductionConstruction(t *testing.T) {
|
|
|
82
87
|
r.Header.Set("Content-Type", "application/json")
|
|
83
88
|
r.Header.Set("Sec-Fetch-Site", "same-origin")
|
|
84
89
|
w := httptest.NewRecorder()
|
|
85
|
-
srv.ServeHTTP(w, r)
|
|
86
|
-
if !strings.Contains(w.Body.String(), `"
|
|
90
|
+
api.WithRequestDeadline(srv, api.Timeout(budget)).ServeHTTP(w, r)
|
|
91
|
+
if !strings.Contains(w.Body.String(), `"request_timeout"`) {
|
|
87
92
|
t.Fatal(w.Body.String())
|
|
88
93
|
}
|
|
89
94
|
}
|
|
@@ -7,9 +7,24 @@ files, portal, jobs and delegate components come from `@synopackageland/sdk/vue`
|
|
|
7
7
|
`github.com/synopackageland/syno-pkg-land-sdk` version in `backend/go.mod`. See
|
|
8
8
|
<https://github.com/synopackageland/syno-pkg-land-sdk> for the components and their props.
|
|
9
9
|
|
|
10
|
+
## Local tests
|
|
11
|
+
|
|
12
|
+
From the project root, install the test tools before running the optional tests:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install --include=dev
|
|
16
|
+
npm test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
20
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
21
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
22
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
23
|
+
run `synopkgland ctl generate`.
|
|
24
|
+
|
|
10
25
|
```sh
|
|
11
26
|
cd frontend
|
|
12
|
-
npm ci
|
|
27
|
+
npm ci --include=dev
|
|
13
28
|
npm test
|
|
14
29
|
npm run build
|
|
15
30
|
```
|
|
@@ -25,16 +25,31 @@ independent package-account worker and report phase/progress. Authenticate
|
|
|
25
25
|
status/result polling by that ID; a slow NAS or failed status read leaves the
|
|
26
26
|
last queued/running state intact. After a lost admission reply, reuse the same
|
|
27
27
|
idempotency key and input instead of replaying a mutation with a new key.
|
|
28
|
-
Jobs default
|
|
28
|
+
Jobs use the SDK's default operation allowance; approved executor limits remain separate. ctl keeps
|
|
29
29
|
its 62-second transport default; use an explicit `--timeout` or an existing
|
|
30
30
|
nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/main/docs/request-deadlines.md).
|
|
31
31
|
|
|
32
|
+
## Local tests
|
|
33
|
+
|
|
34
|
+
From the project root, install the test tools before running the optional tests:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm install --include=dev
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
42
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
43
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
44
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
45
|
+
run `synopkgland ctl generate`.
|
|
46
|
+
|
|
32
47
|
## Quick start
|
|
33
48
|
|
|
34
49
|
Needs Node.js 20 or later with `synopkgland` installed. The SPK depends on DSM's **Node.js v20** package (INFO `install_dep_packages`).
|
|
35
50
|
|
|
36
51
|
```bash
|
|
37
|
-
(cd backend && npm ci && npm test && npm run build) # method tests, then app/*.js
|
|
52
|
+
(cd backend && npm ci --include=dev && npm test && npm run build) # method tests, then app/*.js
|
|
38
53
|
synopkgland ctl generate
|
|
39
54
|
synopkgland check
|
|
40
55
|
synopkgland build # → dist/__PACKAGE__-0.1.0-0001-{x86_64,aarch64}.spk
|
|
@@ -42,7 +42,7 @@ arches: [x86_64, aarch64]
|
|
|
42
42
|
# and app/permissions.json, then runs `node main.js` in backend/app as the
|
|
43
43
|
# package account, once per call, with Package Center's Node.js (node.package
|
|
44
44
|
# becomes an install dependency). backend/src compiles to backend/app: run
|
|
45
|
-
# `npm ci` and `npm run build` in backend before check and build. sdk is the
|
|
45
|
+
# `npm ci --include=dev` and `npm run build` in backend before check and build. sdk is the
|
|
46
46
|
# SDK release whose runtime and Node SDK (node_modules/@synopackageland/app)
|
|
47
47
|
# the build ships; the build reads the methods from `main.js --describe`.
|
|
48
48
|
backend:
|
|
@@ -7,9 +7,24 @@ delegate components come from `@synopackageland/sdk/vue` (types from
|
|
|
7
7
|
`@synopackageland/sdk`); its version must equal the `backend.sdk` in dsm-package.yaml. See
|
|
8
8
|
<https://github.com/synopackageland/syno-pkg-land-sdk> for the components and their props.
|
|
9
9
|
|
|
10
|
+
## Local tests
|
|
11
|
+
|
|
12
|
+
From the project root, install the test tools before running the optional tests:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install --include=dev
|
|
16
|
+
npm test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
20
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
21
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
22
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
23
|
+
run `synopkgland ctl generate`.
|
|
24
|
+
|
|
10
25
|
```sh
|
|
11
26
|
cd frontend
|
|
12
|
-
npm ci
|
|
27
|
+
npm ci --include=dev
|
|
13
28
|
npm test
|
|
14
29
|
npm run build
|
|
15
30
|
```
|
|
@@ -25,10 +25,25 @@ independent package-account worker and report phase/progress. Authenticate
|
|
|
25
25
|
status/result polling by that ID; a slow NAS or failed status read leaves the
|
|
26
26
|
last queued/running state intact. After a lost admission reply, reuse the same
|
|
27
27
|
idempotency key and input instead of replaying a mutation with a new key.
|
|
28
|
-
Jobs default
|
|
28
|
+
Jobs use the SDK's default operation allowance; approved executor limits remain separate. ctl keeps
|
|
29
29
|
its 62-second transport default; use an explicit `--timeout` or an existing
|
|
30
30
|
nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/main/docs/request-deadlines.md).
|
|
31
31
|
|
|
32
|
+
## Local tests
|
|
33
|
+
|
|
34
|
+
From the project root, install the test tools before running the optional tests:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm install --include=dev
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
42
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
43
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
44
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
45
|
+
run `synopkgland ctl generate`.
|
|
46
|
+
|
|
32
47
|
## Quick start
|
|
33
48
|
|
|
34
49
|
Needs Node.js with `synopkgland` installed, and Python 3.8 or later.
|
|
@@ -7,9 +7,24 @@ delegate components come from `@synopackageland/sdk/vue` (types from
|
|
|
7
7
|
`@synopackageland/sdk`); its version must equal the `backend.sdk` in dsm-package.yaml. See
|
|
8
8
|
<https://github.com/synopackageland/syno-pkg-land-sdk> for the components and their props.
|
|
9
9
|
|
|
10
|
+
## Local tests
|
|
11
|
+
|
|
12
|
+
From the project root, install the test tools before running the optional tests:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install --include=dev
|
|
16
|
+
npm test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
20
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
21
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
22
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
23
|
+
run `synopkgland ctl generate`.
|
|
24
|
+
|
|
10
25
|
```sh
|
|
11
26
|
cd frontend
|
|
12
|
-
npm ci
|
|
27
|
+
npm ci --include=dev
|
|
13
28
|
npm test
|
|
14
29
|
npm run build
|
|
15
30
|
```
|
|
@@ -25,10 +25,25 @@ independent package-account worker and report phase/progress. Authenticate
|
|
|
25
25
|
status/result polling by that ID; a slow NAS or failed status read leaves the
|
|
26
26
|
last queued/running state intact. After a lost admission reply, reuse the same
|
|
27
27
|
idempotency key and input instead of replaying a mutation with a new key.
|
|
28
|
-
Jobs default
|
|
28
|
+
Jobs use the SDK's default operation allowance; approved executor limits remain separate. ctl keeps
|
|
29
29
|
its 62-second transport default; use an explicit `--timeout` or an existing
|
|
30
30
|
nonblocking job. See the SDK's [request deadline contract](https://github.com/synopackageland/syno-pkg-land-sdk/blob/main/docs/request-deadlines.md).
|
|
31
31
|
|
|
32
|
+
## Local tests
|
|
33
|
+
|
|
34
|
+
From the project root, install the test tools before running the optional tests:
|
|
35
|
+
|
|
36
|
+
```sh
|
|
37
|
+
npm install --include=dev
|
|
38
|
+
npm test
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
42
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
43
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
44
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
45
|
+
run `synopkgland ctl generate`.
|
|
46
|
+
|
|
32
47
|
## Quick start
|
|
33
48
|
|
|
34
49
|
Needs Node.js with `synopkgland` installed, Rust through rustup, Zig and `cargo-zigbuild`: `backend/rust-toolchain.toml` pins 1.97.1 with the three DSM targets, so the first `cargo` run installs them.
|
|
@@ -7,9 +7,24 @@ delegate components come from `@synopackageland/sdk/vue` (types from
|
|
|
7
7
|
`@synopackageland/sdk`); its version must equal the `syno-pkg-land-app` tag in backend/Cargo.toml. See
|
|
8
8
|
<https://github.com/synopackageland/syno-pkg-land-sdk> for the components and their props.
|
|
9
9
|
|
|
10
|
+
## Local tests
|
|
11
|
+
|
|
12
|
+
From the project root, install the test tools before running the optional tests:
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npm install --include=dev
|
|
16
|
+
npm test
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`NODE_ENV=production` skips devDependencies; `test:local` needs the CLI.
|
|
20
|
+
After committing the generated root lockfile, use `npm ci --include=dev`.
|
|
21
|
+
Before `npm run test:local`, prepare the backend and, for Vue, build the
|
|
22
|
+
frontend using the steps below and the backend section in `AGENTS.md`, then
|
|
23
|
+
run `synopkgland ctl generate`.
|
|
24
|
+
|
|
10
25
|
```sh
|
|
11
26
|
cd frontend
|
|
12
|
-
npm ci
|
|
27
|
+
npm ci --include=dev
|
|
13
28
|
npm test
|
|
14
29
|
npm run build
|
|
15
30
|
```
|