Fix StartOS 0.4 TypeScript packaging to match SDK API
This commit is contained in:
+755
@@ -0,0 +1,755 @@
|
||||
"use strict";
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || (function () {
|
||||
var ownKeys = function(o) {
|
||||
ownKeys = Object.getOwnPropertyNames || function (o) {
|
||||
var ar = [];
|
||||
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
||||
return ar;
|
||||
};
|
||||
return ownKeys(o);
|
||||
};
|
||||
return function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
})();
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.StartSdk = exports.OSVersion = void 0;
|
||||
exports.runCommand = runCommand;
|
||||
const fs = __importStar(require("node:fs/promises"));
|
||||
const actions = __importStar(require("../../base/lib/actions"));
|
||||
const inputSpec_1 = require("../../base/lib/actions/input/builder/inputSpec");
|
||||
const list_1 = require("../../base/lib/actions/input/builder/list");
|
||||
const value_1 = require("../../base/lib/actions/input/builder/value");
|
||||
const variants_1 = require("../../base/lib/actions/input/builder/variants");
|
||||
const inputSpecConstants_1 = require("../../base/lib/actions/input/inputSpecConstants");
|
||||
const setupActions_1 = require("../../base/lib/actions/setupActions");
|
||||
const dependencies_1 = require("../../base/lib/dependencies/dependencies");
|
||||
const setupDependencies_1 = require("../../base/lib/dependencies/setupDependencies");
|
||||
const exver_1 = require("../../base/lib/exver");
|
||||
const inits_1 = require("../../base/lib/inits");
|
||||
const Host_1 = require("../../base/lib/interfaces/Host");
|
||||
const ServiceInterfaceBuilder_1 = require("../../base/lib/interfaces/ServiceInterfaceBuilder");
|
||||
const setupExportedUrls_1 = require("../../base/lib/interfaces/setupExportedUrls");
|
||||
const setupInterfaces_1 = require("../../base/lib/interfaces/setupInterfaces");
|
||||
const T = __importStar(require("../../base/lib/types"));
|
||||
const GetContainerIp_1 = require("../../base/lib/util/GetContainerIp");
|
||||
const GetStatus_1 = require("../../base/lib/util/GetStatus");
|
||||
const getServiceInterface_1 = require("../../base/lib/util/getServiceInterface");
|
||||
const getServiceInterfaces_1 = require("../../base/lib/util/getServiceInterfaces");
|
||||
const patterns = __importStar(require("../../base/lib/util/patterns"));
|
||||
const Backups_1 = require("./backup/Backups");
|
||||
const setupBackups_1 = require("./backup/setupBackups");
|
||||
const checkFns_1 = require("./health/checkFns");
|
||||
const checkPortListening_1 = require("./health/checkFns/checkPortListening");
|
||||
const mainFn_1 = require("./mainFn");
|
||||
const Daemons_1 = require("./mainFn/Daemons");
|
||||
const Mounts_1 = require("./mainFn/Mounts");
|
||||
const trigger_1 = require("./trigger");
|
||||
const defaultTrigger_1 = require("./trigger/defaultTrigger");
|
||||
const successFailure_1 = require("./trigger/successFailure");
|
||||
const util_1 = require("./util");
|
||||
const SubContainer_1 = require("./util/SubContainer");
|
||||
const Volume_1 = require("./util/Volume");
|
||||
const version_1 = require("./version");
|
||||
/** The minimum StartOS version required by this SDK release */
|
||||
exports.OSVersion = (0, exver_1.testTypeVersion)('0.4.0-alpha.23');
|
||||
/**
|
||||
* The top-level SDK facade for building StartOS service packages.
|
||||
*
|
||||
* Use `StartSdk.of()` to create an uninitialized instance, then call `.withManifest()`
|
||||
* to bind it to a manifest, and finally `.build()` to obtain the full toolkit of helpers
|
||||
* for actions, daemons, backups, interfaces, health checks, and more.
|
||||
*
|
||||
* @typeParam Manifest - The service manifest type; starts as `never` until `.withManifest()` is called.
|
||||
*/
|
||||
class StartSdk {
|
||||
constructor(manifest) {
|
||||
this.manifest = manifest;
|
||||
}
|
||||
/**
|
||||
* Create an uninitialized StartSdk instance. Call `.withManifest()` next.
|
||||
* @returns A new StartSdk with no manifest bound.
|
||||
*/
|
||||
static of() {
|
||||
return new StartSdk(null);
|
||||
}
|
||||
/**
|
||||
* Bind a manifest to the SDK, producing a typed SDK instance.
|
||||
* @param manifest - The service manifest definition
|
||||
* @returns A new StartSdk instance parameterized by the given manifest type
|
||||
*/
|
||||
withManifest(manifest) {
|
||||
return new StartSdk(manifest);
|
||||
}
|
||||
ifPluginEnabled(plugin, value) {
|
||||
if (this.manifest.plugins?.includes(plugin))
|
||||
return value;
|
||||
return null;
|
||||
}
|
||||
/**
|
||||
* Finalize the SDK and return the full set of helpers for building a StartOS service.
|
||||
*
|
||||
* This method is only callable after `.withManifest()` has been called (enforced at the type level).
|
||||
*
|
||||
* @param isReady - Type-level gate; resolves to `true` only when a manifest is bound.
|
||||
* @returns An object containing all SDK utilities: actions, daemons, backups, interfaces, health checks, volumes, triggers, and more.
|
||||
*/
|
||||
build(isReady) {
|
||||
const startSdkEffectWrapper = {
|
||||
restart: (effects, ...args) => effects.restart(...args),
|
||||
setDependencies: (effects, ...args) => effects.setDependencies(...args),
|
||||
checkDependencies: (effects, ...args) => effects.checkDependencies(...args),
|
||||
mount: (effects, ...args) => effects.mount(...args),
|
||||
getInstalledPackages: (effects, ...args) => effects.getInstalledPackages(...args),
|
||||
getServicePortForward: (effects, ...args) => effects.getServicePortForward(...args),
|
||||
clearBindings: (effects, ...args) => effects.clearBindings(...args),
|
||||
getOsIp: (effects, ...args) => effects.getOsIp(...args),
|
||||
getSslKey: (effects, ...args) => effects.getSslKey(...args),
|
||||
shutdown: (effects, ...args) => effects.shutdown(...args),
|
||||
getDependencies: (effects, ...args) => effects.getDependencies(...args),
|
||||
setHealth: (effects, ...args) => effects.setHealth(...args),
|
||||
};
|
||||
return {
|
||||
/** The bound service manifest */
|
||||
manifest: this.manifest,
|
||||
/** Volume path helpers derived from the manifest volume definitions */
|
||||
volumes: (0, Volume_1.createVolumes)(this.manifest),
|
||||
...startSdkEffectWrapper,
|
||||
/** Persist the current data version to the StartOS effect system */
|
||||
setDataVersion: version_1.setDataVersion,
|
||||
/** Retrieve the current data version from the StartOS effect system */
|
||||
getDataVersion: version_1.getDataVersion,
|
||||
action: {
|
||||
/** Execute an action by its ID, optionally providing input */
|
||||
run: actions.runAction,
|
||||
/** Create a task notification for a specific package's action */
|
||||
createTask: (effects, packageId, action, severity, options) => actions.createTask({
|
||||
effects,
|
||||
packageId,
|
||||
action,
|
||||
severity,
|
||||
options: options,
|
||||
}),
|
||||
/** Create a task notification for this service's own action (uses manifest.id automatically) */
|
||||
createOwnTask: (effects, action, severity, options) => actions.createTask({
|
||||
effects,
|
||||
packageId: this.manifest.id,
|
||||
action,
|
||||
severity,
|
||||
options: options,
|
||||
}),
|
||||
/**
|
||||
* Clear one or more task notifications by their replay IDs
|
||||
* @param effects - The effects context
|
||||
* @param replayIds - One or more replay IDs of the tasks to clear
|
||||
*/
|
||||
clearTask: (effects, ...replayIds) => effects.action.clearTasks({ only: replayIds }),
|
||||
},
|
||||
/**
|
||||
* Check whether the specified (or all) dependencies are satisfied.
|
||||
* @param effects - The effects context
|
||||
* @param packageIds - Optional subset of dependency IDs to check; defaults to all
|
||||
* @returns An object describing which dependencies are satisfied and which are not
|
||||
*/
|
||||
checkDependencies: dependencies_1.checkDependencies,
|
||||
serviceInterface: {
|
||||
/** Retrieve a single service interface belonging to this package by its ID */
|
||||
getOwn: getServiceInterface_1.getOwnServiceInterface,
|
||||
/** Retrieve a single service interface from any package */
|
||||
get: util_1.getServiceInterface,
|
||||
/** Retrieve all service interfaces belonging to this package */
|
||||
getAllOwn: getServiceInterfaces_1.getOwnServiceInterfaces,
|
||||
/** Retrieve all service interfaces, optionally filtering by package */
|
||||
getAll: util_1.getServiceInterfaces,
|
||||
},
|
||||
/**
|
||||
* Get the container IP address with reactive subscription support.
|
||||
*
|
||||
* Returns an object with multiple read strategies: `const()` for a value
|
||||
* that retries on change, `once()` for a single read, `watch()` for an async
|
||||
* generator, `onChange()` for a callback, and `waitFor()` to block until a predicate is met.
|
||||
*
|
||||
* @param effects - The effects context
|
||||
* @param options - Optional filtering options (e.g. `containerId`)
|
||||
*/
|
||||
getContainerIp: (effects, options = {}) => new GetContainerIp_1.GetContainerIp(effects, options),
|
||||
/**
|
||||
* Get the service's current status with reactive subscription support.
|
||||
*
|
||||
* Returns an object with multiple read strategies: `const()` for a value
|
||||
* that retries on change, `once()` for a single read, `watch()` for an async
|
||||
* generator, `onChange()` for a callback, and `waitFor()` to block until a predicate is met.
|
||||
*
|
||||
* @param effects - The effects context
|
||||
* @param options - Optional filtering options (e.g. `packageId`)
|
||||
*/
|
||||
getStatus: (effects, options = {}) => new GetStatus_1.GetStatus(effects, options),
|
||||
MultiHost: {
|
||||
/**
|
||||
* Create a new MultiHost instance for binding ports and exporting interfaces.
|
||||
* @param effects - The effects context
|
||||
* @param id - A unique identifier for this multi-host group
|
||||
*/
|
||||
of: (effects, id) => new Host_1.MultiHost({ id, effects }),
|
||||
},
|
||||
/**
|
||||
* Return `null` if the given string is empty, otherwise return the string unchanged.
|
||||
* Useful for converting empty user input into explicit null values.
|
||||
*/
|
||||
nullIfEmpty: util_1.nullIfEmpty,
|
||||
/**
|
||||
* Indicate that a daemon should use the container image's configured entrypoint.
|
||||
* @param overrideCmd - Optional command arguments to append after the entrypoint
|
||||
*/
|
||||
useEntrypoint: (overrideCmd) => new T.UseEntrypoint(overrideCmd),
|
||||
/**
|
||||
* @description Use this class to create an Action. By convention, each Action should receive its own file.
|
||||
*
|
||||
*/
|
||||
Action: {
|
||||
/**
|
||||
* @description Use this function to create an action that accepts form input
|
||||
* @param id - a unique ID for this action
|
||||
* @param metadata - information describing the action and its availability
|
||||
* @param inputSpec - define the form input using the InputSpec and Value classes
|
||||
* @param prefillFn - optionally fetch data from the file system to pre-fill the input form. Must returns a deep partial of the input spec
|
||||
* @param executionFn - execute the action. Optionally return data for the user to view. Must be in the structure of an ActionResult, version "1"
|
||||
* @example
|
||||
* In this example, we create an action for a user to provide their name.
|
||||
* We prefill the input form with their existing name from the service's yaml file.
|
||||
* The new name is saved to the yaml file, and we return nothing to the user, which
|
||||
* means they will receive a generic success message.
|
||||
*
|
||||
* ```
|
||||
import { sdk } from '../sdk'
|
||||
import { yamlFile } from '../file-models/config.yml'
|
||||
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
export const inputSpec = InputSpec.of({
|
||||
name: Value.text({
|
||||
name: 'Name',
|
||||
description:
|
||||
'When you launch the Hello World UI, it will display "Hello [Name]"',
|
||||
required: true,
|
||||
default: 'World',
|
||||
}),
|
||||
})
|
||||
|
||||
export const setName = sdk.Action.withInput(
|
||||
// id
|
||||
'set-name',
|
||||
|
||||
// metadata
|
||||
async ({ effects }) => ({
|
||||
name: 'Set Name',
|
||||
description: 'Set your name so Hello World can say hello to you',
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
|
||||
// form input specification
|
||||
inputSpec,
|
||||
|
||||
// optionally pre-fill the input form
|
||||
async ({ effects }) => {
|
||||
const name = await yamlFile.read.const(effects)?.name
|
||||
return { name }
|
||||
},
|
||||
|
||||
// the execution function
|
||||
async ({ effects, input }) => yamlFile.merge(input)
|
||||
)
|
||||
* ```
|
||||
*/
|
||||
withInput: setupActions_1.Action.withInput,
|
||||
/**
|
||||
* @description Use this function to create an action that does not accept form input
|
||||
* @param id - a unique ID for this action
|
||||
* @param metadata - information describing the action and its availability
|
||||
* @param executionFn - execute the action. Optionally return data for the user to view. Must be in the structure of an ActionResult, version "1"
|
||||
* @example
|
||||
* In this example, we create an action that returns a secret phrase for the user to see.
|
||||
*
|
||||
* ```
|
||||
import { store } from '../file-models/store.json'
|
||||
import { sdk } from '../sdk'
|
||||
|
||||
export const showSecretPhrase = sdk.Action.withoutInput(
|
||||
// id
|
||||
'show-secret-phrase',
|
||||
|
||||
// metadata
|
||||
async ({ effects }) => ({
|
||||
name: 'Show Secret Phrase',
|
||||
description: 'Reveal the secret phrase for Hello World',
|
||||
warning: null,
|
||||
allowedStatuses: 'any',
|
||||
group: null,
|
||||
visibility: 'enabled',
|
||||
}),
|
||||
|
||||
// the execution function
|
||||
async ({ effects }) => ({
|
||||
version: '1',
|
||||
title: 'Secret Phrase',
|
||||
message:
|
||||
'Below is your secret phrase. Use it to gain access to extraordinary places',
|
||||
result: {
|
||||
type: 'single',
|
||||
value: (await store.read.once())?.secretPhrase,
|
||||
copyable: true,
|
||||
qr: true,
|
||||
masked: true,
|
||||
},
|
||||
}),
|
||||
)
|
||||
* ```
|
||||
*/
|
||||
withoutInput: (id, metadata, run) => setupActions_1.Action.withoutInput(id, metadata, run),
|
||||
},
|
||||
inputSpecConstants: {
|
||||
smtpInputSpec: inputSpecConstants_1.smtpInputSpec,
|
||||
systemSmtpSpec: inputSpecConstants_1.systemSmtpSpec,
|
||||
customSmtp: inputSpecConstants_1.customSmtp,
|
||||
smtpProviderVariants: inputSpecConstants_1.smtpProviderVariants,
|
||||
},
|
||||
/**
|
||||
* @description Use this function to create a service interface.
|
||||
* @param effects
|
||||
* @param options
|
||||
* @example
|
||||
* In this example, we create a standard web UI
|
||||
*
|
||||
* ```
|
||||
const ui = sdk.createInterface(effects, {
|
||||
name: 'Web UI',
|
||||
id: 'ui',
|
||||
description: 'The primary web app for this service.',
|
||||
type: 'ui',
|
||||
masked: false,
|
||||
schemeOverride: null,
|
||||
username: null,
|
||||
path: '',
|
||||
query: {},
|
||||
})
|
||||
* ```
|
||||
*/
|
||||
createInterface: (effects, options) => new ServiceInterfaceBuilder_1.ServiceInterfaceBuilder({ ...options, effects }),
|
||||
/**
|
||||
* Get the system SMTP configuration with reactive subscription support.
|
||||
* @param effects - The effects context
|
||||
*/
|
||||
getSystemSmtp: (effects) => new util_1.GetSystemSmtp(effects),
|
||||
/**
|
||||
* Get the outbound network gateway address with reactive subscription support.
|
||||
* @param effects - The effects context
|
||||
*/
|
||||
getOutboundGateway: (effects) => new util_1.GetOutboundGateway(effects),
|
||||
/**
|
||||
* Get an SSL certificate for the given hostnames with reactive subscription support.
|
||||
* @param effects - The effects context
|
||||
* @param hostnames - The hostnames to obtain a certificate for
|
||||
* @param algorithm - Optional algorithm preference (e.g. Ed25519)
|
||||
*/
|
||||
getSslCertificate: (effects, hostnames, algorithm) => new util_1.GetSslCertificate(effects, { hostnames, algorithm }),
|
||||
/** Retrieve the manifest of any installed service package by its ID */
|
||||
getServiceManifest: util_1.getServiceManifest,
|
||||
healthCheck: {
|
||||
checkPortListening: checkPortListening_1.checkPortListening,
|
||||
checkWebUrl: checkFns_1.checkWebUrl,
|
||||
runHealthScript: checkFns_1.runHealthScript,
|
||||
},
|
||||
/** Common utility patterns (e.g. hostname regex, port validators) */
|
||||
patterns,
|
||||
/**
|
||||
* @description Use this function to list every Action offered by the service. Actions will be displayed in the provided order.
|
||||
*
|
||||
* By convention, each Action should receive its own file in the "actions" directory.
|
||||
* @example
|
||||
*
|
||||
* ```
|
||||
import { sdk } from '../sdk'
|
||||
import { config } from './config'
|
||||
import { nameToLogs } from './nameToLogs'
|
||||
|
||||
export const actions = sdk.Actions.of().addAction(config).addAction(nameToLogs)
|
||||
* ```
|
||||
*/
|
||||
Actions: (setupActions_1.Actions),
|
||||
/**
|
||||
* @description Use this function to determine which volumes are backed up when a user creates a backup, including advanced options.
|
||||
* @example
|
||||
* In this example, we back up the entire "main" volume and nothing else.
|
||||
*
|
||||
* ```
|
||||
import { sdk } from './sdk'
|
||||
|
||||
export const { createBackup, restoreBackup } = sdk.setupBackups(
|
||||
async ({ effects }) => sdk.Backups.volumes('main'),
|
||||
)
|
||||
* ```
|
||||
* @example
|
||||
* In this example, we back up the "main" volume, but exclude hypothetical directory "excludedDir".
|
||||
*
|
||||
* ```
|
||||
import { sdk } from './sdk'
|
||||
|
||||
export const { createBackup, restoreBackup } = sdk.setupBackups(async () =>
|
||||
sdk.Backups.volumes('main').setOptions({
|
||||
exclude: ['excludedDir'],
|
||||
}),
|
||||
)
|
||||
* ```
|
||||
*/
|
||||
setupBackups: (options) => (0, setupBackups_1.setupBackups)(options),
|
||||
/**
|
||||
* @description Use this function to set dependency information.
|
||||
* @example
|
||||
* In this example, we create a dependency on Hello World >=1.0.0:0, where Hello World must be running and passing its "primary" health check.
|
||||
*
|
||||
* ```
|
||||
export const setDependencies = sdk.setupDependencies(
|
||||
async ({ effects }) => {
|
||||
return {
|
||||
'hello-world': {
|
||||
kind: 'running',
|
||||
versionRange: '>=1.0.0',
|
||||
healthChecks: ['primary'],
|
||||
},
|
||||
}
|
||||
},
|
||||
)
|
||||
* ```
|
||||
*/
|
||||
setupDependencies: (setupDependencies_1.setupDependencies),
|
||||
/**
|
||||
* @description Use this function to create an InitScript that runs every time the service initializes (install, update, restore, rebuild, and server bootup)
|
||||
*/
|
||||
setupOnInit: inits_1.setupOnInit,
|
||||
/**
|
||||
* @description Use this function to create an UninitScript that runs every time the service uninitializes (update, uninstall, and server shutdown)
|
||||
*/
|
||||
setupOnUninit: inits_1.setupOnUninit,
|
||||
/**
|
||||
* @description Use this function to setup what happens when the service initializes.
|
||||
*
|
||||
* This happens when the server boots, or a service is installed, updated, or restored
|
||||
*
|
||||
* Not every init script does something on every initialization. For example, versions only does something on install or update
|
||||
*
|
||||
* These scripts are run in the order they are supplied
|
||||
* @example
|
||||
*
|
||||
* ```
|
||||
export const init = sdk.setupInit(
|
||||
restoreInit,
|
||||
versions,
|
||||
setDependencies,
|
||||
setInterfaces,
|
||||
actions,
|
||||
postInstall,
|
||||
)
|
||||
* ```
|
||||
*/
|
||||
setupInit: inits_1.setupInit,
|
||||
/**
|
||||
* @description Use this function to setup what happens when the service uninitializes.
|
||||
*
|
||||
* This happens when the server shuts down, or a service is uninstalled or updated
|
||||
*
|
||||
* Not every uninit script does something on every uninitialization. For example, versions only does something on uninstall or update
|
||||
*
|
||||
* These scripts are run in the order they are supplied
|
||||
* @example
|
||||
*
|
||||
* ```
|
||||
export const uninit = sdk.setupUninit(
|
||||
versions,
|
||||
)
|
||||
* ```
|
||||
*/
|
||||
setupUninit: inits_1.setupUninit,
|
||||
/**
|
||||
* @description Use this function to determine how this service will be hosted and served. The function executes on service install, service update, and inputSpec save.
|
||||
* @param inputSpec - The inputSpec spec of this service as exported from /inputSpec/spec.
|
||||
* @param fn - an async function that returns an array of interface receipts. The function always has access to `effects`; it has access to `input` only after inputSpec save, otherwise `input` will be null.
|
||||
* @example
|
||||
* In this example, we create two UIs from one multi-host, and one API from another multi-host.
|
||||
*
|
||||
* ```
|
||||
export const setInterfaces = sdk.setupInterfaces(
|
||||
async ({ effects }) => {
|
||||
// ** UI multi-host **
|
||||
const uiMulti = sdk.MultiHost.of(effects, 'ui-multi')
|
||||
const uiMultiOrigin = await uiMulti.bindPort(80, {
|
||||
protocol: 'http',
|
||||
})
|
||||
// Primary UI
|
||||
const primaryUi = sdk.createInterface(effects, {
|
||||
name: 'Primary UI',
|
||||
id: 'primary-ui',
|
||||
description: 'The primary web app for this service.',
|
||||
type: 'ui',
|
||||
masked: false,
|
||||
schemeOverride: null,
|
||||
username: null,
|
||||
path: '',
|
||||
query: {},
|
||||
})
|
||||
// Admin UI
|
||||
const adminUi = sdk.createInterface(effects, {
|
||||
name: 'Admin UI',
|
||||
id: 'admin-ui',
|
||||
description: 'The admin web app for this service.',
|
||||
type: 'ui',
|
||||
masked: false,
|
||||
schemeOverride: null,
|
||||
username: null,
|
||||
path: '/admin',
|
||||
query: {},
|
||||
})
|
||||
// UI receipt
|
||||
const uiReceipt = await uiMultiOrigin.export([primaryUi, adminUi])
|
||||
|
||||
// ** API multi-host **
|
||||
const apiMulti = sdk.MultiHost.of(effects, 'api-multi')
|
||||
const apiMultiOrigin = await apiMulti.bindPort(5959, {
|
||||
protocol: 'http',
|
||||
})
|
||||
// API
|
||||
const api = sdk.createInterface(effects, {
|
||||
name: 'Admin API',
|
||||
id: 'api',
|
||||
description: 'The advanced API for this service.',
|
||||
type: 'api',
|
||||
masked: false,
|
||||
schemeOverride: null,
|
||||
username: null,
|
||||
path: '',
|
||||
query: {},
|
||||
})
|
||||
// API receipt
|
||||
const apiReceipt = await apiMultiOrigin.export([api])
|
||||
|
||||
// ** Return receipts **
|
||||
return [uiReceipt, apiReceipt]
|
||||
},
|
||||
)
|
||||
* ```
|
||||
*/
|
||||
setupInterfaces: setupInterfaces_1.setupServiceInterfaces,
|
||||
/**
|
||||
* Define the main entrypoint for the service. The provided function should
|
||||
* configure and return a `Daemons` instance describing all long-running processes.
|
||||
* @param fn - Async function that receives `effects` and returns a `Daemons` instance
|
||||
*/
|
||||
setupMain: (fn) => (0, mainFn_1.setupMain)(fn),
|
||||
/** Built-in trigger strategies for controlling health-check polling intervals */
|
||||
trigger: {
|
||||
/** Default trigger: polls at a fixed interval */
|
||||
defaultTrigger: defaultTrigger_1.defaultTrigger,
|
||||
/** Trigger with a cooldown period between checks */
|
||||
cooldownTrigger: trigger_1.cooldownTrigger,
|
||||
/** Switches to a different interval after the first successful check */
|
||||
changeOnFirstSuccess: trigger_1.changeOnFirstSuccess,
|
||||
/** Uses different intervals based on success vs failure results */
|
||||
successFailure: successFailure_1.successFailure,
|
||||
},
|
||||
Mounts: {
|
||||
/**
|
||||
* Create an empty Mounts builder for declaring volume, asset, dependency, and backup mounts.
|
||||
* @returns A new Mounts instance with no mounts configured
|
||||
*/
|
||||
of: (Mounts_1.Mounts.of),
|
||||
},
|
||||
Backups: {
|
||||
/**
|
||||
* Create a Backups configuration that backs up entire volumes by name.
|
||||
* @param volumeNames - Volume IDs from the manifest to include in backups
|
||||
*/
|
||||
ofVolumes: (Backups_1.Backups.ofVolumes),
|
||||
/**
|
||||
* Create a Backups configuration from explicit sync path pairs.
|
||||
* @param syncs - Array of `{ dataPath, backupPath }` objects
|
||||
*/
|
||||
ofSyncs: (Backups_1.Backups.ofSyncs),
|
||||
/**
|
||||
* Create a Backups configuration with custom rsync options (e.g. exclude patterns).
|
||||
* @param options - Partial sync options to override defaults
|
||||
*/
|
||||
withOptions: (Backups_1.Backups.withOptions),
|
||||
/**
|
||||
* Create a Backups configuration that uses pg_dump/pg_restore instead of
|
||||
* rsyncing the raw PostgreSQL data directory. Chain `.addVolume()` to include
|
||||
* additional volumes in the backup.
|
||||
*/
|
||||
withPgDump: (Backups_1.Backups.withPgDump),
|
||||
/**
|
||||
* Create a Backups configuration that uses mysqldump/mysql instead of
|
||||
* rsyncing the raw MySQL/MariaDB data directory. Chain `.addVolume()` to
|
||||
* include additional volumes in the backup.
|
||||
*/
|
||||
withMysqlDump: (Backups_1.Backups.withMysqlDump),
|
||||
},
|
||||
InputSpec: {
|
||||
/**
|
||||
* @description Use this function to define the inputSpec specification that will ultimately present to the user as validated form inputs.
|
||||
*
|
||||
* Most form controls are supported, including text, textarea, number, toggle, select, multiselect, list, color, datetime, object (sub form), and union (conditional sub form).
|
||||
* @example
|
||||
* In this example, we define a inputSpec form with two value: name and makePublic.
|
||||
*
|
||||
* ```
|
||||
import { sdk } from '../sdk'
|
||||
const { InputSpec, Value } = sdk
|
||||
|
||||
export const inputSpecSpec = InputSpec.of({
|
||||
name: Value.text({
|
||||
name: 'Name',
|
||||
description:
|
||||
'When you launch the Hello World UI, it will display "Hello [Name]"',
|
||||
required: true,
|
||||
default: 'World'
|
||||
}),
|
||||
makePublic: Value.toggle({
|
||||
name: 'Make Public',
|
||||
description: 'Whether or not to expose the service to the network',
|
||||
default: false,
|
||||
}),
|
||||
})
|
||||
* ```
|
||||
*/
|
||||
of: (spec) => inputSpec_1.InputSpec.of(spec),
|
||||
},
|
||||
Daemon: {
|
||||
/**
|
||||
* Create a single Daemon that wraps a long-running process with automatic restart logic.
|
||||
* Returns a curried function: call with `(effects, subcontainer, exec)`.
|
||||
*/
|
||||
get of() {
|
||||
return Daemons_1.Daemon.of();
|
||||
},
|
||||
},
|
||||
Daemons: {
|
||||
/**
|
||||
* Create a new Daemons builder for defining the service's daemon topology.
|
||||
* Chain `.addDaemon()` calls to register each long-running process.
|
||||
* @param effects - The effects context
|
||||
*/
|
||||
of(effects) {
|
||||
return Daemons_1.Daemons.of({ effects });
|
||||
},
|
||||
},
|
||||
SubContainer: {
|
||||
/**
|
||||
* @description Create a new SubContainer
|
||||
* @param effects
|
||||
* @param image - what container image to use
|
||||
* @param mounts - what to mount to the subcontainer
|
||||
* @param name - a name to use to refer to the subcontainer for debugging purposes
|
||||
*/
|
||||
of(effects, image, mounts, name) {
|
||||
return SubContainer_1.SubContainerOwned.of(effects, image, mounts, name).then((subc) => subc.rc());
|
||||
},
|
||||
/**
|
||||
* @description Run a function with a temporary SubContainer
|
||||
* @param effects
|
||||
* @param image - what container image to use
|
||||
* @param mounts - what to mount to the subcontainer
|
||||
* @param name - a name to use to refer to the ephemeral subcontainer for debugging purposes
|
||||
*/
|
||||
withTemp(effects, image, mounts, name, fn) {
|
||||
return SubContainer_1.SubContainerOwned.withTemp(effects, image, mounts, name, fn);
|
||||
},
|
||||
},
|
||||
List: list_1.List,
|
||||
Value: value_1.Value,
|
||||
Variants: variants_1.Variants,
|
||||
plugin: {
|
||||
url: this.ifPluginEnabled('url-v0', {
|
||||
register: (effects, options) => effects.plugin.url.register({
|
||||
tableAction: options.tableAction.id,
|
||||
}),
|
||||
exportUrl: (effects, options) => effects.plugin.url.exportUrl({
|
||||
hostnameInfo: options.hostnameInfo,
|
||||
removeAction: options.removeAction?.id ?? null,
|
||||
overflowActions: options.overflowActions.map((a) => a.id),
|
||||
}),
|
||||
setupExportedUrls: setupExportedUrls_1.setupExportedUrls, // similar to setupInterfaces
|
||||
}),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
exports.StartSdk = StartSdk;
|
||||
/**
|
||||
* Run a one-shot command inside a temporary subcontainer.
|
||||
*
|
||||
* Creates a subcontainer, executes the command, and destroys the subcontainer when finished.
|
||||
* Throws an {@link ExitError} if the command exits with a non-zero code or signal.
|
||||
*
|
||||
* @param effects - The effects context
|
||||
* @param image - The container image to use
|
||||
* @param command - The command to execute (string array or UseEntrypoint)
|
||||
* @param options - Mount and command options
|
||||
* @param name - Optional human-readable name for debugging
|
||||
* @returns The stdout and stderr output of the command
|
||||
*/
|
||||
async function runCommand(effects, image, command, options, name) {
|
||||
let commands;
|
||||
if (T.isUseEntrypoint(command)) {
|
||||
const imageMeta = await fs
|
||||
.readFile(`/media/startos/images/${image.imageId}.json`, {
|
||||
encoding: 'utf8',
|
||||
})
|
||||
.catch(() => '{}')
|
||||
.then(JSON.parse);
|
||||
commands = imageMeta.entrypoint ?? [];
|
||||
commands = commands.concat(...(command.overridCmd ?? imageMeta.cmd ?? []));
|
||||
}
|
||||
else
|
||||
commands = (0, util_1.splitCommand)(command);
|
||||
return SubContainer_1.SubContainerOwned.withTemp(effects, image, options.mounts, name ||
|
||||
commands
|
||||
.map((c) => {
|
||||
if (c.includes(' ')) {
|
||||
return `"${c.replace(/"/g, `\"`)}"`;
|
||||
}
|
||||
else {
|
||||
return c;
|
||||
}
|
||||
})
|
||||
.join(' '), async (subcontainer) => {
|
||||
const res = await subcontainer.exec(commands);
|
||||
if (res.exitCode || res.exitSignal) {
|
||||
throw new SubContainer_1.ExitError(commands[0], res);
|
||||
}
|
||||
else {
|
||||
return res;
|
||||
}
|
||||
});
|
||||
}
|
||||
//# sourceMappingURL=StartSdk.js.map
|
||||
Reference in New Issue
Block a user