@north-light/api 0.3.591 → 0.3.593
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/imports.d.ts +6 -0
- package/dist/commands/pkg/market-inspect.js +2 -2
- package/dist/commands/pkg/market-manage.js +17 -10
- package/dist/commands/pkg/plugin-inspect.js +2 -2
- package/dist/commands/pkg/plugin-manage.js +6 -6
- package/dist/daemon/manage.d.ts +10 -3
- package/dist/daemon/manage.js +17 -7
- package/dist/shared/env.d.ts +4 -0
- package/dist/shared/env.js +6 -0
- package/package.json +1 -1
package/dist/api/contract.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
|
|
2
|
-
export declare const CONTRACT_LEVEL = "1.
|
|
2
|
+
export declare const CONTRACT_LEVEL = "1.45";
|
|
3
3
|
/** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
|
|
4
4
|
export declare const REQUIRED_CONTRACT_HEADER = "Crouter-Required-Contract";
|
|
5
5
|
/** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
|
package/dist/api/contract.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
//
|
|
13
13
|
// PURITY (spec §3.1): Node built-ins + `src/api/*` only.
|
|
14
14
|
/** The contract level this build serves and requires. Written by `scripts/contract.mjs release`; never edit by hand. */
|
|
15
|
-
export const CONTRACT_LEVEL = '1.
|
|
15
|
+
export const CONTRACT_LEVEL = '1.45';
|
|
16
16
|
/** Request header naming the one contract level the caller needs, e.g. `1.3`. Optional: absent means no check. */
|
|
17
17
|
export const REQUIRED_CONTRACT_HEADER = 'Crouter-Required-Contract';
|
|
18
18
|
/** Response header listing the contract levels the server serves, comma-separated, one per major, e.g. `1.3`. */
|
|
@@ -61,6 +61,12 @@ export interface ImportRunDTO {
|
|
|
61
61
|
verify: ImportVerifyDTO | null;
|
|
62
62
|
failure: ImportFailureDTO | null;
|
|
63
63
|
unresolved_plugins: string[];
|
|
64
|
+
/** Plugins whose marketplace source the directory would not publish: the install is imported, its documents become the person's own copies. `holder` names the source that holds a `name_taken` name. */
|
|
65
|
+
refused_plugins?: Array<{
|
|
66
|
+
plugin: string;
|
|
67
|
+
reason: 'name_taken' | 'not_public' | 'not_found';
|
|
68
|
+
holder?: string;
|
|
69
|
+
}>;
|
|
64
70
|
created: string;
|
|
65
71
|
loaded: string | null;
|
|
66
72
|
finished: string | null;
|
|
@@ -3,7 +3,7 @@ import { notFound } from '../../core/errors.js';
|
|
|
3
3
|
import { paginate } from '../../core/pagination.js';
|
|
4
4
|
import { listInstalledPlugins, listInstalledMarketplaces, listAllMarketplaces, findMarketplaceByName, } from '../../core/resolver.js';
|
|
5
5
|
import { resolveScopeArg, projectScopeRoot } from '../../core/scope.js';
|
|
6
|
-
import {
|
|
6
|
+
import { installsLiveInAppBlock } from '../../shared/env.js';
|
|
7
7
|
import { listAppMarketplaces } from './app-installs.js';
|
|
8
8
|
export const marketList = defineLeaf({
|
|
9
9
|
name: 'list',
|
|
@@ -26,7 +26,7 @@ export const marketList = defineLeaf({
|
|
|
26
26
|
effects: ['None. Read-only.'],
|
|
27
27
|
},
|
|
28
28
|
run: async (input) => {
|
|
29
|
-
if (
|
|
29
|
+
if (installsLiveInAppBlock())
|
|
30
30
|
return listAppMarketplaces();
|
|
31
31
|
const scopeInput = input['scope'];
|
|
32
32
|
const limitRaw = input['limit'];
|
|
@@ -4,7 +4,7 @@ import { defineLeaf } from '../../core/command.js';
|
|
|
4
4
|
import { general, notFound } from '../../core/errors.js';
|
|
5
5
|
import { listAllMarketplaces, listAllPlugins, findMarketplaceByName } from '../../core/resolver.js';
|
|
6
6
|
import { resolveScopeArg, requireScopeRoot, marketplacesDir, pluginsDir } from '../../core/scope.js';
|
|
7
|
-
import {
|
|
7
|
+
import { installsLiveInAppBlock } from '../../shared/env.js';
|
|
8
8
|
import { addAppMarketplace, removeAppMarketplace } from './app-installs.js';
|
|
9
9
|
import { updateConfig, updateState, ensureScopeInitialized, readConfig } from '../../core/config.js';
|
|
10
10
|
import { pathExists, ensureDir, removePath, isSymlink, nowIso, readSymlinkTarget } from '../../core/fs-utils.js';
|
|
@@ -41,13 +41,20 @@ export async function addMarketplace(opts) {
|
|
|
41
41
|
renameSync(tempDest, finalDest);
|
|
42
42
|
}
|
|
43
43
|
const effectiveRef = ref !== undefined ? ref : 'main';
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
// A refused registration leaves no clone behind: a directory without its config row would list as a marketplace with no url.
|
|
45
|
+
try {
|
|
46
|
+
await updateConfig(scope, (cfg) => {
|
|
47
|
+
cfg.marketplaces[finalName] = {
|
|
48
|
+
url,
|
|
49
|
+
ref: effectiveRef,
|
|
50
|
+
installed_at: nowIso(),
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
removePath(finalDest);
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
51
58
|
return { name: finalName, scope, path: finalDest };
|
|
52
59
|
}
|
|
53
60
|
export const marketAdd = defineLeaf({
|
|
@@ -74,7 +81,7 @@ export const marketAdd = defineLeaf({
|
|
|
74
81
|
const url = input['url'];
|
|
75
82
|
const ref = input['ref'];
|
|
76
83
|
const scopeInput = input['scope'];
|
|
77
|
-
if (
|
|
84
|
+
if (installsLiveInAppBlock())
|
|
78
85
|
return await addAppMarketplace(url, ref);
|
|
79
86
|
return addMarketplace({ url, ref, scope: resolveInstallScope(scopeInput) });
|
|
80
87
|
},
|
|
@@ -96,7 +103,7 @@ export const marketRemove = defineLeaf({
|
|
|
96
103
|
},
|
|
97
104
|
run: async (input) => {
|
|
98
105
|
const name = input['name'];
|
|
99
|
-
if (
|
|
106
|
+
if (installsLiveInAppBlock())
|
|
100
107
|
return await removeAppMarketplace(name);
|
|
101
108
|
const scopeInput = input['scope'];
|
|
102
109
|
let targetScope;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { defineLeaf } from '../../core/command.js';
|
|
2
2
|
import { notFound } from '../../core/errors.js';
|
|
3
|
-
import {
|
|
3
|
+
import { installsLiveInAppBlock } from '../../shared/env.js';
|
|
4
4
|
import { listAppPlugins } from './app-installs.js';
|
|
5
5
|
import { paginate } from '../../core/pagination.js';
|
|
6
6
|
import { listInstalledPlugins, findPluginByName, } from '../../core/resolver.js';
|
|
@@ -46,7 +46,7 @@ export const pluginList = defineLeaf({
|
|
|
46
46
|
run: async (input) => {
|
|
47
47
|
const scopeInput = input['scope'];
|
|
48
48
|
const includeDisabled = input['includeDisabled'] ?? false;
|
|
49
|
-
if (
|
|
49
|
+
if (installsLiveInAppBlock())
|
|
50
50
|
return await listAppPlugins(includeDisabled);
|
|
51
51
|
const limitRaw = input['limit'];
|
|
52
52
|
const limit = limitRaw !== undefined ? Math.min(Math.max(1, limitRaw), 200) : 50;
|
|
@@ -5,7 +5,7 @@ import { dirname, join, resolve, isAbsolute, relative } from 'node:path';
|
|
|
5
5
|
import { renameSync } from 'node:fs';
|
|
6
6
|
import { defineLeaf } from '../../core/command.js';
|
|
7
7
|
import { cliClient, rethrowAsCliError } from '../api-client.js';
|
|
8
|
-
import {
|
|
8
|
+
import { installsLiveInAppBlock } from '../../shared/env.js';
|
|
9
9
|
import { installAppPlugin, removeAppPlugin, setAppPluginEnabled, updateAppPlugins } from './app-installs.js';
|
|
10
10
|
import { notFound, usage, general, network, CrtrError } from '../../core/errors.js';
|
|
11
11
|
import { findMarketplaceByName, findPluginByName, listAllPlugins } from '../../core/resolver.js';
|
|
@@ -812,7 +812,7 @@ export const pluginInstall = defineLeaf({
|
|
|
812
812
|
const authEnv = input['authEnv'];
|
|
813
813
|
const scopeInput = input['scope'];
|
|
814
814
|
const gitRef = input['ref'];
|
|
815
|
-
if (
|
|
815
|
+
if (installsLiveInAppBlock()) {
|
|
816
816
|
if (rawRef === undefined || endpoint !== undefined || name !== undefined || authEnv !== undefined || gitRef !== undefined || scopeInput !== undefined)
|
|
817
817
|
throw usage('a sandboxed node installs only INSTALL-REF as marketplace/plugin');
|
|
818
818
|
return await installAppPlugin(rawRef);
|
|
@@ -874,7 +874,7 @@ export const pluginRemove = defineLeaf({
|
|
|
874
874
|
if (name === 'crtr') {
|
|
875
875
|
throw usage('cannot remove builtin plugin "crtr" — it ships with the binary');
|
|
876
876
|
}
|
|
877
|
-
if (
|
|
877
|
+
if (installsLiveInAppBlock())
|
|
878
878
|
return await removeAppPlugin(name);
|
|
879
879
|
let scopes;
|
|
880
880
|
if (scopeInput !== undefined) {
|
|
@@ -939,7 +939,7 @@ export const pluginEnable = defineLeaf({
|
|
|
939
939
|
},
|
|
940
940
|
run: async (input) => {
|
|
941
941
|
const name = input['name'];
|
|
942
|
-
if (
|
|
942
|
+
if (installsLiveInAppBlock())
|
|
943
943
|
return await setAppPluginEnabled(name, true);
|
|
944
944
|
const target = resolvePluginConfigTarget(name, input['scope']);
|
|
945
945
|
if (target === null)
|
|
@@ -972,7 +972,7 @@ export const pluginDisable = defineLeaf({
|
|
|
972
972
|
effects: ['Sets plugin enabled=false in config.json.'],
|
|
973
973
|
},
|
|
974
974
|
run: async (input) => {
|
|
975
|
-
if (
|
|
975
|
+
if (installsLiveInAppBlock())
|
|
976
976
|
return await setAppPluginEnabled(input['name'], false);
|
|
977
977
|
const result = await setPluginEnabled(input, false);
|
|
978
978
|
await reconcilePluginDocuments();
|
|
@@ -1002,7 +1002,7 @@ export const pluginUpdate = defineLeaf({
|
|
|
1002
1002
|
},
|
|
1003
1003
|
run: async (input) => {
|
|
1004
1004
|
const name = input['name'];
|
|
1005
|
-
if (
|
|
1005
|
+
if (installsLiveInAppBlock())
|
|
1006
1006
|
return await updateAppPlugins(name);
|
|
1007
1007
|
const scopeInput = input['scope'];
|
|
1008
1008
|
const scope = scopeInput === undefined ? undefined : resolveScopeArg(scopeInput);
|
package/dist/daemon/manage.d.ts
CHANGED
|
@@ -122,6 +122,11 @@ export declare class DaemonExitedBeforeReadyError extends Error {
|
|
|
122
122
|
readonly stderrTail: string;
|
|
123
123
|
constructor(message: string, stderrTail: string);
|
|
124
124
|
}
|
|
125
|
+
/** The daemon's launch command could not be resolved, so no child was ever
|
|
126
|
+
* started — a standing failure that waiting longer cannot fix. */
|
|
127
|
+
export declare class DaemonLaunchError extends Error {
|
|
128
|
+
constructor(cause: unknown);
|
|
129
|
+
}
|
|
125
130
|
/** A live daemon has bound its repair socket but cannot serve normal work
|
|
126
131
|
* until its corpus migration succeeds. */
|
|
127
132
|
export declare class DaemonStartupBlockedError extends Error {
|
|
@@ -200,7 +205,8 @@ export interface EnsureDaemonDeps {
|
|
|
200
205
|
spawnDaemon?: () => Promise<SpawnDaemonResult>;
|
|
201
206
|
now?: () => number;
|
|
202
207
|
}
|
|
203
|
-
/** Why the daemon this process autostarted died before becoming ready, or
|
|
208
|
+
/** Why the daemon this process autostarted died before becoming ready, or why
|
|
209
|
+
* its launch command could not be resolved (no daemon was started), or
|
|
204
210
|
* undefined while it is starting, running, or exited cleanly (a clean exit may
|
|
205
211
|
* be a lost singleton race, so only a non-zero/signal exit counts). A client
|
|
206
212
|
* waiting for that daemon polls this so a startup failure ends the wait now
|
|
@@ -210,6 +216,7 @@ export declare function daemonAutostartStartupFailure(): string | undefined;
|
|
|
210
216
|
* autostart is suppressed for this invocation, while `crtr sys join` holds
|
|
211
217
|
* the join lock (see core/directory/join-lock.ts), while an attempt is in flight,
|
|
212
218
|
* or while backing off from a recent failure.
|
|
213
|
-
*
|
|
214
|
-
*
|
|
219
|
+
* Does not throw: a spawn error is recorded, and a launch or early-exit failure
|
|
220
|
+
* is reported through `daemonAutostartStartupFailure()` so a waiting client
|
|
221
|
+
* fails with it. */
|
|
215
222
|
export declare function ensureDaemon(deps?: EnsureDaemonDeps): void;
|
package/dist/daemon/manage.js
CHANGED
|
@@ -290,6 +290,14 @@ export class DaemonExitedBeforeReadyError extends Error {
|
|
|
290
290
|
this.stderrTail = stderrTail;
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
|
+
/** The daemon's launch command could not be resolved, so no child was ever
|
|
294
|
+
* started — a standing failure that waiting longer cannot fix. */
|
|
295
|
+
export class DaemonLaunchError extends Error {
|
|
296
|
+
constructor(cause) {
|
|
297
|
+
super(cause instanceof Error ? cause.message : String(cause), { cause });
|
|
298
|
+
this.name = 'DaemonLaunchError';
|
|
299
|
+
}
|
|
300
|
+
}
|
|
293
301
|
/** A live daemon has bound its repair socket but cannot serve normal work
|
|
294
302
|
* until its corpus migration succeeds. */
|
|
295
303
|
export class DaemonStartupBlockedError extends Error {
|
|
@@ -544,7 +552,9 @@ export async function spawnDaemon(opts = {}) {
|
|
|
544
552
|
// in macOS Full Disk Access, not "node" (see branded-host). NOTE: when crtrd
|
|
545
553
|
// is launchd-owned, the plist's ProgramArguments must point at the branded
|
|
546
554
|
// binary too — this path only covers a manual `crtr sys daemon start`.
|
|
547
|
-
const launchArgs = await resolveDaemonLaunch()
|
|
555
|
+
const launchArgs = await resolveDaemonLaunch().catch((error) => {
|
|
556
|
+
throw new DaemonLaunchError(error);
|
|
557
|
+
});
|
|
548
558
|
// The one spawn point every start reaches (autostart, `sys daemon start`, a handover successor): an
|
|
549
559
|
// autostart begun before a join took its lock lands here after it, and must not start a daemon.
|
|
550
560
|
if (opts.waitForJoinLock === true)
|
|
@@ -671,7 +681,8 @@ export function resetDaemonAutostart() {
|
|
|
671
681
|
autostartFailures = 0;
|
|
672
682
|
autostartNextAttemptAt = 0;
|
|
673
683
|
}
|
|
674
|
-
/** Why the daemon this process autostarted died before becoming ready, or
|
|
684
|
+
/** Why the daemon this process autostarted died before becoming ready, or why
|
|
685
|
+
* its launch command could not be resolved (no daemon was started), or
|
|
675
686
|
* undefined while it is starting, running, or exited cleanly (a clean exit may
|
|
676
687
|
* be a lost singleton race, so only a non-zero/signal exit counts). A client
|
|
677
688
|
* waiting for that daemon polls this so a startup failure ends the wait now
|
|
@@ -683,8 +694,9 @@ export function daemonAutostartStartupFailure() {
|
|
|
683
694
|
* autostart is suppressed for this invocation, while `crtr sys join` holds
|
|
684
695
|
* the join lock (see core/directory/join-lock.ts), while an attempt is in flight,
|
|
685
696
|
* or while backing off from a recent failure.
|
|
686
|
-
*
|
|
687
|
-
*
|
|
697
|
+
* Does not throw: a spawn error is recorded, and a launch or early-exit failure
|
|
698
|
+
* is reported through `daemonAutostartStartupFailure()` so a waiting client
|
|
699
|
+
* fails with it. */
|
|
688
700
|
export function ensureDaemon(deps = {}) {
|
|
689
701
|
const running = deps.isDaemonRunning ?? isDaemonRunning;
|
|
690
702
|
const spawn = deps.spawnDaemon ?? spawnDaemon;
|
|
@@ -711,11 +723,9 @@ export function ensureDaemon(deps = {}) {
|
|
|
711
723
|
autostartFailures = 0;
|
|
712
724
|
autostartNextAttemptAt = 0;
|
|
713
725
|
}, (error) => {
|
|
714
|
-
if (error instanceof DaemonExitedBeforeReadyError) {
|
|
726
|
+
if (error instanceof DaemonExitedBeforeReadyError || error instanceof DaemonLaunchError) {
|
|
715
727
|
autostartStartupFailure = error.message;
|
|
716
728
|
}
|
|
717
|
-
// Intentionally silent — a missing dist/daemon/crtrd-cli.js (dev mode,
|
|
718
|
-
// pre-build) must not break the calling command.
|
|
719
729
|
autostartFailures += 1;
|
|
720
730
|
autostartNextAttemptAt = now() + autostartBackoffMs(autostartFailures);
|
|
721
731
|
}).finally(() => {
|
package/dist/shared/env.d.ts
CHANGED
|
@@ -16,6 +16,10 @@ export declare function isSandboxedNode(): boolean;
|
|
|
16
16
|
* descriptions (`GET /v1/providers/tools` through that relay); the relay's node token stays the authority. It is not `isSandboxedNode()`,
|
|
17
17
|
* which gates owner-only commands and launch-snapshot reads on a Linux launcher mount. */
|
|
18
18
|
export declare function providerProjectionOverBrokerRelay(): boolean;
|
|
19
|
+
/** True when this CLI's plugin and marketplace installs are its app's install block on the control plane (`app-plugins.replace` over `/v1`), not
|
|
20
|
+
* directories in a scope: a Linux launcher sandbox (`isSandboxedNode()`) or a fleet node reaching `/v1` through its broker relay
|
|
21
|
+
* (`providerProjectionOverBrokerRelay()`, which has no launcher mount). The `pkg plugin` and `pkg market` leaves choose their path with it. */
|
|
22
|
+
export declare function installsLiveInAppBlock(): boolean;
|
|
19
23
|
/** The app that owns the current node, supplied in its broker launch identity. */
|
|
20
24
|
export declare function envGrantee(): string | undefined;
|
|
21
25
|
/** Set to '1' by `nodeEnv` for an app-started run's brokers, which admit inbox
|
package/dist/shared/env.js
CHANGED
|
@@ -71,6 +71,12 @@ export function isSandboxedNode() {
|
|
|
71
71
|
export function providerProjectionOverBrokerRelay() {
|
|
72
72
|
return /^https?:\/\//.test(process.env['CRTR_API_URL'] ?? '') && (process.env['CRTR_API_SOCKET'] ?? '') !== '';
|
|
73
73
|
}
|
|
74
|
+
/** True when this CLI's plugin and marketplace installs are its app's install block on the control plane (`app-plugins.replace` over `/v1`), not
|
|
75
|
+
* directories in a scope: a Linux launcher sandbox (`isSandboxedNode()`) or a fleet node reaching `/v1` through its broker relay
|
|
76
|
+
* (`providerProjectionOverBrokerRelay()`, which has no launcher mount). The `pkg plugin` and `pkg market` leaves choose their path with it. */
|
|
77
|
+
export function installsLiveInAppBlock() {
|
|
78
|
+
return isSandboxedNode() || providerProjectionOverBrokerRelay();
|
|
79
|
+
}
|
|
74
80
|
/** The app that owns the current node, supplied in its broker launch identity. */
|
|
75
81
|
export function envGrantee() {
|
|
76
82
|
return process.env['CRTR_GRANTEE'];
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/api",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.593",
|
|
4
4
|
"description": "Typed crtrd /v1 API contract — DTOs, route builders, the error contract, the CrtrClient, and the command-plugin manifest format. Zero runtime dependencies.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/api/index.js",
|