@nocobase/app-installer 0.0.0-stage → 0.1.0-beta.4

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.
Files changed (61) hide show
  1. package/README.md +199 -2
  2. package/bin/run.js +43 -0
  3. package/dist/cli.js +168 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/commands/install.js +319 -0
  6. package/dist/commands/install.js.map +1 -0
  7. package/dist/commands/rollback.js +284 -0
  8. package/dist/commands/rollback.js.map +1 -0
  9. package/dist/commands/status.js +132 -0
  10. package/dist/commands/status.js.map +1 -0
  11. package/dist/commands/upgrade.js +549 -0
  12. package/dist/commands/upgrade.js.map +1 -0
  13. package/dist/lib/app-cli.js +89 -0
  14. package/dist/lib/app-cli.js.map +1 -0
  15. package/dist/lib/backup.js +167 -0
  16. package/dist/lib/backup.js.map +1 -0
  17. package/dist/lib/confirm.js +36 -0
  18. package/dist/lib/confirm.js.map +1 -0
  19. package/dist/lib/current-link.js +22 -0
  20. package/dist/lib/current-link.js.map +1 -0
  21. package/dist/lib/ecosystem.js +82 -0
  22. package/dist/lib/ecosystem.js.map +1 -0
  23. package/dist/lib/env-file.js +87 -0
  24. package/dist/lib/env-file.js.map +1 -0
  25. package/dist/lib/errors.js +32 -0
  26. package/dist/lib/errors.js.map +1 -0
  27. package/dist/lib/health.js +55 -0
  28. package/dist/lib/health.js.map +1 -0
  29. package/dist/lib/initial-admin.js +39 -0
  30. package/dist/lib/initial-admin.js.map +1 -0
  31. package/dist/lib/interrupt.js +50 -0
  32. package/dist/lib/interrupt.js.map +1 -0
  33. package/dist/lib/invocation.js +33 -0
  34. package/dist/lib/invocation.js.map +1 -0
  35. package/dist/lib/layout.js +48 -0
  36. package/dist/lib/layout.js.map +1 -0
  37. package/dist/lib/lock.js +59 -0
  38. package/dist/lib/lock.js.map +1 -0
  39. package/dist/lib/mount-path.js +34 -0
  40. package/dist/lib/mount-path.js.map +1 -0
  41. package/dist/lib/output.js +61 -0
  42. package/dist/lib/output.js.map +1 -0
  43. package/dist/lib/pm2.js +92 -0
  44. package/dist/lib/pm2.js.map +1 -0
  45. package/dist/lib/prechecks.js +199 -0
  46. package/dist/lib/prechecks.js.map +1 -0
  47. package/dist/lib/registry.js +9 -0
  48. package/dist/lib/registry.js.map +1 -0
  49. package/dist/lib/release.js +173 -0
  50. package/dist/lib/release.js.map +1 -0
  51. package/dist/lib/run-command.js +108 -0
  52. package/dist/lib/run-command.js.map +1 -0
  53. package/dist/lib/service.js +100 -0
  54. package/dist/lib/service.js.map +1 -0
  55. package/dist/lib/source.js +44 -0
  56. package/dist/lib/source.js.map +1 -0
  57. package/dist/lib/state.js +84 -0
  58. package/dist/lib/state.js.map +1 -0
  59. package/dist/lib/version.js +51 -0
  60. package/dist/lib/version.js.map +1 -0
  61. package/package.json +51 -3
