@nmakarov/cli-toolkit 0.46.0 → 0.49.0

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/index.cjs CHANGED
@@ -1142,6 +1142,7 @@ __export(src_exports, {
1142
1142
  h: () => import_react5.createElement,
1143
1143
  initServiceStructure: () => initServiceStructure,
1144
1144
  installDeps: () => installDeps,
1145
+ installOperatorShell: () => installOperatorShell,
1145
1146
  ipcFileLogsTableNameForSourceResource: () => ipcFileLogsTableNameForSourceResource,
1146
1147
  joiEdateType: () => joiEdateType,
1147
1148
  joiStringArrayType: () => joiStringArrayType,
@@ -1561,36 +1562,29 @@ var Args = class _Args {
1561
1562
  }
1562
1563
  return;
1563
1564
  }
1564
- let dotEnvPathFile = null;
1565
- const envSpecificFile = `.env.${this.env}`;
1566
- const envSpecificPath = (0, import_path.resolve)(dotEnvPath, envSpecificFile);
1567
- if ((0, import_fs.existsSync)(envSpecificPath)) {
1568
- dotEnvPathFile = envSpecificPath;
1569
- }
1570
- if (!dotEnvPathFile && !this.get("dotEnvPath")) {
1565
+ let dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, dotEnvFile);
1566
+ if (!(0, import_fs.existsSync)(dotEnvPathFile) && !this.get("dotEnvPath")) {
1571
1567
  const examplesPath = (0, import_path.resolve)(dotEnvPath, "examples");
1572
- const examplesEnvSpecificPath = (0, import_path.resolve)(examplesPath, envSpecificFile);
1573
- if ((0, import_fs.existsSync)(examplesEnvSpecificPath)) {
1574
- dotEnvPathFile = examplesEnvSpecificPath;
1575
- }
1576
- }
1577
- if (!dotEnvPathFile) {
1578
- dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, dotEnvFile);
1579
- if (!(0, import_fs.existsSync)(dotEnvPathFile)) {
1580
- if (!this.get("dotEnvPath")) {
1581
- const examplesPath = (0, import_path.resolve)(dotEnvPath, "examples");
1582
- const examplesEnvFile = (0, import_path.resolve)(examplesPath, dotEnvFile);
1583
- if ((0, import_fs.existsSync)(examplesEnvFile)) {
1584
- dotEnvPathFile = examplesEnvFile;
1585
- } else {
1586
- dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, "..", dotEnvFile);
1587
- }
1588
- }
1568
+ const examplesEnvFile = (0, import_path.resolve)(examplesPath, dotEnvFile);
1569
+ if ((0, import_fs.existsSync)(examplesEnvFile)) {
1570
+ dotEnvPathFile = examplesEnvFile;
1571
+ } else {
1572
+ dotEnvPathFile = (0, import_path.resolve)(dotEnvPath, "..", dotEnvFile);
1589
1573
  }
1590
1574
  }
1591
1575
  if (dotEnvPathFile && (0, import_fs.existsSync)(dotEnvPathFile)) {
1592
1576
  (0, import_dotenv.config)({ path: dotEnvPathFile, quiet: true });
1593
1577
  }
1578
+ const envSpecificFile = `.env.${this.env}`;
1579
+ const envSpecificPath = (0, import_path.resolve)(dotEnvPath, envSpecificFile);
1580
+ if ((0, import_fs.existsSync)(envSpecificPath) && envSpecificPath !== dotEnvPathFile) {
1581
+ (0, import_dotenv.config)({ path: envSpecificPath, quiet: true });
1582
+ } else if (!this.get("dotEnvPath")) {
1583
+ const examplesOverlay = (0, import_path.resolve)(dotEnvPath, "examples", envSpecificFile);
1584
+ if ((0, import_fs.existsSync)(examplesOverlay)) {
1585
+ (0, import_dotenv.config)({ path: examplesOverlay, quiet: true });
1586
+ }
1587
+ }
1594
1588
  }
