@devrouter/cli 0.0.36 → 0.0.38
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/devrouter.js +255 -16
- package/package.json +1 -1
- package/upgrade-prompts/0.0.37.md +30 -0
- package/upgrade-prompts/0.0.38.md +45 -0
package/dist/devrouter.js
CHANGED
|
@@ -221,6 +221,31 @@ healthcheck:
|
|
|
221
221
|
retries: 20
|
|
222
222
|
\`\`\`
|
|
223
223
|
|
|
224
|
+
## Profiles
|
|
225
|
+
|
|
226
|
+
Optional named subsets of routed apps in \`.devrouter.yml\` so \`ensure\` can start only what a task needs:
|
|
227
|
+
|
|
228
|
+
\`\`\`yaml
|
|
229
|
+
profiles:
|
|
230
|
+
manage:
|
|
231
|
+
apps: [manage, api, auth]
|
|
232
|
+
readiness: [manage, api]
|
|
233
|
+
pwa:
|
|
234
|
+
apps: [pwa, api, auth]
|
|
235
|
+
readiness: [pwa, api]
|
|
236
|
+
full:
|
|
237
|
+
apps: ['*']
|
|
238
|
+
default: true
|
|
239
|
+
\`\`\`
|
|
240
|
+
|
|
241
|
+
- \`apps\` (required): routed app names (\`kind=app\`) or \`['*']\` for everything.
|
|
242
|
+
- \`dependencies\` (optional): \`kind=dependency\` services this profile needs; omitted = every dependency a kept app requires transitively.
|
|
243
|
+
- \`readiness\` (optional): subset of the profile's apps that \`ensure\` HTTP-probes; omitted = all profile apps with an http route.
|
|
244
|
+
- \`default\` (optional): at most one; used when \`--profile\` is omitted. No \`profiles\` key at all = implicit full behavior.
|
|
245
|
+
- Validation is strict at config load: unknown keys, non-routed \`apps\`, non-dependency \`dependencies\`, \`readiness\` outside the profile's apps, and multiple defaults are rejected.
|
|
246
|
+
- Selection: \`devrouter ensure <path> --profile <name>\`. Comma-separated selections (\`--profile manage,pwa\`) merge with deduplication; the canonical name is sorted-unique so order never affects identity or fingerprints. A wildcard member collapses to everything.
|
|
247
|
+
- Managed adapters receive \`DEVROUTER_PROFILE\` (canonical resolved name) in the post-start env; profile switches replace the owned process group via the fingerprint.
|
|
248
|
+
|
|
224
249
|
## Env var injection
|
|
225
250
|
|
|
226
251
|
When a host app depends on a TCP Docker service, \`devrouter app run\` and \`devrouter app exec\` inject per-dep deterministic vars (where \`{PREFIX} = dep.name.toUpperCase().replace(/-/g, "_")\`):
|
|
@@ -1926,7 +1951,11 @@ function parseApp(value, index) {
|
|
|
1926
1951
|
}
|
|
1927
1952
|
function parseConfig(raw, configPath) {
|
|
1928
1953
|
const root = ensureObject(raw, configPath);
|
|
1929
|
-
ensureAllowedKeys(
|
|
1954
|
+
ensureAllowedKeys(
|
|
1955
|
+
root,
|
|
1956
|
+
["version", "devrouter", "project", "secretManager", "profiles", "apps"],
|
|
1957
|
+
configPath
|
|
1958
|
+
);
|
|
1930
1959
|
const version = toIntegerOrThrow(root.version, `${configPath}.version`);
|
|
1931
1960
|
if (version !== 1) {
|
|
1932
1961
|
throw new Error(`${configPath}.version must be 1.`);
|
|
@@ -1994,14 +2023,202 @@ function parseConfig(raw, configPath) {
|
|
|
1994
2023
|
}
|
|
1995
2024
|
seenNames.add(app.name);
|
|
1996
2025
|
}
|
|
2026
|
+
const profiles = parseProfiles(root.profiles, configPath, apps);
|
|
1997
2027
|
return {
|
|
1998
2028
|
version: 1,
|
|
1999
2029
|
...devrouter ? { devrouter } : {},
|
|
2000
2030
|
project: root.project && typeof root.project === "object" ? { name: root.project.name } : void 0,
|
|
2001
2031
|
...secretManager ? { secretManager } : {},
|
|
2032
|
+
...profiles ? { profiles } : {},
|
|
2002
2033
|
apps
|
|
2003
2034
|
};
|
|
2004
2035
|
}
|
|
2036
|
+
function parseProfiles(value, configPath, apps) {
|
|
2037
|
+
if (value === void 0) {
|
|
2038
|
+
return void 0;
|
|
2039
|
+
}
|
|
2040
|
+
const raw = ensureObject(value, `${configPath}.profiles`);
|
|
2041
|
+
if (Object.keys(raw).length === 0) {
|
|
2042
|
+
throw new Error(`${configPath}.profiles must define at least one profile.`);
|
|
2043
|
+
}
|
|
2044
|
+
if (Object.keys(raw).length > MAX_PROFILES) {
|
|
2045
|
+
throw new Error(`${configPath}.profiles exceeds the maximum of ${MAX_PROFILES} profiles.`);
|
|
2046
|
+
}
|
|
2047
|
+
const routedNames = new Set(
|
|
2048
|
+
apps.filter((app) => app.kind !== "dependency").map((app) => app.name)
|
|
2049
|
+
);
|
|
2050
|
+
const result = {};
|
|
2051
|
+
let defaultCount = 0;
|
|
2052
|
+
for (const [name, profileValue] of Object.entries(raw)) {
|
|
2053
|
+
if (!PROFILE_NAME_RE.test(name)) {
|
|
2054
|
+
throw new Error(
|
|
2055
|
+
`${configPath}.profiles.${name} is not a valid profile name (lowercase alphanumerics and hyphens).`
|
|
2056
|
+
);
|
|
2057
|
+
}
|
|
2058
|
+
const profile = ensureObject(profileValue, `${configPath}.profiles.${name}`);
|
|
2059
|
+
ensureAllowedKeys(
|
|
2060
|
+
profile,
|
|
2061
|
+
["apps", "dependencies", "readiness", "default"],
|
|
2062
|
+
`${configPath}.profiles.${name}`
|
|
2063
|
+
);
|
|
2064
|
+
const profileApps = toStringArray(profile.apps, `${configPath}.profiles.${name}.apps`);
|
|
2065
|
+
if (profileApps.length === 0) {
|
|
2066
|
+
throw new Error(`${configPath}.profiles.${name}.apps must not be empty.`);
|
|
2067
|
+
}
|
|
2068
|
+
const isWildcard = profileApps.length === 1 && profileApps[0] === "*";
|
|
2069
|
+
if (!isWildcard) {
|
|
2070
|
+
for (const appName of profileApps) {
|
|
2071
|
+
if (!routedNames.has(appName)) {
|
|
2072
|
+
throw new Error(
|
|
2073
|
+
`${configPath}.profiles.${name}.apps references '${appName}', which is not a routed app (kind=app).`
|
|
2074
|
+
);
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
let dependencies;
|
|
2079
|
+
if (profile.dependencies !== void 0) {
|
|
2080
|
+
dependencies = toStringArray(
|
|
2081
|
+
profile.dependencies,
|
|
2082
|
+
`${configPath}.profiles.${name}.dependencies`
|
|
2083
|
+
);
|
|
2084
|
+
for (const depName of dependencies) {
|
|
2085
|
+
const dependency = apps.find((candidate) => candidate.name === depName);
|
|
2086
|
+
if (!dependency) {
|
|
2087
|
+
throw new Error(
|
|
2088
|
+
`${configPath}.profiles.${name}.dependencies references '${depName}', which does not exist in apps.`
|
|
2089
|
+
);
|
|
2090
|
+
}
|
|
2091
|
+
if (dependency.kind !== "dependency") {
|
|
2092
|
+
throw new Error(
|
|
2093
|
+
`${configPath}.profiles.${name}.dependencies references '${depName}', which is not kind=dependency.`
|
|
2094
|
+
);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
}
|
|
2098
|
+
let readiness;
|
|
2099
|
+
if (profile.readiness !== void 0) {
|
|
2100
|
+
readiness = toStringArray(profile.readiness, `${configPath}.profiles.${name}.readiness`);
|
|
2101
|
+
const appSet = new Set(isWildcard ? Array.from(routedNames) : profileApps);
|
|
2102
|
+
for (const readyName of readiness) {
|
|
2103
|
+
if (!appSet.has(readyName)) {
|
|
2104
|
+
throw new Error(
|
|
2105
|
+
`${configPath}.profiles.${name}.readiness references '${readyName}', which is not in the profile's apps.`
|
|
2106
|
+
);
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
}
|
|
2110
|
+
let isDefault = false;
|
|
2111
|
+
if (profile.default !== void 0) {
|
|
2112
|
+
if (typeof profile.default !== "boolean") {
|
|
2113
|
+
throw new Error(`${configPath}.profiles.${name}.default must be a boolean.`);
|
|
2114
|
+
}
|
|
2115
|
+
isDefault = profile.default;
|
|
2116
|
+
}
|
|
2117
|
+
if (isDefault) {
|
|
2118
|
+
defaultCount += 1;
|
|
2119
|
+
}
|
|
2120
|
+
result[name] = {
|
|
2121
|
+
apps: profileApps,
|
|
2122
|
+
...dependencies ? { dependencies } : {},
|
|
2123
|
+
...readiness ? { readiness } : {},
|
|
2124
|
+
...isDefault ? { default: true } : {}
|
|
2125
|
+
};
|
|
2126
|
+
}
|
|
2127
|
+
if (defaultCount > 1) {
|
|
2128
|
+
throw new Error(`${configPath}.profiles must have at most one default profile.`);
|
|
2129
|
+
}
|
|
2130
|
+
return result;
|
|
2131
|
+
}
|
|
2132
|
+
function resolveProfile(config, profileOverride) {
|
|
2133
|
+
const profiles = config.profiles;
|
|
2134
|
+
const mergeSelection = (selection) => {
|
|
2135
|
+
const names = Array.from(
|
|
2136
|
+
new Set(
|
|
2137
|
+
selection.split(",").map((name) => name.trim()).filter(Boolean)
|
|
2138
|
+
)
|
|
2139
|
+
);
|
|
2140
|
+
if (names.length === 0) {
|
|
2141
|
+
throw new Error("Profile selection is empty.");
|
|
2142
|
+
}
|
|
2143
|
+
const missing = names.filter((name) => !profiles?.[name]);
|
|
2144
|
+
if (missing.length > 0) {
|
|
2145
|
+
const available = profiles ? Object.keys(profiles).join(", ") : "(none defined)";
|
|
2146
|
+
throw new Error(
|
|
2147
|
+
`Profile '${missing[0]}' is not defined in .devrouter.yml. Available: ${available}`
|
|
2148
|
+
);
|
|
2149
|
+
}
|
|
2150
|
+
if (names.length === 1) {
|
|
2151
|
+
return { name: names[0], profile: profiles?.[names[0]] };
|
|
2152
|
+
}
|
|
2153
|
+
const canonicalName = [...names].sort().join(",");
|
|
2154
|
+
const apps = /* @__PURE__ */ new Set();
|
|
2155
|
+
const dependencies = /* @__PURE__ */ new Set();
|
|
2156
|
+
const readiness = /* @__PURE__ */ new Set();
|
|
2157
|
+
let hasWildcard = false;
|
|
2158
|
+
for (const name of names) {
|
|
2159
|
+
const profile = profiles?.[name];
|
|
2160
|
+
if (!profile) continue;
|
|
2161
|
+
if (profile.apps.length === 1 && profile.apps[0] === "*") {
|
|
2162
|
+
hasWildcard = true;
|
|
2163
|
+
continue;
|
|
2164
|
+
}
|
|
2165
|
+
for (const appName of profile.apps) apps.add(appName);
|
|
2166
|
+
for (const depName of profile.dependencies ?? []) dependencies.add(depName);
|
|
2167
|
+
for (const readyName of profile.readiness ?? []) readiness.add(readyName);
|
|
2168
|
+
}
|
|
2169
|
+
if (hasWildcard) {
|
|
2170
|
+
return { name: canonicalName, profile: { apps: ["*"] } };
|
|
2171
|
+
}
|
|
2172
|
+
const merged = {
|
|
2173
|
+
apps: Array.from(apps),
|
|
2174
|
+
...dependencies.size > 0 ? { dependencies: Array.from(dependencies) } : {},
|
|
2175
|
+
...readiness.size > 0 ? { readiness: Array.from(readiness) } : {}
|
|
2176
|
+
};
|
|
2177
|
+
return { name: canonicalName, profile: merged };
|
|
2178
|
+
};
|
|
2179
|
+
if (profileOverride !== void 0) {
|
|
2180
|
+
return mergeSelection(profileOverride);
|
|
2181
|
+
}
|
|
2182
|
+
if (profiles) {
|
|
2183
|
+
const defaultName = Object.keys(profiles).find((name) => profiles[name].default);
|
|
2184
|
+
if (defaultName) {
|
|
2185
|
+
return { name: defaultName, profile: profiles[defaultName] };
|
|
2186
|
+
}
|
|
2187
|
+
return { name: "full", profile: void 0 };
|
|
2188
|
+
}
|
|
2189
|
+
return { name: "full", profile: void 0 };
|
|
2190
|
+
}
|
|
2191
|
+
function applyProfile(config, profile) {
|
|
2192
|
+
if (!profile || profile.apps.length === 1 && profile.apps[0] === "*") {
|
|
2193
|
+
return config;
|
|
2194
|
+
}
|
|
2195
|
+
const appSet = new Set(profile.apps);
|
|
2196
|
+
const next = structuredClone(config);
|
|
2197
|
+
const kept = [];
|
|
2198
|
+
const keptDependencies = /* @__PURE__ */ new Set();
|
|
2199
|
+
for (const app of next.apps) {
|
|
2200
|
+
if (app.kind === "dependency") {
|
|
2201
|
+
continue;
|
|
2202
|
+
}
|
|
2203
|
+
if (!appSet.has(app.name)) {
|
|
2204
|
+
continue;
|
|
2205
|
+
}
|
|
2206
|
+
for (const dependency of resolveAppDependencies(config, app)) {
|
|
2207
|
+
keptDependencies.add(dependency.name);
|
|
2208
|
+
}
|
|
2209
|
+
kept.push(app);
|
|
2210
|
+
}
|
|
2211
|
+
for (const dependencyName of profile.dependencies ?? []) {
|
|
2212
|
+
keptDependencies.add(dependencyName);
|
|
2213
|
+
}
|
|
2214
|
+
for (const app of next.apps) {
|
|
2215
|
+
if (app.kind === "dependency" && keptDependencies.has(app.name)) {
|
|
2216
|
+
kept.push(app);
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
next.apps = kept;
|
|
2220
|
+
return next;
|
|
2221
|
+
}
|
|
2005
2222
|
function renderConfig(config) {
|
|
2006
2223
|
return import_yaml2.default.stringify(config, { lineWidth: 0 });
|
|
2007
2224
|
}
|
|
@@ -2049,7 +2266,7 @@ function loadRepoConfig(repoPath) {
|
|
|
2049
2266
|
const config = parseConfig(parsed ?? {}, configPath);
|
|
2050
2267
|
const requiredVersion = config.devrouter?.version;
|
|
2051
2268
|
if (requiredVersion && !hasWarnedVersionMismatch) {
|
|
2052
|
-
const cliVersion = true ? "0.0.
|
|
2269
|
+
const cliVersion = true ? "0.0.38" : "0.0.0-dev";
|
|
2053
2270
|
if (cliVersion !== "0.0.0-dev" && compareSemver(requiredVersion, cliVersion) > 0) {
|
|
2054
2271
|
hasWarnedVersionMismatch = true;
|
|
2055
2272
|
process.stderr.write(
|
|
@@ -2340,11 +2557,13 @@ function applyWorkspace(config, workspace, repoPath) {
|
|
|
2340
2557
|
}
|
|
2341
2558
|
return next;
|
|
2342
2559
|
}
|
|
2343
|
-
function loadRuntimeConfig(repoPath, workspaceOverride) {
|
|
2560
|
+
function loadRuntimeConfig(repoPath, workspaceOverride, profileOverride) {
|
|
2344
2561
|
const resolved = resolveRepoPath(repoPath);
|
|
2345
2562
|
const raw = loadRepoConfig(resolved);
|
|
2563
|
+
const resolvedProfile = resolveProfile(raw, profileOverride);
|
|
2346
2564
|
const workspace = resolveWorkspace(resolved, workspaceOverride);
|
|
2347
|
-
|
|
2565
|
+
const config = applyProfile(applyWorkspace(raw, workspace, resolved), resolvedProfile.profile);
|
|
2566
|
+
return { config, workspace, profile: resolvedProfile.name };
|
|
2348
2567
|
}
|
|
2349
2568
|
function resolveAppByName(repoPath, name, workspaceOverride) {
|
|
2350
2569
|
const { config, workspace } = loadRuntimeConfig(repoPath, workspaceOverride);
|
|
@@ -2386,7 +2605,7 @@ function resolveAppDependencies(config, app) {
|
|
|
2386
2605
|
}
|
|
2387
2606
|
return results;
|
|
2388
2607
|
}
|
|
2389
|
-
var import_node_fs7, import_node_path6, import_yaml2, hasWarnedVersionMismatch, CONFIG_FILE_NAME, DEFAULT_TCP_PROTOCOL, VALID_HOSTNAME_RE, DEVROUTER_VERSION_RE, VALID_ENV_NAME_RE, VALID_ENV_VAR_RE, UPSTREAM_TEMPLATE_RE, MAX_COMMAND_LENGTH, DEFAULT_HOST_STRATEGY;
|
|
2608
|
+
var import_node_fs7, import_node_path6, import_yaml2, hasWarnedVersionMismatch, CONFIG_FILE_NAME, DEFAULT_TCP_PROTOCOL, VALID_HOSTNAME_RE, DEVROUTER_VERSION_RE, VALID_ENV_NAME_RE, VALID_ENV_VAR_RE, UPSTREAM_TEMPLATE_RE, MAX_COMMAND_LENGTH, DEFAULT_HOST_STRATEGY, PROFILE_NAME_RE, MAX_PROFILES;
|
|
2390
2609
|
var init_repo_config = __esm({
|
|
2391
2610
|
"src/core/repo-config.ts"() {
|
|
2392
2611
|
"use strict";
|
|
@@ -2410,6 +2629,8 @@ var init_repo_config = __esm({
|
|
|
2410
2629
|
denyPorts: [80, 443, 5432],
|
|
2411
2630
|
allowPortRange: "1024-65535"
|
|
2412
2631
|
};
|
|
2632
|
+
PROFILE_NAME_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/;
|
|
2633
|
+
MAX_PROFILES = 32;
|
|
2413
2634
|
}
|
|
2414
2635
|
});
|
|
2415
2636
|
|
|
@@ -2470,6 +2691,7 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
2470
2691
|
"- project.name: string (optional)",
|
|
2471
2692
|
`- secretManager.command: string (optional; SM command including trailing \`--\` boundary; supports \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template placeholder)`,
|
|
2472
2693
|
`- secretManager.defaultEnv: string (optional; fallback env for \`${SECRET_MANAGER_ENV_PLACEHOLDER}\` template; required when command contains \`${SECRET_MANAGER_ENV_PLACEHOLDER}\`)`,
|
|
2694
|
+
"- profiles: map (optional; named subsets of routed apps; see Profile schema below)",
|
|
2473
2695
|
"- apps: array (required)",
|
|
2474
2696
|
"",
|
|
2475
2697
|
"Canonical valid skeleton:",
|
|
@@ -2514,6 +2736,14 @@ function buildOnboardingPrompt(options = {}) {
|
|
|
2514
2736
|
" - docker.composeFiles: string[]",
|
|
2515
2737
|
" - do not set host/protocol/tcpProtocol/hostRun/docker.internalPort/docker.router",
|
|
2516
2738
|
"",
|
|
2739
|
+
"Profile schema (each value in the optional top-level profiles map):",
|
|
2740
|
+
'- apps: array (required; routed app names, or ["*"] for every routed app)',
|
|
2741
|
+
"- dependencies: array (optional; kind=dependency names this profile needs; omitted = every dependency a kept app requires transitively)",
|
|
2742
|
+
"- readiness: array (optional; subset of the profile's apps that `devrouter ensure` HTTP-probes before readiness; omitted = all profile apps with an http route)",
|
|
2743
|
+
"- default: boolean (optional; at most one profile may set true)",
|
|
2744
|
+
"- validation: unknown profile keys are rejected; apps must reference routed apps (kind=app); dependencies must reference kind=dependency apps; readiness entries must be in the profile's apps; at most one default",
|
|
2745
|
+
"- selection: `devrouter ensure <path> --profile <name>` scopes routes/readiness to the profile; comma-separated selections (e.g. --profile a,b) merge with deduplication and a canonical sorted name; managed adapters receive DEVROUTER_PROFILE in the post-start env",
|
|
2746
|
+
"",
|
|
2517
2747
|
"Validation rules to enforce:",
|
|
2518
2748
|
"- kind=app host must end with .localhost",
|
|
2519
2749
|
`- kind=app runtime=host supports protocol=${formatProtocolRule("host")} only`,
|
|
@@ -3619,6 +3849,7 @@ function runManagedPostStart(options) {
|
|
|
3619
3849
|
`DEVROUTER_PROCESS_HELPER=${RUNTIME_HELPER_PATH}`,
|
|
3620
3850
|
"--env",
|
|
3621
3851
|
`DEVROUTER_PROCESS_ADAPTER_SHA256=${options.plan.adapterSha256}`,
|
|
3852
|
+
...options.profile ? ["--env", `DEVROUTER_PROFILE=${options.profile}`] : [],
|
|
3622
3853
|
options.container.id,
|
|
3623
3854
|
"bash",
|
|
3624
3855
|
"-c",
|
|
@@ -3630,7 +3861,7 @@ function runManagedPostStart(options) {
|
|
|
3630
3861
|
);
|
|
3631
3862
|
if (started.status !== 0) {
|
|
3632
3863
|
const details = commandFailure(started);
|
|
3633
|
-
throw new Error(`Managed post-start failed${details ? `: ${details}
|
|
3864
|
+
throw new Error(`Managed post-start failed${details ? `: ${details}.` : "."}`);
|
|
3634
3865
|
}
|
|
3635
3866
|
}
|
|
3636
3867
|
var import_node_child_process5, import_node_crypto3, import_node_fs9, import_node_path8, MANAGED_MARKER, MANAGED_ADAPTER_PATH, RUNTIME_HELPER_PATH, ADAPTER_WRAPPER;
|
|
@@ -5794,7 +6025,7 @@ async function buildDoctorReport(options = {}) {
|
|
|
5794
6025
|
const config = runtimeConfig.config;
|
|
5795
6026
|
loadedConfig = config;
|
|
5796
6027
|
loadedWorkspace = runtimeConfig.workspace;
|
|
5797
|
-
const cliVersion = true ? "0.0.
|
|
6028
|
+
const cliVersion = true ? "0.0.38" : "0.0.0-dev";
|
|
5798
6029
|
const configVersion = config.devrouter?.version;
|
|
5799
6030
|
if (configVersion && cliVersion !== "0.0.0-dev" && compareSemver(configVersion, cliVersion) > 0) {
|
|
5800
6031
|
addCheck(checks, {
|
|
@@ -6457,7 +6688,8 @@ function probeHttpRoute(host, options = {}) {
|
|
|
6457
6688
|
String(options.maxTimeSeconds ?? 5)
|
|
6458
6689
|
];
|
|
6459
6690
|
if (tlsEnabled) {
|
|
6460
|
-
|
|
6691
|
+
getMkcertRootCAPath({ repoPath: options.repoPath });
|
|
6692
|
+
args.push("--cacert", CERT_FILE);
|
|
6461
6693
|
}
|
|
6462
6694
|
args.push(url);
|
|
6463
6695
|
const result = (0, import_node_child_process13.spawnSync)("curl", args, { encoding: "utf-8" });
|
|
@@ -6781,7 +7013,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6781
7013
|
const managedPostStart = resolveManagedPostStartPlan(repoPath);
|
|
6782
7014
|
const runtime = loadRuntimeConfig(
|
|
6783
7015
|
repoPath,
|
|
6784
|
-
target.kind === "primary" ? "" : target.workspace
|
|
7016
|
+
target.kind === "primary" ? "" : target.workspace,
|
|
7017
|
+
options.profile
|
|
6785
7018
|
);
|
|
6786
7019
|
const apps = proxyAppsFromConfig(runtime.config);
|
|
6787
7020
|
const parsedUpstreams = apps.map((app) => parseUpstream(app.upstream));
|
|
@@ -6859,7 +7092,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6859
7092
|
runManagedPostStart({
|
|
6860
7093
|
plan: managedPostStart,
|
|
6861
7094
|
container,
|
|
6862
|
-
quiet: options.quiet
|
|
7095
|
+
quiet: options.quiet,
|
|
7096
|
+
profile: runtime.profile
|
|
6863
7097
|
});
|
|
6864
7098
|
const publication = await replacePublishedProxyRoutes(
|
|
6865
7099
|
repoPath,
|
|
@@ -6881,7 +7115,8 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6881
7115
|
runManagedPostStart({
|
|
6882
7116
|
plan: managedPostStart,
|
|
6883
7117
|
container: recoveredContainer,
|
|
6884
|
-
quiet: options.quiet
|
|
7118
|
+
quiet: options.quiet,
|
|
7119
|
+
profile: runtime.profile
|
|
6885
7120
|
});
|
|
6886
7121
|
recreated = true;
|
|
6887
7122
|
replaceHostRoutesForRepo(repoPath, publication.routes);
|
|
@@ -6906,6 +7141,7 @@ async function workspaceEnsure(requestedRepoPath, options = {}) {
|
|
|
6906
7141
|
kind: target.kind,
|
|
6907
7142
|
repoPath,
|
|
6908
7143
|
workspace: target.workspace,
|
|
7144
|
+
profile: runtime.profile,
|
|
6909
7145
|
devpodId,
|
|
6910
7146
|
urls,
|
|
6911
7147
|
recreated,
|
|
@@ -6977,14 +7213,15 @@ async function runEnsureCommand(options) {
|
|
|
6977
7213
|
const repoPath = resolveGitCheckoutPath(options.path);
|
|
6978
7214
|
const result = await workspaceEnsure(repoPath, {
|
|
6979
7215
|
open: options.open,
|
|
6980
|
-
quiet: Boolean(options.json)
|
|
7216
|
+
quiet: Boolean(options.json),
|
|
7217
|
+
profile: options.profile
|
|
6981
7218
|
});
|
|
6982
7219
|
if (options.json) {
|
|
6983
7220
|
process.stdout.write(`${JSON.stringify(result, null, 2)}
|
|
6984
7221
|
`);
|
|
6985
7222
|
return;
|
|
6986
7223
|
}
|
|
6987
|
-
const label = result.kind === "primary" ?
|
|
7224
|
+
const label = result.kind === "primary" ? `Primary checkout [profile: ${result.profile}]` : `Workspace '${result.workspace}' [profile: ${result.profile}]`;
|
|
6988
7225
|
const routes = result.urls.map((url) => ` ${url}`).join("\n");
|
|
6989
7226
|
process.stdout.write(`${label} is ready (${result.devpodId}).
|
|
6990
7227
|
${routes}${routes ? "\n" : ""}`);
|
|
@@ -11424,7 +11661,7 @@ var init_version = __esm({
|
|
|
11424
11661
|
|
|
11425
11662
|
// src/cli.ts
|
|
11426
11663
|
var import_commander = require("commander");
|
|
11427
|
-
var CLI_VERSION = true ? "0.0.
|
|
11664
|
+
var CLI_VERSION = true ? "0.0.38" : "0.0.0-dev";
|
|
11428
11665
|
var VERSION_FLAGS = /* @__PURE__ */ new Set(["-V", "--version"]);
|
|
11429
11666
|
function withErrorHandling(action2) {
|
|
11430
11667
|
return async (...args) => {
|
|
@@ -11474,12 +11711,13 @@ program.command("up").description("Ensure devnet and start shared Traefik (reser
|
|
|
11474
11711
|
await runUpCommand2();
|
|
11475
11712
|
})
|
|
11476
11713
|
);
|
|
11477
|
-
program.command("ensure").description("Start and prove a primary or linked checkout's environment and routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--open", "Open HTTP routes after readiness succeeds").option("--json", "Output JSON").action(
|
|
11714
|
+
program.command("ensure").description("Start and prove a primary or linked checkout's environment and routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--profile <name>", "Start only this profile's apps/dependencies from .devrouter.yml").option("--open", "Open HTTP routes after readiness succeeds").option("--json", "Output JSON").action(
|
|
11478
11715
|
withErrorHandling(async (repoPath, _options, command) => {
|
|
11479
11716
|
const options = command.opts();
|
|
11480
11717
|
const { runEnsureCommand: runEnsureCommand2 } = await Promise.resolve().then(() => (init_ensure(), ensure_exports));
|
|
11481
11718
|
await runEnsureCommand2({
|
|
11482
11719
|
path: repoPath,
|
|
11720
|
+
profile: options.profile,
|
|
11483
11721
|
open: Boolean(options.open),
|
|
11484
11722
|
json: Boolean(options.json)
|
|
11485
11723
|
});
|
|
@@ -11678,13 +11916,14 @@ workspaceCommand2.command("up").description(
|
|
|
11678
11916
|
});
|
|
11679
11917
|
})
|
|
11680
11918
|
);
|
|
11681
|
-
workspaceCommand2.command("ensure").description("Start and prove a primary or linked checkout's DevPod, upstreams, and routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--open", "Open HTTP routes after readiness succeeds").option("--json", "Output JSON").action(
|
|
11919
|
+
workspaceCommand2.command("ensure").description("Start and prove a primary or linked checkout's DevPod, upstreams, and routes").argument("[path]", "Git checkout path (defaults to current directory)").option("--profile <name>", "Start only this profile's apps/dependencies from .devrouter.yml").option("--open", "Open HTTP routes after readiness succeeds").option("--json", "Output JSON").action(
|
|
11682
11920
|
withErrorHandling(
|
|
11683
11921
|
async (worktreePath, _options, command) => {
|
|
11684
11922
|
const options = command.opts();
|
|
11685
11923
|
const { runEnsureCommand: runEnsureCommand2 } = await Promise.resolve().then(() => (init_ensure(), ensure_exports));
|
|
11686
11924
|
await runEnsureCommand2({
|
|
11687
11925
|
path: worktreePath,
|
|
11926
|
+
profile: options.profile,
|
|
11688
11927
|
open: Boolean(options.open),
|
|
11689
11928
|
json: Boolean(options.json)
|
|
11690
11929
|
});
|
package/package.json
CHANGED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.0.37
|
|
2
|
+
|
|
3
|
+
Devrouter HTTPS readiness probes now pin curl to the exact certificate served
|
|
4
|
+
by the local Traefik router. This avoids a macOS SecureTransport failure while
|
|
5
|
+
retaining hostname verification, SNI, and the existing route status contract.
|
|
6
|
+
|
|
7
|
+
1. Install `@devrouter/cli@0.0.37` on the host and bump `.devrouter.yml` to
|
|
8
|
+
`devrouter.version: 0.0.37`.
|
|
9
|
+
2. No schema or data migration is required.
|
|
10
|
+
3. Re-run `devrouter doctor --repo <checkout> --json` and then use
|
|
11
|
+
`devrouter ensure <checkout> --json` for managed startup. The readiness
|
|
12
|
+
probe still requires the existing mkcert setup and does not use an
|
|
13
|
+
insecure certificate bypass.
|
|
14
|
+
|
|
15
|
+
Verification:
|
|
16
|
+
|
|
17
|
+
- Run `devrouter -V --repo <checkout>` and confirm both the installed CLI and
|
|
18
|
+
local repository report `0.0.37`.
|
|
19
|
+
- Run `devrouter repo devcontainer verify --repo <checkout> --json` and confirm
|
|
20
|
+
the static checks pass.
|
|
21
|
+
- Run `devrouter ensure <checkout> --json` and confirm the HTTPS route reaches
|
|
22
|
+
the existing status contract (`100..499` accepted, `5xx` rejected).
|
|
23
|
+
|
|
24
|
+
Report template:
|
|
25
|
+
|
|
26
|
+
- CLI/config version: `0.0.37`
|
|
27
|
+
- mkcert setup and certificate readiness: `<passed or details>`
|
|
28
|
+
- Static verification: `<passed or details>`
|
|
29
|
+
- Managed HTTPS readiness: `<passed or details>`
|
|
30
|
+
- Remaining blockers: `<none or details>`
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Upgrade to devrouter 0.0.38
|
|
2
|
+
|
|
3
|
+
Devrouter now supports profile-scoped environments: a `profiles` map in
|
|
4
|
+
`.devrouter.yml` plus `devrouter ensure --profile <name>` starts, routes, and
|
|
5
|
+
readiness-probes only the apps a task needs. Comma-separated selections
|
|
6
|
+
(`--profile manage,pwa`) merge profiles with deduplication, and managed
|
|
7
|
+
adapters receive `DEVROUTER_PROFILE` so they can scope their dev process tree
|
|
8
|
+
to the same selection.
|
|
9
|
+
|
|
10
|
+
1. Install `@devrouter/cli@0.0.38` on the host and bump `.devrouter.yml` to
|
|
11
|
+
`devrouter.version: 0.0.38`.
|
|
12
|
+
2. No schema or data migration is required. Repositories without a `profiles`
|
|
13
|
+
key keep the implicit full behavior; adding `profiles` is optional.
|
|
14
|
+
Validation is strict: unknown keys, references to apps that are not routed
|
|
15
|
+
(`kind=app`), references to apps that are not `kind=dependency` in
|
|
16
|
+
`dependencies`, `readiness` entries outside the profile's apps, and more
|
|
17
|
+
than one `default: true` are rejected at config-load time.
|
|
18
|
+
3. Optional: declare profiles and select one per task, e.g.
|
|
19
|
+
`devrouter ensure <checkout> --profile <name>` or
|
|
20
|
+
`devrouter ensure <checkout> --profile <name-a>,<name-b>`. The merged name
|
|
21
|
+
is canonicalized (sorted unique), so order does not affect identity.
|
|
22
|
+
Repository post-start adapters can read `DEVROUTER_PROFILE` and must treat
|
|
23
|
+
an empty/unset value as the default profile.
|
|
24
|
+
4. Consumers that parse `ensure --json` output must accept the new `profile`
|
|
25
|
+
field in the result object.
|
|
26
|
+
|
|
27
|
+
Verification:
|
|
28
|
+
|
|
29
|
+
- Run `devrouter -V --repo <checkout>` and confirm both the installed CLI and
|
|
30
|
+
the local repository report `0.0.38`.
|
|
31
|
+
- Run `devrouter doctor --repo <checkout> --json` and confirm the config still
|
|
32
|
+
validates.
|
|
33
|
+
- With profiles declared, run `devrouter ensure <checkout> --profile <name>
|
|
34
|
+
--json` and confirm the result reports `"profile": "<name>"` and that only
|
|
35
|
+
the profile's routes are published (`devrouter ls`).
|
|
36
|
+
- Re-run `devrouter ensure <checkout> --json` without `--profile` and confirm
|
|
37
|
+
the default/full route set is restored.
|
|
38
|
+
|
|
39
|
+
Report template:
|
|
40
|
+
|
|
41
|
+
- CLI/config version: `0.0.38`
|
|
42
|
+
- Config validation: `<passed or details>`
|
|
43
|
+
- Profile-scoped ensure: `<passed or details>`
|
|
44
|
+
- Default (full) ensure after profile use: `<passed or details>`
|
|
45
|
+
- Remaining blockers: `<none or details>`
|