@@ -0,0 +1,33 @@
1
+ import { formatCommandLine } from '@nocobase/cli-envelope';
2
+ import { createRequire } from 'node:module';
3
+ import { defaultRegistry, normalizeRegistry } from "./registry.js";
4
+ export const INSTALLER_PACKAGE = '@nocobase/app-installer';
5
+ /** This installer's own version; `src/lib` and `dist/lib` both sit two levels below the package root. */
6
+ export const INSTALLER_VERSION = createRequire(import.meta.url)('../../package.json').version;
7
+ /**
8
+ * An app-installer command as a suggestion's `run` names it: through npx, because nothing installs an `app-installer`
9
+ * binary on PATH, and with the registry named, because NocoBase 3 packages are not on the public npm registry. `--yes`
10
+ * answers npx's own install prompt, which would otherwise block an agent. The version is pinned to this installer's, so
11
+ * a recovery runs the same code that wrote the state it recovers from.
12
+ */
13
+ export function installerCommandLine(args, options = {}) {
14
+ const registry = normalizeRegistry(options.registry ?? defaultRegistry());
15
+ const version = options.version ?? INSTALLER_VERSION;
16
+ return {
17
+ command: 'npx',
18
+ args: [
19
+ '--yes',
20
+ `--registry=${registry}`,
21
+ `${INSTALLER_PACKAGE}@${version}`,
22
+ ...args,
23
+ ],
24
+ };
25
+ }
26
+ /**
27
+ * The same command written into prose or help, where it is read rather than run: `args` is inserted as given, so it may
28
+ * hold a placeholder such as `<the copied archive>`, and anything a shell would split must already be quoted.
29
+ */
30
+ export function installerCommand(args, options = {}) {
31
+ return `${formatCommandLine(installerCommandLine([], options))} ${args}`;
32
+ }
33
+ //# sourceMappingURL=invocation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"invocation.js","sourceRoot":"","sources":["../../src/lib/invocation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAoB,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,eAAe,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAEnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,yBAAyB,CAAC;AAE3D,yGAAyG;AACzG,MAAM,CAAC,MAAM,iBAAiB,GAC5B,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,oBAAoB,CACpD,CAAC,OAAO,CAAC;AASV;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,IAAuB,EACvB,UAAmC,EAAE;IAErC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,QAAQ,IAAI,eAAe,EAAE,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,iBAAiB,CAAC;IACrD,OAAO;QACL,OAAO,EAAE,KAAK;QACd,IAAI,EAAE;YACJ,OAAO;YACP,cAAc,QAAQ,EAAE;YACxB,GAAG,iBAAiB,IAAI,OAAO,EAAE;YACjC,GAAG,IAAI;SACR;KACF,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,IAAY,EACZ,UAAmC,EAAE;IAErC,OAAO,GAAG,iBAAiB,CAAC,oBAAoB,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC,IAAI,IAAI,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,48 @@
1
+ import path from 'node:path';
2
+ /** The directory inside `releases/<id>/` holding a release's deployment root: `dist/` and `config.example.yml`. */
3
+ export const RELEASE_APP_DIR = 'app';
4
+ export function layoutOf(root) {
5
+ return {
6
+ root,
7
+ appEnv: path.join(root, 'app.env'),
8
+ configFile: path.join(root, 'config.yml'),
9
+ storageDir: path.join(root, 'storage'),
10
+ logsDir: path.join(root, 'logs'),
11
+ outLog: path.join(root, 'logs/app.out.log'),
12
+ errorLog: path.join(root, 'logs/app.err.log'),
13
+ releasesDir: path.join(root, 'releases'),
14
+ backupsDir: path.join(root, 'backups'),
15
+ current: path.join(root, 'current'),
16
+ stateFile: path.join(root, 'installer.json'),
17
+ lockFile: path.join(root, '.installer.lock'),
18
+ ecosystemFile: path.join(root, 'ecosystem.config.cjs'),
19
+ launcherFile: path.join(root, 'launcher.mjs'),
20
+ };
21
+ }
22
+ /**
23
+ * A release's identity: its version and when it was built, as in `1.2.0_20260927T005500Z`. Two builds of the same
24
+ * version are two releases, so a project that deploys without bumping its version still gets a release of its own
25
+ * each time. Neither `-`, which would make the id read as a semantic-version prerelease, nor `+`, which is awkward in a
26
+ * directory name and a shell command, separates the parts.
27
+ */
28
+ export function releaseId(version, builtAt) {
29
+ const compact = new Date(builtAt)
30
+ .toISOString()
31
+ .replace(/\.\d+Z$/u, 'Z')
32
+ .replaceAll('-', '')
33
+ .replaceAll(':', '');
34
+ return `${version}_${compact}`;
35
+ }
36
+ /** A release's deployment root: the directory holding `dist/` and `config.example.yml`. */
37
+ export function releaseDir(layout, id) {
38
+ return path.join(layout.releasesDir, id, RELEASE_APP_DIR);
39
+ }
40
+ /** The path `current` points at, relative to the root so the whole root can be moved. */
41
+ export function releaseLinkTarget(id) {
42
+ return path.join('releases', id, RELEASE_APP_DIR);
43
+ }
44
+ /** Where an archive is unpacked before its manifest names the release it is. */
45
+ export function stagingDir(layout) {
46
+ return path.join(layout.releasesDir, `.staging-${process.pid}`);
47
+ }
48
+ //# sourceMappingURL=layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout.js","sourceRoot":"","sources":["../../src/lib/layout.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,mHAAmH;AACnH,MAAM,CAAC,MAAM,eAAe,GAAG,KAAK,CAAC;AAuBrC,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;QAClC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,YAAY,CAAC;QACzC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;QACtC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;QAChC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC;QAC3C,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC;QAC7C,WAAW,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC;QACxC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;QACtC,OAAO,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC;QACnC,SAAS,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,gBAAgB,CAAC;QAC5C,QAAQ,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,iBAAiB,CAAC;QAC5C,aAAa,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,sBAAsB,CAAC;QACtD,YAAY,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,cAAc,CAAC;KAC9C,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,OAAe;IACxD,MAAM,OAAO,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC;SAC9B,WAAW,EAAE;SACb,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC;SACxB,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC;SACnB,UAAU,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;IACvB,OAAO,GAAG,OAAO,IAAI,OAAO,EAAE,CAAC;AACjC,CAAC;AAED,2FAA2F;AAC3F,MAAM,UAAU,UAAU,CAAC,MAAc,EAAE,EAAU;IACnD,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,EAAE,eAAe,CAAC,CAAC;AAC5D,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,iBAAiB,CAAC,EAAU;IAC1C,OAAO,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,EAAE,EAAE,eAAe,CAAC,CAAC;AACpD,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,UAAU,CAAC,MAAc;IACvC,OAAO,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,YAAY,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;AAClE,CAAC"}
@@ -0,0 +1,59 @@
1
+ import { link, readFile, rm, writeFile } from 'node:fs/promises';
2
+ import { EXIT_INVALID, InstallerError } from "./errors.js";
3
+ function isAlive(pid) {
4
+ try {
5
+ process.kill(pid, 0);
6
+ return true;
7
+ }
8
+ catch (error) {
9
+ // EPERM means the process exists but belongs to someone else.
10
+ return error.code === 'EPERM';
11
+ }
12
+ }
13
+ /**
14
+ * Takes the root's lock so two installers never run against the same installation. The file holds the owner's pid; a lock whose
15
+ * owner is gone is stale and is taken over rather than blocking every later run.
16
+ *
17
+ * The pid is written to a private file first and linked into place, so the lock never exists without its owner in it:
18
+ * with `open('wx')` followed by a write, a second installer could read the still-empty file, take it for stale, and
19
+ * proceed alongside the first.
20
+ */
21
+ export async function acquireLock(file) {
22
+ const candidate = `${file}.${process.pid}`;
23
+ await writeFile(candidate, `${process.pid}\n`);
24
+ try {
25
+ for (let attempt = 0; attempt < 2; attempt += 1) {
26
+ try {
27
+ await link(candidate, file);
28
+ return async () => {
29
+ await rm(file, { force: true });
30
+ };
31
+ }
32
+ catch (error) {
33
+ if (error.code !== 'EEXIST')
34
+ throw error;
35
+ const owner = Number.parseInt((await readFile(file, 'utf8').catch(() => '')).trim(), 10);
36
+ if (Number.isInteger(owner) &&
37
+ owner !== process.pid &&
38
+ isAlive(owner)) {
39
+ throw new InstallerError('LOCKED', `Another app-installer (pid ${owner}) is working on this installation.`, {
40
+ exitCode: EXIT_INVALID,
41
+ suggestions: [
42
+ {
43
+ message: 'Wait for it to finish, then run the command again.',
44
+ },
45
+ ],
46
+ });
47
+ }
48
+ await rm(file, { force: true });
49
+ }
50
+ }
51
+ throw new InstallerError('LOCKED', `Could not take the lock at ${file}.`, {
52
+ exitCode: EXIT_INVALID,
53
+ });
54
+ }
55
+ finally {
56
+ await rm(candidate, { force: true });
57
+ }
58
+ }
59
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/lib/lock.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACjE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAI3D,SAAS,OAAO,CAAC,GAAW;IAC1B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,8DAA8D;QAC9D,OAAQ,KAA+B,CAAC,IAAI,KAAK,OAAO,CAAC;IAC3D,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3C,MAAM,SAAS,CAAC,SAAS,EAAE,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC;IAC/C,IAAI,CAAC;QACH,KAAK,IAAI,OAAO,GAAG,CAAC,EAAE,OAAO,GAAG,CAAC,EAAE,OAAO,IAAI,CAAC,EAAE,CAAC;YAChD,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;gBAC5B,OAAO,KAAK,IAAI,EAAE;oBAChB,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;gBAClC,CAAC,CAAC;YACJ,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;oBAAE,MAAM,KAAK,CAAC;gBACpE,MAAM,KAAK,GAAG,MAAM,CAAC,QAAQ,CAC3B,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,EACrD,EAAE,CACH,CAAC;gBACF,IACE,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;oBACvB,KAAK,KAAK,OAAO,CAAC,GAAG;oBACrB,OAAO,CAAC,KAAK,CAAC,EACd,CAAC;oBACD,MAAM,IAAI,cAAc,CACtB,QAAQ,EACR,8BAA8B,KAAK,oCAAoC,EACvE;wBACE,QAAQ,EAAE,YAAY;wBACtB,WAAW,EAAE;4BACX;gCACE,OAAO,EAAE,oDAAoD;6BAC9D;yBACF;qBACF,CACF,CAAC;gBACJ,CAAC;gBACD,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;YAClC,CAAC;QACH,CAAC;QACD,MAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,8BAA8B,IAAI,GAAG,EAAE;YACxE,QAAQ,EAAE,YAAY;SACvB,CAAC,CAAC;IACL,CAAC;YAAS,CAAC;QACT,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACvC,CAAC;AACH,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { EXIT_INVALID, InstallerError } from "./errors.js";
2
+ import { rebuildCommandLine } from "./prechecks.js";
3
+ /** A mount path as the server spells it: `/crm`, `/apps/crm`, or `''` for the origin root. */
4
+ export function normalizeMountPath(value) {
5
+ const trimmed = value.trim().replace(/^\/+|\/+$/gu, '');
6
+ return trimmed ? `/${trimmed}` : '';
7
+ }
8
+ /** `--base-path`, checked and normalized; `undefined` when it was not given. */
9
+ export function parseBasePathFlag(value) {
10
+ if (value === undefined)
11
+ return undefined;
12
+ if (/[\s?#\\]/u.test(value) || value.split('/').includes('..')) {
13
+ throw new InstallerError('INVALID_USAGE', `--base-path ${JSON.stringify(value)} is not a URL path; give one such as /crm, or / for the origin root.`, { exitCode: EXIT_INVALID });
14
+ }
15
+ return normalizeMountPath(value);
16
+ }
17
+ /**
18
+ * A release built before relocatable builds has its mount path compiled into its client, and serves pages whose asset
19
+ * URLs all miss anywhere else. It runs only where it was built for.
20
+ */
21
+ export function assertFixedMountPath(subject, builtFor, mountedAt) {
22
+ if (normalizeMountPath(builtFor) === normalizeMountPath(mountedAt))
23
+ return;
24
+ throw new InstallerError('BASE_PATH_MISMATCH', `${subject} was built for the base path ${normalizeMountPath(builtFor) || '/'} and cannot be mounted at ${normalizeMountPath(mountedAt) || '/'}.`, {
25
+ exitCode: EXIT_INVALID,
26
+ suggestions: [
27
+ {
28
+ message: 'Upgrade @nocobase/app-cli in the application project and build the archive again; a current build runs at any path:',
29
+ run: rebuildCommandLine(),
30
+ },
31
+ ],
32
+ });
33
+ }
34
+ //# sourceMappingURL=mount-path.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mount-path.js","sourceRoot":"","sources":["../../src/lib/mount-path.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAEpD,8FAA8F;AAC9F,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,aAAa,EAAE,EAAE,CAAC,CAAC;IACxD,OAAO,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;AACtC,CAAC;AAED,gFAAgF;AAChF,MAAM,UAAU,iBAAiB,CAC/B,KAAyB;IAEzB,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC1C,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,MAAM,IAAI,cAAc,CACtB,eAAe,EACf,eAAe,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,sEAAsE,EAC1G,EAAE,QAAQ,EAAE,YAAY,EAAE,CAC3B,CAAC;IACJ,CAAC;IACD,OAAO,kBAAkB,CAAC,KAAK,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAAe,EACf,QAAgB,EAChB,SAAiB;IAEjB,IAAI,kBAAkB,CAAC,QAAQ,CAAC,KAAK,kBAAkB,CAAC,SAAS,CAAC;QAAE,OAAO;IAC3E,MAAM,IAAI,cAAc,CACtB,oBAAoB,EACpB,GAAG,OAAO,gCAAgC,kBAAkB,CAAC,QAAQ,CAAC,IAAI,GAAG,6BAA6B,kBAAkB,CAAC,SAAS,CAAC,IAAI,GAAG,GAAG,EACjJ;QACE,QAAQ,EAAE,YAAY;QACtB,WAAW,EAAE;YACX;gBACE,OAAO,EACL,qHAAqH;gBACvH,GAAG,EAAE,kBAAkB,EAAE;aAC1B;SACF;KACF,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,61 @@
1
+ import { commandFailureJson, commandSuccessJson, formatCommandLine, } from '@nocobase/cli-envelope';
2
+ import { EXIT_FAILED, isInstallerError } from "./errors.js";
3
+ export function createReporter(json, stderr = process.stderr) {
4
+ const warnings = [];
5
+ return {
6
+ json,
7
+ warnings,
8
+ progress(message) {
9
+ stderr.write(`${message}\n`);
10
+ },
11
+ warn(message) {
12
+ warnings.push(message);
13
+ stderr.write(`Warning: ${message}\n`);
14
+ },
15
+ };
16
+ }
17
+ export function successEnvelope(command, result, warnings, status = 'success') {
18
+ return commandSuccessJson(command, status, result, warnings);
19
+ }
20
+ /** An `InstallerError` as it was raised; anything else as `UNEXPECTED`, with only its message. */
21
+ export function errorEnvelope(command, error, warnings) {
22
+ if (isInstallerError(error)) {
23
+ return commandFailureJson(command, {
24
+ code: error.code,
25
+ message: error.message,
26
+ suggestions: error.suggestions,
27
+ details: error.details,
28
+ }, warnings);
29
+ }
30
+ return commandFailureJson(command, {
31
+ code: 'UNEXPECTED',
32
+ message: error instanceof Error ? error.message : String(error),
33
+ suggestions: [],
34
+ }, warnings);
35
+ }
36
+ export function exitCodeOf(error) {
37
+ return isInstallerError(error) ? error.exitCode : EXIT_FAILED;
38
+ }
39
+ /** Renders an error for a person: the message, then each suggestion with the command to run under it. */
40
+ export function formatError(error) {
41
+ const envelope = errorEnvelope('', error, []);
42
+ if (envelope.ok)
43
+ return '';
44
+ const lines = [`Error: ${envelope.error.message}`];
45
+ // A failed step's own output is usually the actual reason; show its tail rather than only the step name. Only an
46
+ // InstallerError carries details, and it holds them as the record they were raised with.
47
+ const details = isInstallerError(error) ? error.details : undefined;
48
+ for (const key of ['output', 'log']) {
49
+ const text = details?.[key];
50
+ if (typeof text === 'string' && text.trim() !== '') {
51
+ lines.push(...text.split('\n').map((line) => ` | ${line}`));
52
+ }
53
+ }
54
+ for (const suggestion of envelope.error.suggestions) {
55
+ lines.push(` ${suggestion.message}`);
56
+ if (suggestion.run)
57
+ lines.push(` ${formatCommandLine(suggestion.run)}`);
58
+ }
59
+ return lines.join('\n');
60
+ }
61
+ //# sourceMappingURL=output.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,GAElB,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAiB5D,MAAM,UAAU,cAAc,CAC5B,IAAa,EACb,SAAgC,OAAO,CAAC,MAAM;IAE9C,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,OAAO;QACL,IAAI;QACJ,QAAQ;QACR,QAAQ,CAAC,OAAO;YACd,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,OAAO;YACV,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YACvB,MAAM,CAAC,KAAK,CAAC,YAAY,OAAO,IAAI,CAAC,CAAC;QACxC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,eAAe,CAC7B,OAAe,EACf,MAAe,EACf,QAAkB,EAClB,SAAqC,SAAS;IAE9C,OAAO,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,aAAa,CAC3B,OAAe,EACf,KAAc,EACd,QAAkB;IAElB,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,OAAO,kBAAkB,CACvB,OAAO,EACP;YACE,IAAI,EAAE,KAAK,CAAC,IAAI;YAChB,OAAO,EAAE,KAAK,CAAC,OAAO;YACtB,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,OAAO,EAAE,KAAK,CAAC,OAAO;SACvB,EACD,QAAQ,CACT,CAAC;IACJ,CAAC;IACD,OAAO,kBAAkB,CACvB,OAAO,EACP;QACE,IAAI,EAAE,YAAY;QAClB,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/D,WAAW,EAAE,EAAE;KAChB,EACD,QAAQ,CACT,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,KAAc;IACvC,OAAO,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,WAAW,CAAC;AAChE,CAAC;AAED,yGAAyG;AACzG,MAAM,UAAU,WAAW,CAAC,KAAc;IACxC,MAAM,QAAQ,GAAG,aAAa,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;IAC9C,IAAI,QAAQ,CAAC,EAAE;QAAE,OAAO,EAAE,CAAC;IAC3B,MAAM,KAAK,GAAG,CAAC,UAAU,QAAQ,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACnD,iHAAiH;IACjH,yFAAyF;IACzF,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC;IACpE,KAAK,MAAM,GAAG,IAAI,CAAC,QAAQ,EAAE,KAAK,CAAU,EAAE,CAAC;QAC7C,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;YACnD,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC;QAC/D,CAAC;IACH,CAAC;IACD,KAAK,MAAM,UAAU,IAAI,QAAQ,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACpD,KAAK,CAAC,IAAI,CAAC,KAAK,UAAU,CAAC,OAAO,EAAE,CAAC,CAAC;QACtC,IAAI,UAAU,CAAC,GAAG;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,iBAAiB,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC7E,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,92 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { CommandFailedError, runCommand, } from "./run-command.js";
5
+ /** pm2 prints daemon notices such as "[PM2] Spawning PM2 daemon" before the JSON; the array is the last line that parses. */
6
+ export function parseJlist(stdout) {
7
+ const lines = stdout.trim().split('\n').reverse();
8
+ for (const line of lines) {
9
+ if (!line.startsWith('['))
10
+ continue;
11
+ try {
12
+ const parsed = JSON.parse(line);
13
+ return parsed.map((entry) => ({
14
+ name: entry.name,
15
+ pid: entry.pid,
16
+ status: entry.pm2_env?.status ?? 'unknown',
17
+ restarts: entry.pm2_env?.restart_time ?? 0,
18
+ ...(entry.pm2_env?.pm_cwd ? { cwd: entry.pm2_env.pm_cwd } : {}),
19
+ }));
20
+ }
21
+ catch {
22
+ // A notice line that happens to start with "[".
23
+ }
24
+ }
25
+ return [];
26
+ }
27
+ /**
28
+ * Whether pm2's daemon is running, read from its pid file. Any pm2 command, `jlist` included, starts the daemon when
29
+ * it is not running, so a read-only question has to be answered without asking pm2.
30
+ */
31
+ export function isDaemonRunning(env = process.env) {
32
+ const home = env.PM2_HOME || path.join(os.homedir(), '.pm2');
33
+ let pid;
34
+ try {
35
+ pid = Number.parseInt(readFileSync(path.join(home, 'pm2.pid'), 'utf8'), 10);
36
+ }
37
+ catch {
38
+ return false;
39
+ }
40
+ if (!Number.isInteger(pid))
41
+ return false;
42
+ try {
43
+ process.kill(pid, 0);
44
+ return true;
45
+ }
46
+ catch (error) {
47
+ return error.code === 'EPERM';
48
+ }
49
+ }
50
+ export function createPm2(bin = 'pm2', run = runCommand, daemonRunning = isDaemonRunning) {
51
+ return {
52
+ async version() {
53
+ const { stdout } = await run(bin, ['--version']);
54
+ // pm2 may print daemon notices around the version, so take the line that is one.
55
+ const lines = stdout
56
+ .trim()
57
+ .split('\n')
58
+ .map((line) => line.trim());
59
+ return ([...lines].reverse().find((line) => /^\d+\.\d+\.\d+/u.test(line)) ??
60
+ lines.at(-1) ??
61
+ '');
62
+ },
63
+ async start(ecosystemFile, cwd) {
64
+ await run(bin, ['start', ecosystemFile], { cwd });
65
+ },
66
+ async stop(name) {
67
+ await run(bin, ['stop', name]);
68
+ },
69
+ async remove(name) {
70
+ try {
71
+ await run(bin, ['delete', name]);
72
+ }
73
+ catch (error) {
74
+ if (error instanceof CommandFailedError &&
75
+ /not found/iu.test(`${error.stdout}\n${error.stderr}`)) {
76
+ return;
77
+ }
78
+ throw error;
79
+ }
80
+ },
81
+ async save() {
82
+ await run(bin, ['save']);
83
+ },
84
+ async describe(name) {
85
+ if (!daemonRunning())
86
+ return undefined;
87
+ const { stdout } = await run(bin, ['jlist']);
88
+ return parseJlist(stdout).find((entry) => entry.name === name);
89
+ },
90
+ };
91
+ }
92
+ //# sourceMappingURL=pm2.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pm2.js","sourceRoot":"","sources":["../../src/lib/pm2.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,199 @@
1
+ import { readdir } from 'node:fs/promises';
2
+ import { createServer } from 'node:net';
3
+ import { EXIT_INVALID, InstallerError } from "./errors.js";
4
+ import { compareVersions } from "./version.js";
5
+ export const MINIMUM_NODE_MAJOR = 24;
6
+ /**
7
+ * pm2 recognises `ecosystem.config.cjs` as a configuration from 4.3 on; an older one runs the file as the application
8
+ * instead, which exits at once and looks like an application that cannot start.
9
+ */
10
+ export const MINIMUM_PM2_VERSION = '4.3.0';
11
+ export function majorOf(version) {
12
+ const match = version.trim().match(/^v?(\d+)/u);
13
+ return match ? Number.parseInt(match[1], 10) : Number.NaN;
14
+ }
15
+ export function currentNodeMajor() {
16
+ return majorOf(process.versions.node);
17
+ }
18
+ export function checkPlatform(platform = process.platform) {
19
+ if (platform === 'win32') {
20
+ throw new InstallerError('PLATFORM_UNSUPPORTED', 'app-installer does not run on Windows: the release switch relies on symbolic links and atomic renames.', {
21
+ exitCode: EXIT_INVALID,
22
+ suggestions: [{ message: 'Run it inside WSL instead.' }],
23
+ });
24
+ }
25
+ }
26
+ /** The C library Node runs on, which a Linux build's native modules are compiled against. */
27
+ export function currentLibc() {
28
+ if (process.platform !== 'linux')
29
+ return undefined;
30
+ const report = process.report.getReport();
31
+ return report.header?.glibcVersionRuntime ? 'glibc' : 'musl';
32
+ }
33
+ /**
34
+ * This machine as `pnpm build --target` names a platform, such as `linux-x64` or `linux-arm64-musl`, so a suggestion
35
+ * to rebuild for it runs as printed.
36
+ */
37
+ export function machineBuildTarget() {
38
+ return `${process.platform}-${process.arch}${currentLibc() === 'musl' ? '-musl' : ''}`;
39
+ }
40
+ /**
41
+ * The build command that produces an archive this machine runs, run in the application project. A current build is not
42
+ * tied to a mount path, so the command names none.
43
+ */
44
+ export function rebuildCommandLine() {
45
+ return {
46
+ command: 'pnpm',
47
+ args: [
48
+ 'build',
49
+ '--target',
50
+ machineBuildTarget(),
51
+ '--node-version',
52
+ String(currentNodeMajor()),
53
+ '--tar',
54
+ ],
55
+ };
56
+ }
57
+ /**
58
+ * pm2 must be installed globally: `pm2 startup` writes a boot service that names pm2's own path, so a copy fetched
59
+ * through `npx` into a cache would vanish from under it.
60
+ */
61
+ export async function checkPm2(pm2) {
62
+ let version;
63
+ try {
64
+ version = await pm2.version();
65
+ }
66
+ catch {
67
+ throw new InstallerError('PM2_MISSING', 'pm2 was not found on PATH.', {
68
+ exitCode: EXIT_INVALID,
69
+ suggestions: [
70
+ {
71
+ message: 'Install pm2 globally:',
72
+ run: { command: 'npm', args: ['install', '-g', 'pm2'] },
73
+ },
74
+ {
75
+ message: 'Or install without starting the application, with --no-start.',
76
+ },
77
+ ],
78
+ });
79
+ }
80
+ const comparison = compareVersions(version, MINIMUM_PM2_VERSION);
81
+ if (comparison === undefined || comparison < 0) {
82
+ throw new InstallerError('PM2_UNSUPPORTED', comparison === undefined
83
+ ? `pm2 ${MINIMUM_PM2_VERSION} or later is required, and \`pm2 --version\` printed "${version}", which is not a version.`
84
+ : `pm2 ${MINIMUM_PM2_VERSION} or later is required; found ${version}.`, {
85
+ exitCode: EXIT_INVALID,
86
+ suggestions: [
87
+ {
88
+ message: 'Update pm2 globally:',
89
+ run: { command: 'npm', args: ['install', '-g', 'pm2@latest'] },
90
+ },
91
+ {
92
+ message: 'Then swap the running daemon for the new version:',
93
+ run: { command: 'pm2', args: ['update'] },
94
+ },
95
+ ],
96
+ });
97
+ }
98
+ return version;
99
+ }
100
+ /**
101
+ * The pm2 process name must be free. `pm2 start` on a name that is already registered does not start a second process:
102
+ * it restarts the existing one — its own script and arguments — with the new environment, so another application on this
103
+ * machine would come back serving this one's configuration and database.
104
+ */
105
+ export async function checkPm2NameFree(pm2, name) {
106
+ const existing = await pm2.describe(name);
107
+ if (existing) {
108
+ throw new InstallerError('PM2_NAME_IN_USE', `pm2 already runs a process named ${name}${existing.cwd ? ` from ${existing.cwd}` : ''}.`, {
109
+ exitCode: EXIT_INVALID,
110
+ suggestions: [
111
+ {
112
+ message: `Give this installation its own process name with --name, such as --name ${name}-2.`,
113
+ },
114
+ ],
115
+ });
116
+ }
117
+ }
118
+ function tryListen(host, port) {
119
+ return new Promise((resolve) => {
120
+ const server = createServer();
121
+ server.once('error', (error) => resolve(error));
122
+ server.listen(port, host, () => server.close(() => resolve(undefined)));
123
+ });
124
+ }
125
+ /** The first port above `after` that can be listened on right now, for a suggestion; `undefined` if none is near. */
126
+ export async function nextFreePort(host, after, attempts = 50) {
127
+ for (let port = after + 1; port <= Math.min(after + attempts, 65535); port += 1) {
128
+ if (!(await tryListen(host, port)))
129
+ return port;
130
+ }
131
+ return undefined;
132
+ }
133
+ /**
134
+ * The application's port must be free. Otherwise whatever already listens there answers the health check, and the
135
+ * install reports success while its own process crash-loops on EADDRINUSE. With `suggestPort`, as for an install that
136
+ * chooses its port, the error names a free one; each installation on a machine needs its own.
137
+ */
138
+ export async function checkPortFree(host, port, options = {}) {
139
+ const error = await tryListen(host, port);
140
+ if (!error)
141
+ return;
142
+ const free = options.suggestPort && error.code === 'EADDRINUSE'
143
+ ? await nextFreePort(host, port)
144
+ : undefined;
145
+ throw new InstallerError('PORT_IN_USE', error.code === 'EADDRINUSE'
146
+ ? `${host}:${port} is already in use.`
147
+ : `Cannot listen on ${host}:${port}: ${error.message}`, {
148
+ exitCode: EXIT_INVALID,
149
+ details: free === undefined ? undefined : { freePort: free },
150
+ suggestions: [
151
+ options.suggestPort
152
+ ? {
153
+ message: free === undefined
154
+ ? 'Choose another port with --port.'
155
+ : `Port ${free} is free: pass --port ${free}, and point the reverse proxy at it.`,
156
+ }
157
+ : { message: 'Stop what holds the port, then run this again.' },
158
+ ],
159
+ });
160
+ }
161
+ /** `--set-from-env` names variables; a typo should fail here, not after the build. */
162
+ export function checkEnvVariables(variables, env = process.env) {
163
+ const missing = variables.filter((name) => env[name] === undefined);
164
+ if (missing.length > 0) {
165
+ throw new InstallerError('ENV_MISSING', `--set-from-env names ${missing.length === 1 ? 'a variable that is' : 'variables that are'} not set: ${missing.join(', ')}.`, { exitCode: EXIT_INVALID });
166
+ }
167
+ }
168
+ /** The target must be new or empty; the installer never overwrites files it did not write. */
169
+ export async function checkTargetEmpty(root) {
170
+ let entries;
171
+ try {
172
+ entries = await readdir(root);
173
+ }
174
+ catch (error) {
175
+ const code = error.code;
176
+ if (code === 'ENOENT')
177
+ return false;
178
+ if (code === 'ENOTDIR') {
179
+ throw new InstallerError('TARGET_NOT_EMPTY', `${root} is a file.`, {
180
+ exitCode: EXIT_INVALID,
181
+ suggestions: [{ message: 'Install into a new or empty directory.' }],
182
+ });
183
+ }
184
+ throw error;
185
+ }
186
+ if (entries.length > 0) {
187
+ throw new InstallerError('TARGET_NOT_EMPTY', `${root} is not empty.`, {
188
+ exitCode: EXIT_INVALID,
189
+ suggestions: [
190
+ { message: 'Install into a new or empty directory.' },
191
+ {
192
+ message: 'To manage an application this installer already set up there, use status, upgrade or rollback instead.',
193
+ },
194
+ ],
195
+ });
196
+ }
197
+ return true;
198
+ }
199
+ //# sourceMappingURL=prechecks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prechecks.js","sourceRoot":"","sources":["../../src/lib/prechecks.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /** The public npm registry is the default; `--registry` and `NOCOBASE_REGISTRY` may select a private registry. */
2
+ export const FALLBACK_REGISTRY = 'https://registry.npmjs.org';
3
+ export function defaultRegistry(env = process.env) {
4
+ return env.NOCOBASE_REGISTRY?.trim() || FALLBACK_REGISTRY;
5
+ }
6
+ export function normalizeRegistry(registry) {
7
+ return registry.replace(/\/+$/u, '');
8
+ }
9
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/lib/registry.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,MAAM,CAAC,MAAM,iBAAiB,GAAG,4BAA4B,CAAC;AAE9D,MAAM,UAAU,eAAe,CAAC,MAAyB,OAAO,CAAC,GAAG;IAClE,OAAO,GAAG,CAAC,iBAAiB,EAAE,IAAI,EAAE,IAAI,iBAAiB,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,QAAgB;IAChD,OAAO,QAAQ,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;AACvC,CAAC"}