1595
1589
  /**
1596
1590
  * Load configuration files
@@ -5947,10 +5941,9 @@ function buildEcosystemConfig(service, paths) {
5947
5941
  const outLog = (0, import_node_path8.join)(paths.logs, `${pm2.appName}.out.log`);
5948
5942
  const errLog = (0, import_node_path8.join)(paths.logs, `${pm2.appName}.err.log`);
5949
5943
  return `/**
5950
- * pm2 ecosystem for ${service.name} \u2014 seeded by cli-toolkit deploy (init).
5951
- *
5952
- * cwd points at the \`current\` symlink (created on first deploy).
5953
- * Tweak \`args\` here, then: pm2 reload ${paths.ecosystem} --update-env
5944
+ * pm2 ecosystem for ${service.name} \u2014 written by cli-toolkit deploy from the
5945
+ * service manifest (init/deploy). Do not hand-edit; change pm2.* in the
5946
+ * manifest and redeploy. cwd \u2192 \`current\` symlink.
5954
5947
  */
5955
5948
  module.exports = {
5956
5949
  apps: [
@@ -5958,7 +5951,7 @@ module.exports = {
5958
5951
  name: "${pm2.appName}",
5959
5952
  script: "${pm2.script}",
5960
5953
  cwd: "${paths.current}",
5961
- args: "${pm2.args}",
5954
+ args: "${pm2.args ?? ""}",
5962
5955
  instances: 1,
5963
5956
  exec_mode: "fork",
5964
5957
  autorestart: true,
@@ -5971,6 +5964,8 @@ module.exports = {
5971
5964
  merge_logs: true,
5972
5965
  time: true,
5973
5966
  env: {
5967
+ // cli-toolkit Args: ENV selects env-suffixed defaults (DB_NAME_PRODUCTION, BUCKET_PRODUCTION, \u2026)
5968
+ ENV: "production",
5974
5969
  NODE_ENV: "production",
5975
5970
  },
5976
5971
  },
@@ -5995,17 +5990,17 @@ async function initServiceStructure(service, options = {}) {
5995
5990
  if (!dryRun) await (0, import_promises11.mkdir)(dir, { recursive: true });
5996
5991
  created.push(dir);
5997
5992
  }
5998
- let ecosystemCreated = false;
5999
- if (await pathExists4(paths.ecosystem)) {
6000
- skipped.push(paths.ecosystem);
5993
+ const ecosystemExisted = await pathExists4(paths.ecosystem);
5994
+ if (!dryRun) {
5995
+ await (0, import_promises11.writeFile)(paths.ecosystem, buildEcosystemConfig(service, paths), { mode: 420 });
5996
+ }
5997
+ if (ecosystemExisted) {
5998
+ logger.info(`synced ${paths.ecosystem} from manifest`);
6001
5999
  } else {
6002
- if (!dryRun) {
6003
- await (0, import_promises11.writeFile)(paths.ecosystem, buildEcosystemConfig(service, paths), { mode: 420 });
6004
- }
6005
- ecosystemCreated = true;
6006
6000
  created.push(paths.ecosystem);
6001
+ logger.info(`seeded ${paths.ecosystem}`);
6007
6002
  }
6008
- const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] init-structure service=${service.name} dryRun=${dryRun} created=${created.length} skipped=${skipped.length}
6003
+ const line = `[${(/* @__PURE__ */ new Date()).toISOString()}] init-structure service=${service.name} dryRun=${dryRun} created=${created.length} skipped=${skipped.length} ecosystem=synced
6009
6004
  `;
6010
6005
  if (!dryRun) {
6011
6006
  await (0, import_promises11.mkdir)(paths.logs, { recursive: true });
@@ -6014,8 +6009,7 @@ async function initServiceStructure(service, options = {}) {
6014
6009
  logger.info(`service=${service.name} appsRoot=${paths.root}`);
6015
6010
  logger.info(`created: ${created.length ? created.join(", ") : "(none)"}`);
6016
6011
  logger.info(`already present: ${skipped.length ? skipped.join(", ") : "(none)"}`);
6017
- if (ecosystemCreated) logger.info(`seeded ${paths.ecosystem}`);
6018
- return { paths, created, skipped, ecosystemCreated };
6012
+ return { paths, created, skipped, ecosystemCreated: !ecosystemExisted, ecosystemSynced: true };
6019
6013
  }
6020
6014
 
6021
6015
  // src/deploy/bootstrap-host.js
@@ -6112,11 +6106,55 @@ async function installLogrotate(service, options = {}) {
6112
6106
  await runShell(`sudo cp '${tmp}' '${conf}'`, { logger });
6113
6107
  logger.info(`logrotate config written: ${conf}`);
6114
6108
  }
6109
+ var OPERATOR_SHELL_BEGIN = "# >>> mlsfarm-operator >>>";
6110
+ var OPERATOR_SHELL_END = "# <<< mlsfarm-operator <<<";
6111
+ async function installOperatorShell(service, options = {}) {
6112
+ const { user = "ubuntu", dryRun = false, logger = console } = options;
6113
+ const appsRoot = service.appsRoot;
6114
+ if (!appsRoot) {
6115
+ logger.warn("installOperatorShell: service.appsRoot missing \u2014 skipped");
6116
+ return;
6117
+ }
6118
+ const home = user === "ubuntu" || user === process.env.USER ? (0, import_node_path9.join)((0, import_node_os2.homedir)(), ".bashrc") : `/home/${user}/.bashrc`;
6119
+ const currentDir = (0, import_node_path9.join)(appsRoot, "current");
6120
+ const block = [
6121
+ OPERATOR_SHELL_BEGIN,
6122
+ "# Managed by cli-toolkit deploy bootstrap \u2014 do not edit by hand.",
6123
+ "export ENV=production",
6124
+ `if [ -d "${currentDir}" ]; then`,
6125
+ ` cd "${currentDir}" || true`,
6126
+ "fi",
6127
+ OPERATOR_SHELL_END,
6128
+ ""
6129
+ ].join("\n");
6130
+ if (dryRun) {
6131
+ logger.info(`[dryRun] would update ${home} (ENV=production, cd ${currentDir})`);
6132
+ return;
6133
+ }
6134
+ let existing = "";
6135
+ if (await pathExists5(home)) {
6136
+ existing = await (0, import_promises12.readFile)(home, "utf8");
6137
+ }
6138
+ const begin = existing.indexOf(OPERATOR_SHELL_BEGIN);
6139
+ const end = existing.indexOf(OPERATOR_SHELL_END);
6140
+ let next;
6141
+ if (begin !== -1 && end !== -1 && end > begin) {
6142
+ const afterEnd = end + OPERATOR_SHELL_END.length;
6143
+ const rest = existing.slice(afterEnd).replace(/^\n/, "");
6144
+ next = `${existing.slice(0, begin).replace(/\s*$/, "\n")}${block}${rest}`;
6145
+ } else {
6146
+ next = `${existing.replace(/\s*$/, "\n")}
6147
+ ${block}`;
6148
+ }
6149
+ await (0, import_promises12.writeFile)(home, next, { mode: 420 });
6150
+ logger.info(`operator shell: ${home} \u2192 ENV=production, cd ${currentDir}`);
6151
+ }
6115
6152
  async function bootstrapHost(service, options = {}) {
6116
6153
  const { deployKey = service.deployKey, user = "ubuntu", dryRun = false, logger = console } = options;
6117
6154
  await ensurePm2Startup({ user, dryRun, logger });
6118
6155
  if (deployKey) await installDeployKey(deployKey, { dryRun, logger });
6119
6156
  await installLogrotate(service, { dryRun, logger });
6157
+ await installOperatorShell(service, { user, dryRun, logger });
6120
6158
  logger.info("bootstrap-host complete");
6121
6159
  }
6122
6160
 
@@ -8855,6 +8893,7 @@ var TasksManager = class _TasksManager {
8855
8893
  h,
8856
8894
  initServiceStructure,
8857
8895
  installDeps,
8896
+ installOperatorShell,
8858
8897
  ipcFileLogsTableNameForSourceResource,
8859
8898
  joiEdateType,
8860
8899
  joiStringArrayType,