@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.
@@ -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`, `npm test` and `npm run build` in frontend.
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. Go `api.RequireSecureOrigin(request)` is opt-in and does not
278
- replace authentication, origin or permission checks.
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#https-與舊-dsm-token)
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
- Test both HTTP/HTTPS entries, both user roles and cross-tab session recovery.
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
@@ -0,0 +1,5 @@
1
+ import base from './e2e.config.ts';
2
+ import { e2eAgent } from '@synopackageland/cli/e2e';
3
+
4
+ // Loaded only by e2e:ai; the default config needs no AI dependencies or key.
5
+ export default { ...base, agents: { default: e2eAgent() } };
@@ -2,13 +2,17 @@
2
2
  "private": true,
3
3
  "type": "module",
4
4
  "scripts": {
5
- "e2e:smoke": "node --env-file-if-exists=.env node_modules/@synopackageland/cli/dist/e2e/dsm.js tunnel -- e2e run tests/e2e/smoke.e2e.ts"
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": "^0.41.6",
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 to 30 minutes; approved executor limits remain separate. ctl keeps
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
- if limits["maxInputBytes"].(float64) != 512*1024 || limits["maxQueued"].(float64) != 16 || limits["maxTotalBytes"].(float64) != 16<<20 || limits["jobTimeoutSeconds"].(float64) != 30*60 {
116
- t.Fatalf("limits: %v", limits)
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 TestBackgroundRequestSubmitBusyAndProductionConstruction(t *testing.T) {
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.call(alice, "bg.jobs.submit", e.submitParams("k1"))
73
- if code != "jobs_busy" || time.Since(start) > background.RequestLockTimeout+time.Second {
74
- t.Fatalf("busy submit: %s %+v", code, out)
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 actual production assembly must use the same bounded wait policy.
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(), `"jobs_busy"`) {
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 to 30 minutes; approved executor limits remain separate. ctl keeps
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 to 30 minutes; approved executor limits remain separate. ctl keeps
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 to 30 minutes; approved executor limits remain separate. ctl keeps
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
  ```