-
Notifications
You must be signed in to change notification settings - Fork 5
Expand file tree
/
Copy pathdefault-host.ts
More file actions
167 lines (156 loc) · 7.01 KB
/
Copy pathdefault-host.ts
File metadata and controls
167 lines (156 loc) · 7.01 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
/**
* Default host config — built-in fallback used by `objectstack start`
* (and `objectstack serve`) when the project does NOT ship an explicit
* `objectstack.config.ts`.
*
* Goal: an `objectstack build` artifact (`dist/objectstack.json`) is
* fully self-describing — it carries the manifest, objects, views,
* flows and a `requires: [...]` capability list. That should be enough
* to boot a runtime, with zero hand-written host code.
*
* Boot mode: **standalone only**. This module deliberately has zero
* dependency on the cloud distribution — cloud / multi-environment hosts
* ship their own bootstrap and write their own `objectstack.config.ts`.
*
* Resolution order for the artifact path:
* 1. `options.artifactPath` (explicit caller override)
* 2. `OS_ARTIFACT_PATH` env var (file path **or** `http(s)://` URL)
* 3. `<cwd>/dist/objectstack.json`
*
* The returned object is shaped like the result of `defineStack()` —
* `{ plugins, api, requires, objects, manifest }` — so the CLI can use
* it interchangeably with a user-authored stack config.
*/
import { resolve as resolvePath } from 'node:path';
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
import { createStandaloneStack, resolveObjectStackHome, type StandaloneStackConfig, type StandaloneStackResult } from './standalone-stack.js';
import { isHttpUrl } from './load-artifact-bundle.js';
export interface DefaultHostConfigOptions extends StandaloneStackConfig {
/**
* When true (the default), throws if no artifact source can be
* resolved (no explicit `artifactPath`, no `OS_ARTIFACT_PATH` env,
* and `<cwd>/dist/objectstack.json` does not exist).
*
* Set to false to allow booting an empty kernel — useful for tests
* that want to assemble plugins manually after the stack is built.
*/
requireArtifact?: boolean;
}
export type DefaultHostConfigResult = StandaloneStackResult;
/**
* Resolve the artifact source for a default-host boot.
*
* Returns the explicit override, then `OS_ARTIFACT_PATH`, then the
* canonical `<cwd>/dist/objectstack.json` if it exists on disk.
* Returns `undefined` if none of these are available.
*
* URLs (`http(s)://`) are returned as-is — they are validated lazily by
* the loader, since we cannot stat a remote resource cheaply.
*/
export function resolveDefaultArtifactPath(
explicitPath?: string,
cwd: string = process.cwd(),
): string | undefined {
const candidate = explicitPath
?? process.env.OS_ARTIFACT_PATH
?? resolvePath(cwd, 'dist/objectstack.json');
if (isHttpUrl(candidate)) return candidate;
if (explicitPath || process.env.OS_ARTIFACT_PATH) return candidate;
return existsSync(candidate) ? candidate : undefined;
}
/**
* Build a `defineStack()`-shaped config from an `objectstack build`
* artifact, with no `objectstack.config.ts` required.
*
* @example
* // packages/cli/src/commands/serve.ts
* if (!fs.existsSync(absolutePath)) {
* config = await createDefaultHostConfig();
* }
*/
export async function createDefaultHostConfig(
options: DefaultHostConfigOptions = {},
): Promise<DefaultHostConfigResult> {
const { requireArtifact = true, ...standaloneOpts } = options;
let resolvedArtifact = resolveDefaultArtifactPath(standaloneOpts.artifactPath);
if (!resolvedArtifact && requireArtifact) {
throw new Error(
'[createDefaultHostConfig] No artifact source available. ' +
'Set OS_ARTIFACT_PATH (file path or http(s):// URL), ' +
'place the artifact at <cwd>/dist/objectstack.json, ' +
'or pass `{ artifactPath: ... }` explicitly. ' +
'To boot an empty kernel anyway, pass `{ requireArtifact: false }`.',
);
}
// A NAMED artifact that is not there is a broken instruction, and this is
// the boot with no `objectstack.config.ts` — the artifact IS the whole
// deployment, so there is nothing else to serve. Fail loudly.
//
// The distinction that matters is **named vs conventional**, not the errno.
// `<cwd>/dist/objectstack.json` missing means "not compiled yet", which is a
// healthy state a development platform must boot from (#4085) — and
// `resolveDefaultArtifactPath` already returns `undefined` for it, so it
// never reaches here. But `OS_ARTIFACT_PATH` / `{ artifactPath }` are
// returned WITHOUT an existence check (validated lazily by the loader), and
// since #4110 made an absent artifact non-fatal all the way down —
// `loadArtifactBundle` logs and returns null, `MetadataPlugin` starts
// empty — that laziness turned a typo'd path into a silent empty boot:
// `OS_ARTIFACT_PATH=/nope os serve` printed "booting from artifact", then
// "Server is ready", and named the missing path NOWHERE in its output
// (serve's boot-quiet window drops the loader's calm line). Checked here so
// the fix lands where the intent is known, leaving the loader's tolerance
// intact for the config-boot path that needs it.
const namedBy = standaloneOpts.artifactPath
? '`artifactPath`'
: (process.env.OS_ARTIFACT_PATH ? 'OS_ARTIFACT_PATH' : null);
if (
namedBy
&& resolvedArtifact
&& !isHttpUrl(resolvedArtifact)
&& !existsSync(resolvedArtifact)
) {
throw new Error(
`[createDefaultHostConfig] The artifact named by ${namedBy} does not exist: `
+ `"${resolvedArtifact}". Run 'os compile' to build it, correct the path, `
+ `or unset ${namedBy} to boot from <cwd>/dist/objectstack.json.`,
);
}
// Empty-boot path: synthesize a minimal artifact stub inside the
// ObjectStack home directory so MetadataPlugin has a real file to
// read (and to watch for marketplace installs that land later).
if (!resolvedArtifact && !requireArtifact) {
const home = resolveObjectStackHome();
const stubPath = resolvePath(home, 'dist/objectstack.json');
if (!existsSync(stubPath)) {
mkdirSync(resolvePath(stubPath, '..'), { recursive: true });
writeFileSync(
stubPath,
JSON.stringify(
{
manifest: {
id: 'com.objectstack.empty',
name: 'empty',
version: '0.0.0',
type: 'app',
description: 'Empty starter kernel — install apps via the Studio marketplace.',
},
objects: [],
views: [],
apps: [],
flows: [],
requires: [],
},
null,
2,
),
'utf8',
);
}
resolvedArtifact = stubPath;
}
return createStandaloneStack({
...standaloneOpts,
artifactPath: resolvedArtifact,
});
}