@getmonoceros/workbench 1.13.0 → 1.13.2
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/bin.js +214 -74
- package/dist/bin.js.map +1 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -356,13 +356,12 @@ var FeatureEntrySchema = z.object({
|
|
|
356
356
|
options: z.record(z.string(), FeatureOptionValueSchema).optional()
|
|
357
357
|
});
|
|
358
358
|
var EMAIL_RE = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
359
|
+
function isValidEmail(value) {
|
|
360
|
+
return EMAIL_RE.test(value);
|
|
361
|
+
}
|
|
359
362
|
var GitUserSchema = z.object({
|
|
360
363
|
name: z.union([z.literal(""), z.null(), z.string().min(1)]).nullish().transform((v) => typeof v === "string" && v.length > 0 ? v : void 0),
|
|
361
|
-
email: z.union([
|
|
362
|
-
z.literal(""),
|
|
363
|
-
z.null(),
|
|
364
|
-
z.string().regex(EMAIL_RE, "Invalid email")
|
|
365
|
-
]).nullish().transform((v) => typeof v === "string" && v.length > 0 ? v : void 0)
|
|
364
|
+
email: z.union([z.literal(""), z.null(), z.string().min(1)]).nullish().transform((v) => typeof v === "string" && v.length > 0 ? v : void 0)
|
|
366
365
|
});
|
|
367
366
|
var RepoEntrySchema = z.object({
|
|
368
367
|
url: z.string().regex(
|
|
@@ -717,6 +716,23 @@ function interpolateServices(services, vars) {
|
|
|
717
716
|
});
|
|
718
717
|
return { services: resolved, missing };
|
|
719
718
|
}
|
|
719
|
+
var GIT_IDENTITY_VAR = {
|
|
720
|
+
name: "GIT_USER_NAME",
|
|
721
|
+
email: "GIT_USER_EMAIL"
|
|
722
|
+
};
|
|
723
|
+
function hasVarPlaceholder(value) {
|
|
724
|
+
return /\$\{[A-Za-z_][A-Za-z0-9_]*\}/.test(value);
|
|
725
|
+
}
|
|
726
|
+
function resolveGitUserFields(user, vars) {
|
|
727
|
+
const resolve = (raw) => {
|
|
728
|
+
if (raw === void 0) return {};
|
|
729
|
+
const r = interpolate(raw, vars);
|
|
730
|
+
if (r.missing.length > 0) return {};
|
|
731
|
+
const trimmed = r.value.trim();
|
|
732
|
+
return trimmed.length > 0 ? { value: trimmed } : {};
|
|
733
|
+
};
|
|
734
|
+
return { name: resolve(user.name), email: resolve(user.email) };
|
|
735
|
+
}
|
|
720
736
|
function interpolateFeatures(features, vars) {
|
|
721
737
|
const missing = [];
|
|
722
738
|
const out = {};
|
|
@@ -742,13 +758,20 @@ function buildEnvStub(name) {
|
|
|
742
758
|
`;
|
|
743
759
|
}
|
|
744
760
|
async function ensureEnvVars(envPath, name, vars) {
|
|
761
|
+
const entries = Array.isArray(vars) ? vars.map((v) => [v, ""]) : Object.entries(vars);
|
|
745
762
|
const exists = existsSync2(envPath);
|
|
746
763
|
let content = exists ? readFileSync(envPath, "utf8") : buildEnvStub(name);
|
|
747
764
|
const present = new Set(Object.keys(parseEnvFile(content)));
|
|
748
|
-
const
|
|
765
|
+
const seen = /* @__PURE__ */ new Set();
|
|
766
|
+
const toAdd = entries.filter(([k]) => {
|
|
767
|
+
if (present.has(k) || seen.has(k)) return false;
|
|
768
|
+
seen.add(k);
|
|
769
|
+
return true;
|
|
770
|
+
});
|
|
771
|
+
const added = toAdd.map(([k]) => k);
|
|
749
772
|
if (!exists || added.length > 0) {
|
|
750
773
|
if (content.length > 0 && !content.endsWith("\n")) content += "\n";
|
|
751
|
-
for (const v of
|
|
774
|
+
for (const [k, v] of toAdd) content += `${k}=${v}
|
|
752
775
|
`;
|
|
753
776
|
await fsp.mkdir(path2.dirname(envPath), { recursive: true });
|
|
754
777
|
await fsp.writeFile(envPath, content);
|
|
@@ -1355,7 +1378,15 @@ var MonocerosConfigSchema = z2.object({
|
|
|
1355
1378
|
// `git: null` for an empty mapping, which zod's plain
|
|
1356
1379
|
// `.optional()` would reject.
|
|
1357
1380
|
git: z2.object({
|
|
1358
|
-
|
|
1381
|
+
// Strict email here: monoceros-config defaults are not tied to
|
|
1382
|
+
// any container `<name>.env`, so `${VAR}` placeholders make no
|
|
1383
|
+
// sense and the format can (and should) be validated at load
|
|
1384
|
+
// time — unlike the container/repo `git.user`, which defers to
|
|
1385
|
+
// apply after interpolation.
|
|
1386
|
+
user: GitUserSchema.optional().refine(
|
|
1387
|
+
(u) => u?.email === void 0 || isValidEmail(u.email),
|
|
1388
|
+
{ message: "Invalid email in defaults.git.user", path: ["email"] }
|
|
1389
|
+
)
|
|
1359
1390
|
}).nullish(),
|
|
1360
1391
|
// .nullish() for the same reason as `git` — the sample keeps
|
|
1361
1392
|
// `features:` uncommented as a category marker.
|
|
@@ -1972,6 +2003,19 @@ var SERVICE_CATALOG = {
|
|
|
1972
2003
|
POSTGRES_PASSWORD: "monoceros",
|
|
1973
2004
|
POSTGRES_DB: "monoceros"
|
|
1974
2005
|
},
|
|
2006
|
+
healthcheck: {
|
|
2007
|
+
test: [
|
|
2008
|
+
"CMD",
|
|
2009
|
+
"pg_isready",
|
|
2010
|
+
"-U",
|
|
2011
|
+
"${POSTGRES_USER}",
|
|
2012
|
+
"-d",
|
|
2013
|
+
"${POSTGRES_DB}"
|
|
2014
|
+
],
|
|
2015
|
+
interval: "10s",
|
|
2016
|
+
timeout: "5s",
|
|
2017
|
+
retries: 5
|
|
2018
|
+
},
|
|
1975
2019
|
// Postgres 18+ stores data under /var/lib/postgresql/<major>/, so
|
|
1976
2020
|
// the recommended mount is the parent directory; pre-18 used
|
|
1977
2021
|
// /var/lib/postgresql/data directly. See
|
|
@@ -1986,12 +2030,33 @@ var SERVICE_CATALOG = {
|
|
|
1986
2030
|
MYSQL_ROOT_PASSWORD: "monoceros",
|
|
1987
2031
|
MYSQL_DATABASE: "monoceros"
|
|
1988
2032
|
},
|
|
2033
|
+
healthcheck: {
|
|
2034
|
+
test: [
|
|
2035
|
+
"CMD",
|
|
2036
|
+
"mysqladmin",
|
|
2037
|
+
"ping",
|
|
2038
|
+
"-h",
|
|
2039
|
+
"127.0.0.1",
|
|
2040
|
+
"-u",
|
|
2041
|
+
"root",
|
|
2042
|
+
"-p${MYSQL_ROOT_PASSWORD}"
|
|
2043
|
+
],
|
|
2044
|
+
interval: "10s",
|
|
2045
|
+
timeout: "5s",
|
|
2046
|
+
retries: 5
|
|
2047
|
+
},
|
|
1989
2048
|
dataMount: "/var/lib/mysql",
|
|
1990
2049
|
defaultPort: 3306
|
|
1991
2050
|
},
|
|
1992
2051
|
redis: {
|
|
1993
2052
|
id: "redis",
|
|
1994
2053
|
image: "redis:8",
|
|
2054
|
+
healthcheck: {
|
|
2055
|
+
test: ["CMD", "redis-cli", "ping"],
|
|
2056
|
+
interval: "10s",
|
|
2057
|
+
timeout: "5s",
|
|
2058
|
+
retries: 5
|
|
2059
|
+
},
|
|
1995
2060
|
dataMount: "/data",
|
|
1996
2061
|
defaultPort: 6379
|
|
1997
2062
|
}
|
|
@@ -2028,10 +2093,20 @@ function expandCuratedService(name) {
|
|
|
2028
2093
|
name: def.id,
|
|
2029
2094
|
image: def.image,
|
|
2030
2095
|
port: def.defaultPort,
|
|
2031
|
-
...def.env ? {
|
|
2032
|
-
|
|
2096
|
+
...def.env ? {
|
|
2097
|
+
env: Object.fromEntries(
|
|
2098
|
+
Object.keys(def.env).map((k) => [k, `\${${k}}`])
|
|
2099
|
+
)
|
|
2100
|
+
} : {},
|
|
2101
|
+
...def.dataMount ? { volumes: [`data:${def.dataMount}`] } : {},
|
|
2102
|
+
...def.healthcheck ? { healthcheck: def.healthcheck } : {},
|
|
2103
|
+
restart: "unless-stopped"
|
|
2033
2104
|
};
|
|
2034
2105
|
}
|
|
2106
|
+
function curatedServiceEnvDefaults(name) {
|
|
2107
|
+
const def = SERVICE_CATALOG[name];
|
|
2108
|
+
return def?.env ? { ...def.env } : {};
|
|
2109
|
+
}
|
|
2035
2110
|
function deriveServiceName(image) {
|
|
2036
2111
|
const lastSegment = image.split("/").pop() ?? image;
|
|
2037
2112
|
const noTag = lastSegment.split("@")[0].split(":")[0];
|
|
@@ -2772,6 +2847,17 @@ function setContainerGitUserInDoc(doc, user) {
|
|
|
2772
2847
|
relocateLeakedSectionComments(doc);
|
|
2773
2848
|
return true;
|
|
2774
2849
|
}
|
|
2850
|
+
function ensureContainerGitUserPlaceholder(doc) {
|
|
2851
|
+
const gitNode = doc.get("git", true);
|
|
2852
|
+
if (gitNode && isMap2(gitNode)) {
|
|
2853
|
+
const userNode = gitNode.get("user", true);
|
|
2854
|
+
if (userNode && isMap2(userNode)) return false;
|
|
2855
|
+
}
|
|
2856
|
+
return setContainerGitUserInDoc(doc, {
|
|
2857
|
+
name: `\${${GIT_IDENTITY_VAR.name}}`,
|
|
2858
|
+
email: `\${${GIT_IDENTITY_VAR.email}}`
|
|
2859
|
+
});
|
|
2860
|
+
}
|
|
2775
2861
|
function relocateLeakedSectionComments(doc) {
|
|
2776
2862
|
const root = doc.contents;
|
|
2777
2863
|
if (!root || !isMap2(root)) return;
|
|
@@ -3137,8 +3223,26 @@ async function runAddService(input) {
|
|
|
3137
3223
|
}
|
|
3138
3224
|
return r.outcome === "added";
|
|
3139
3225
|
});
|
|
3140
|
-
if (result.status === "updated"
|
|
3141
|
-
|
|
3226
|
+
if (result.status === "updated") {
|
|
3227
|
+
if (curated) {
|
|
3228
|
+
const defaults = curatedServiceEnvDefaults(arg);
|
|
3229
|
+
if (Object.keys(defaults).length > 0) {
|
|
3230
|
+
const home = input.monocerosHome ?? monocerosHome();
|
|
3231
|
+
await ensureEnvGitignored(containerConfigsDir(home));
|
|
3232
|
+
const seeded = await ensureEnvVars(
|
|
3233
|
+
containerEnvPath(input.name, home),
|
|
3234
|
+
input.name,
|
|
3235
|
+
defaults
|
|
3236
|
+
);
|
|
3237
|
+
if (seeded.added.length > 0) {
|
|
3238
|
+
(input.logger ?? defaultLogger()).info(
|
|
3239
|
+
`Seeded ${seeded.added.join(", ")} into ${input.name}.env (dev-defaults \u2014 change them there if needed).`
|
|
3240
|
+
);
|
|
3241
|
+
}
|
|
3242
|
+
}
|
|
3243
|
+
} else {
|
|
3244
|
+
(input.logger ?? defaultLogger()).info(customServiceHint(name));
|
|
3245
|
+
}
|
|
3142
3246
|
}
|
|
3143
3247
|
return result;
|
|
3144
3248
|
}
|
|
@@ -3165,6 +3269,14 @@ async function runAddRepo(input) {
|
|
|
3165
3269
|
"--git-name and --git-email must be set together. Pass both, or neither."
|
|
3166
3270
|
);
|
|
3167
3271
|
}
|
|
3272
|
+
if (hasEmail) {
|
|
3273
|
+
const email = input.gitEmail.trim();
|
|
3274
|
+
if (!isValidEmail(email) && !hasVarPlaceholder(email)) {
|
|
3275
|
+
throw new Error(
|
|
3276
|
+
`Invalid --git-email '${email}': must be a valid email or a \${VAR} placeholder resolved from <name>.env.`
|
|
3277
|
+
);
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3168
3280
|
const explicitProvider = normalizeProvider(input.provider);
|
|
3169
3281
|
let host;
|
|
3170
3282
|
try {
|
|
@@ -3195,7 +3307,23 @@ async function runAddRepo(input) {
|
|
|
3195
3307
|
} : {},
|
|
3196
3308
|
...providerToWrite ? { provider: providerToWrite } : {}
|
|
3197
3309
|
};
|
|
3198
|
-
|
|
3310
|
+
let gitUserScaffolded = false;
|
|
3311
|
+
const result = await mutate(input, (doc) => {
|
|
3312
|
+
const repoAdded = addRepoToDoc(doc, entry2);
|
|
3313
|
+
if (repoAdded) gitUserScaffolded = ensureContainerGitUserPlaceholder(doc);
|
|
3314
|
+
return repoAdded;
|
|
3315
|
+
});
|
|
3316
|
+
if (result.status === "updated" && gitUserScaffolded) {
|
|
3317
|
+
const home = input.monocerosHome ?? monocerosHome();
|
|
3318
|
+
await ensureEnvGitignored(containerConfigsDir(home));
|
|
3319
|
+
await ensureEnvVars(containerEnvPath(input.name, home), input.name, [
|
|
3320
|
+
GIT_IDENTITY_VAR.name,
|
|
3321
|
+
GIT_IDENTITY_VAR.email
|
|
3322
|
+
]);
|
|
3323
|
+
(input.logger ?? defaultLogger()).info(
|
|
3324
|
+
`Added a container git.user with \${${GIT_IDENTITY_VAR.name}}/\${${GIT_IDENTITY_VAR.email}} placeholders and seeded ${input.name}.env \u2014 fill them or leave blank to use your global git identity.`
|
|
3325
|
+
);
|
|
3326
|
+
}
|
|
3199
3327
|
if (result.status === "updated") {
|
|
3200
3328
|
await tryCloneInRunningContainer(input, entry2);
|
|
3201
3329
|
}
|
|
@@ -4971,6 +5099,44 @@ ${sectionLine(label)}
|
|
|
4971
5099
|
}
|
|
4972
5100
|
createOpts.services = interpServices.services;
|
|
4973
5101
|
if (createOpts.features) createOpts.features = interpFeatures.features;
|
|
5102
|
+
const gitUserErrors = [];
|
|
5103
|
+
let containerGitOverride;
|
|
5104
|
+
if (parsed.config.git?.user) {
|
|
5105
|
+
const f = resolveGitUserFields(parsed.config.git.user, envVars);
|
|
5106
|
+
if (f.email.value !== void 0 && !isValidEmail(f.email.value)) {
|
|
5107
|
+
gitUserErrors.push(
|
|
5108
|
+
`git.user.email resolved to "${f.email.value}", which is not a valid email`
|
|
5109
|
+
);
|
|
5110
|
+
}
|
|
5111
|
+
const override2 = {
|
|
5112
|
+
...f.name.value !== void 0 ? { name: f.name.value } : {},
|
|
5113
|
+
...f.email.value !== void 0 ? { email: f.email.value } : {}
|
|
5114
|
+
};
|
|
5115
|
+
if (Object.keys(override2).length > 0) containerGitOverride = override2;
|
|
5116
|
+
}
|
|
5117
|
+
for (const repo of createOpts.repos ?? []) {
|
|
5118
|
+
if (!repo.gitUser) continue;
|
|
5119
|
+
const f = resolveGitUserFields(repo.gitUser, envVars);
|
|
5120
|
+
if (f.name.value === void 0 || f.email.value === void 0) {
|
|
5121
|
+
delete repo.gitUser;
|
|
5122
|
+
continue;
|
|
5123
|
+
}
|
|
5124
|
+
if (!isValidEmail(f.email.value)) {
|
|
5125
|
+
gitUserErrors.push(
|
|
5126
|
+
`repos[${repo.path}].git.user.email resolved to "${f.email.value}", which is not a valid email`
|
|
5127
|
+
);
|
|
5128
|
+
continue;
|
|
5129
|
+
}
|
|
5130
|
+
repo.gitUser = { name: f.name.value, email: f.email.value };
|
|
5131
|
+
}
|
|
5132
|
+
if (gitUserErrors.length > 0) {
|
|
5133
|
+
throw new Error(
|
|
5134
|
+
`Invalid git identity after resolving ${prettyPath(envPath)}:
|
|
5135
|
+
` + gitUserErrors.map((e) => ` - ${e}`).join("\n") + `
|
|
5136
|
+
|
|
5137
|
+
Fix the value in the env file (or the yml).`
|
|
5138
|
+
);
|
|
5139
|
+
}
|
|
4974
5140
|
validateOptions(createOpts);
|
|
4975
5141
|
logger.success(`yml validated ${dim(`(${prettyPath(ymlPath)})`)}`);
|
|
4976
5142
|
const hasRepos = (createOpts.repos ?? []).length > 0;
|
|
@@ -4985,7 +5151,7 @@ ${sectionLine(label)}
|
|
|
4985
5151
|
...opts.identitySpawn ? { spawn: opts.identitySpawn } : {},
|
|
4986
5152
|
...opts.identityPrompt ? { prompt: opts.identityPrompt } : {},
|
|
4987
5153
|
...opts.identityScopePrompt ? { scopePrompt: opts.identityScopePrompt } : {},
|
|
4988
|
-
...
|
|
5154
|
+
...containerGitOverride ? { containerOverride: containerGitOverride } : {},
|
|
4989
5155
|
...globalConfig?.defaults?.git?.user ? { defaults: globalConfig.defaults.git.user } : {},
|
|
4990
5156
|
logger: idLogger
|
|
4991
5157
|
});
|
|
@@ -5193,7 +5359,7 @@ async function persistPromptedIdentity(prompted, ymlPath, home, logger) {
|
|
|
5193
5359
|
}
|
|
5194
5360
|
|
|
5195
5361
|
// src/version.ts
|
|
5196
|
-
var CLI_VERSION = true ? "1.13.
|
|
5362
|
+
var CLI_VERSION = true ? "1.13.2" : "dev";
|
|
5197
5363
|
|
|
5198
5364
|
// src/commands/_dispatch.ts
|
|
5199
5365
|
import { consola as consola12 } from "consola";
|
|
@@ -5828,6 +5994,7 @@ function generateComposedYml(name, composed, lookupManifest, repoUrls = [], port
|
|
|
5828
5994
|
lines.push("");
|
|
5829
5995
|
}
|
|
5830
5996
|
if (repoUrls.length > 0) {
|
|
5997
|
+
pushGitIdentityBlock(lines);
|
|
5831
5998
|
pushSectionHeader(
|
|
5832
5999
|
lines,
|
|
5833
6000
|
REPOS_HEADER,
|
|
@@ -5945,6 +6112,7 @@ function generateDocumentedYml(name, catalog, lookupManifest, repoUrls = [], por
|
|
|
5945
6112
|
lines.push("");
|
|
5946
6113
|
}
|
|
5947
6114
|
if (repoUrls.length > 0) {
|
|
6115
|
+
pushGitIdentityBlock(lines);
|
|
5948
6116
|
pushSectionHeader(
|
|
5949
6117
|
lines,
|
|
5950
6118
|
REPOS_HEADER,
|
|
@@ -6024,6 +6192,20 @@ function pushServiceEntry(out, svc) {
|
|
|
6024
6192
|
var FEATURES_HEADER_ACTIVE = "A Monoceros dev-container is shaped by features \u2014 pluggable units that drop tooling (AI assistants, language CLIs, cloud SDKs, \u2026) into the container and bring their own options. The features active for this container are listed below; adjust their options as needed. Shared credentials used across containers belong in monoceros-config.yml under `defaults.features.<ref>` rather than here. Full catalog: `monoceros list-components`.";
|
|
6025
6193
|
var FEATURES_HEADER_DOCUMENTED = "A Monoceros dev-container is shaped by features \u2014 pluggable units that drop tooling (AI assistants, language CLIs, cloud SDKs, \u2026) into the container and bring their own options. Un-comment the blocks below for the features you want active. Shared credentials used across containers belong in monoceros-config.yml under `defaults.features.<ref>` rather than here. Full catalog: `monoceros list-components`.";
|
|
6026
6194
|
var REPOS_HEADER = "Git repositories cloned into `projects/` on container start-up. HTTPS URLs only. The provider is auto-detected for github.com / gitlab.com / bitbucket.org; for any other host (self-hosted GitLab, Gitea, \u2026) declare `provider:` explicitly. Add more later with `monoceros add-repo`.";
|
|
6195
|
+
var GIT_IDENTITY_HEADER = "Git committer identity for commits made inside the container. The ${VAR} values resolve from <name>.env at apply time \u2014 fill them there, or leave them blank to fall back to your global git config (or a one-time prompt). Override per repo under repos[].git.user.";
|
|
6196
|
+
function pushGitIdentityBlock(lines) {
|
|
6197
|
+
pushSectionHeader(
|
|
6198
|
+
lines,
|
|
6199
|
+
GIT_IDENTITY_HEADER,
|
|
6200
|
+
/* commented */
|
|
6201
|
+
false
|
|
6202
|
+
);
|
|
6203
|
+
lines.push("git:");
|
|
6204
|
+
lines.push(" user:");
|
|
6205
|
+
lines.push(` name: \${${GIT_IDENTITY_VAR.name}}`);
|
|
6206
|
+
lines.push(` email: \${${GIT_IDENTITY_VAR.email}}`);
|
|
6207
|
+
lines.push("");
|
|
6208
|
+
}
|
|
6027
6209
|
function routingHeader(name) {
|
|
6028
6210
|
return `Container ports exposed to the host through Traefik. Reach them in your browser as ${name}-<port>.localhost (e.g. ${name}-3000.localhost). The first entry is the default route and is also reachable as the bare ${name}.localhost. Manage the list with \`monoceros add-port\`.`;
|
|
6029
6211
|
}
|
|
@@ -6168,17 +6350,6 @@ async function runInit(opts) {
|
|
|
6168
6350
|
seenPorts.add(raw);
|
|
6169
6351
|
ports.push(raw);
|
|
6170
6352
|
}
|
|
6171
|
-
let promptedIdentity;
|
|
6172
|
-
if (repos.length > 0) {
|
|
6173
|
-
const globalConfig = await readMonocerosConfig({ monocerosHome: home });
|
|
6174
|
-
promptedIdentity = await resolveIdentityWithPrompt({
|
|
6175
|
-
...opts.identitySpawn ? { spawn: opts.identitySpawn } : {},
|
|
6176
|
-
...opts.identityPrompt ? { prompt: opts.identityPrompt } : {},
|
|
6177
|
-
...opts.identityScopePrompt ? { scopePrompt: opts.identityScopePrompt } : {},
|
|
6178
|
-
...globalConfig?.defaults?.git?.user ? { defaults: globalConfig.defaults.git.user } : {},
|
|
6179
|
-
logger: { info: logger.info, warn: logger.info }
|
|
6180
|
-
});
|
|
6181
|
-
}
|
|
6182
6353
|
let text;
|
|
6183
6354
|
const composed = resolveComposedInit(catalog, {
|
|
6184
6355
|
languages: opts.languages ?? [],
|
|
@@ -6196,57 +6367,26 @@ async function runInit(opts) {
|
|
|
6196
6367
|
await ensureEnvGitignored(containerConfigsDir(home));
|
|
6197
6368
|
await fs14.writeFile(dest, text, "utf8");
|
|
6198
6369
|
const envPath = containerEnvPath(opts.name, home);
|
|
6199
|
-
const
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
if (scope === "g" || scope === "b") {
|
|
6208
|
-
try {
|
|
6209
|
-
const result = await writeGlobalDefaultGitUser(
|
|
6210
|
-
{ name, email },
|
|
6211
|
-
{ monocerosHome: home }
|
|
6212
|
-
);
|
|
6213
|
-
if (result.alreadySet) {
|
|
6214
|
-
logger.info(
|
|
6215
|
-
`monoceros-config.yml already had a defaults.git.user \u2014 left it alone.`
|
|
6216
|
-
);
|
|
6217
|
-
} else if (result.created) {
|
|
6218
|
-
logger.info(
|
|
6219
|
-
`Saved identity globally \u2014 created ${prettyPath(result.filePath)} with defaults.git.user.`
|
|
6220
|
-
);
|
|
6221
|
-
} else {
|
|
6222
|
-
logger.info(
|
|
6223
|
-
`Saved identity globally to ${prettyPath(result.filePath)}.`
|
|
6224
|
-
);
|
|
6225
|
-
}
|
|
6226
|
-
} catch (err) {
|
|
6227
|
-
logger.info(
|
|
6228
|
-
`Could not persist identity to monoceros-config.yml: ${err instanceof Error ? err.message : String(err)}. \`monoceros apply\` will re-prompt.`
|
|
6229
|
-
);
|
|
6230
|
-
}
|
|
6370
|
+
const seedVars = {};
|
|
6371
|
+
for (const f of composed.features) {
|
|
6372
|
+
for (const h of featureOptionHints(
|
|
6373
|
+
lookup(f.ref),
|
|
6374
|
+
f.ref,
|
|
6375
|
+
Object.keys(f.options ?? {})
|
|
6376
|
+
)) {
|
|
6377
|
+
if (!(h.envVar in seedVars)) seedVars[h.envVar] = "";
|
|
6231
6378
|
}
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6236
|
-
const changed = setContainerGitUserInDoc(parsed.doc, { name, email });
|
|
6237
|
-
if (changed) {
|
|
6238
|
-
await fs14.writeFile(dest, stringifyConfig(parsed.doc), "utf8");
|
|
6239
|
-
logger.info(
|
|
6240
|
-
`Saved identity in ${prettyPath(dest)} (container-level git.user).`
|
|
6241
|
-
);
|
|
6242
|
-
}
|
|
6243
|
-
} catch (err) {
|
|
6244
|
-
logger.info(
|
|
6245
|
-
`Could not persist identity into ${prettyPath(dest)}: ${err instanceof Error ? err.message : String(err)}. \`monoceros apply\` will re-prompt.`
|
|
6246
|
-
);
|
|
6247
|
-
}
|
|
6379
|
+
}
|
|
6380
|
+
for (const svc of composed.services) {
|
|
6381
|
+
if (svc.kind === "curated") {
|
|
6382
|
+
Object.assign(seedVars, curatedServiceEnvDefaults(svc.name));
|
|
6248
6383
|
}
|
|
6249
6384
|
}
|
|
6385
|
+
if (repos.length > 0) {
|
|
6386
|
+
seedVars[GIT_IDENTITY_VAR.name] = "";
|
|
6387
|
+
seedVars[GIT_IDENTITY_VAR.email] = "";
|
|
6388
|
+
}
|
|
6389
|
+
await ensureEnvVars(envPath, opts.name, seedVars);
|
|
6250
6390
|
const documented = !anyComposed;
|
|
6251
6391
|
const ymlRel = path16.relative(home, dest);
|
|
6252
6392
|
const envRel = path16.relative(home, envPath);
|