@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 @@
1
+ {"version":3,"file":"app-cli.js","sourceRoot":"","sources":["../../src/lib/app-cli.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAmB,MAAM,aAAa,CAAC;AAC9D,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EACL,kBAAkB,EAClB,UAAU,EACV,IAAI,GAEL,MAAM,kBAAkB,CAAC;AAkB1B;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAC1B,KAES;IAET,MAAM,GAAG,GACP,OAAO,KAAK,CAAC,GAAG,KAAK,QAAQ;QAC3B,CAAC,CAAC,KAAK,CAAC,GAAG;QACX,CAAC,CAAC,KAAK,CAAC,GAAG;YACT,CAAC,CAAC,iBAAiB,CAAC,KAAK,CAAC,GAAG,CAAC;YAC9B,CAAC,CAAC,SAAS,CAAC;IAClB,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,IAAI,EAAE,CAAC;IACpC,OAAO;QACL,OAAO,EAAE,GAAG;YACV,CAAC,CAAC,CAAC,OAAO,EAAE,mCAAmC,GAAG,GAAG,CAAC;iBACjD,MAAM,CAAC,OAAO,CAAC;iBACf,IAAI,CAAC,GAAG,CAAC;YACd,CAAC,CAAC,OAAO;KACZ,CAAC;AACJ,CAAC;AAYD;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAuB;IACxD,MAAM,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC;IAC1D,IAAI,OAAO,GAAG,KAAK,CAAC;IACpB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7B,IAAI,GAAG,KAAK,YAAY;YAAE,OAAO,GAAG,IAAI,CAAC;QACzC,IAAI,CAAC,OAAO,IAAI,OAAO,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QAC3D,MAAM,KAAK,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC/B,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;IAC3D,CAAC,CAAC,CAAC;IACH,OAAO,YAAY,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;AACvC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,IAAuB,EACvB,OAAsB;IAEtB,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC;IACjE,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,UAAU,CAAC;IACtC,IAAI,MAAc,CAAC;IACnB,IAAI,MAAc,CAAC;IACnB,IAAI,CAAC;QACH,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAC7B,OAAO,CAAC,QAAQ,EAChB,CAAC,KAAK,EAAE,GAAG,IAAI,EAAE,QAAQ,CAAC,EAC1B;YACE,GAAG,EAAE,OAAO,CAAC,GAAG;YAChB,GAAG,EAAE,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE;YACvC,SAAS,EAAE,EAAE,GAAG,MAAM;SACvB,CACF,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,CAAC,CAAC,KAAK,YAAY,kBAAkB,CAAC;YAAE,MAAM,KAAK,CAAC;QACxD,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;QACtB,MAAM,GAAG,KAAK,CAAC,MAAM,CAAC;IACxB,CAAC;IAED,IAAI,QAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAmB,CAAC;IAClD,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,cAAc,CACtB,gBAAgB,EAChB,GAAG,KAAK,+BAA+B,EACvC,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAC5D,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,cAAc,CACtB,QAAQ,CAAC,KAAK,EAAE,IAAI,IAAI,gBAAgB,EACxC,GAAG,KAAK,YAAY,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,YAAY,EAAE,EAC7D;YACE,WAAW,EAAE,CAAC,QAAQ,CAAC,KAAK,EAAE,WAAW,IAAI,EAAE,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC;YAClE,OAAO,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE;SAC5B,CACF,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC;AAClB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,QAAwB;IACvD,MAAM,IAAI,GACR,QAAQ,CAAC,MACV,EAAE,IAAI,CAAC;IACR,OAAO,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC,MAAM,CACxB,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,EAAE,MAAM,IAAI,CAAC,CAAC,EACpD,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,167 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { copyFile, mkdir, readFile, readdir, rm, writeFile, } from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { parse } from 'yaml';
5
+ import { EXIT_INVALID, InstallerError } from "./errors.js";
6
+ /** A SQLite database is the main file plus whichever journal files exist beside it. */
7
+ const SQLITE_SUFFIXES = ['', '-wal', '-shm', '-journal'];
8
+ /** Written into every backup, so a restore knows where each database goes even after `config.yml` changes. */
9
+ const MANIFEST = 'backup.json';
10
+ /** `${NAME}` references, resolved the way the runtime resolves them from its environment; `undefined` if one is unset. */
11
+ function interpolate(value, env) {
12
+ let missing = false;
13
+ const resolved = value.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/gu, (_, name) => {
14
+ const found = env[name];
15
+ if (found === undefined)
16
+ missing = true;
17
+ return found ?? '';
18
+ });
19
+ return missing ? undefined : resolved;
20
+ }
21
+ /**
22
+ * The application's databases as `config.yml` declares them. A SQLite path is resolved against the storage directory,
23
+ * as the runtime resolves it; `:memory:` holds nothing to back up. A connection `config.yml` does not declare is not
24
+ * seen, which is why an upgrade stops when the default connection is SQLite and its file is missing.
25
+ */
26
+ export function readDatabaseInventory(layout, env) {
27
+ let document = {};
28
+ try {
29
+ document = (parse(readFileSync(layout.configFile, 'utf8')) ??
30
+ {});
31
+ }
32
+ catch (error) {
33
+ // Treating this as "no databases" would let an upgrade proceed without a backup and without --backup-done.
34
+ throw new InstallerError('CONFIG_UNREADABLE', `${layout.configFile} could not be read as YAML, so the databases to back up are unknown: ${error instanceof Error ? error.message : String(error)}`, { exitCode: EXIT_INVALID, cause: error });
35
+ }
36
+ const inventory = {
37
+ defaultConnection: typeof document.database?.default === 'string'
38
+ ? document.database.default
39
+ : 'main',
40
+ sqlite: [],
41
+ external: [],
42
+ unresolved: [],
43
+ };
44
+ const lookup = { ...process.env, ...env };
45
+ for (const [connection, raw] of Object.entries(document.database?.connections ?? {})) {
46
+ const config = (raw ?? {});
47
+ if (config.dialect !== 'sqlite') {
48
+ inventory.external.push(connection);
49
+ continue;
50
+ }
51
+ const declared = config.database ?? config.filename;
52
+ if (typeof declared !== 'string' || declared === '') {
53
+ inventory.unresolved.push(connection);
54
+ continue;
55
+ }
56
+ const file = interpolate(declared, lookup);
57
+ if (file === undefined) {
58
+ inventory.unresolved.push(connection);
59
+ continue;
60
+ }
61
+ if (file === ':memory:')
62
+ continue;
63
+ inventory.sqlite.push({
64
+ connection,
65
+ file: path.resolve(layout.storageDir, file),
66
+ });
67
+ }
68
+ return inventory;
69
+ }
70
+ /** UTC, so backups taken on machines in different time zones sort the same way. */
71
+ function stamp(date) {
72
+ const pad = (value) => String(value).padStart(2, '0');
73
+ return `${date.getUTCFullYear()}${pad(date.getUTCMonth() + 1)}${pad(date.getUTCDate())}-${pad(date.getUTCHours())}${pad(date.getUTCMinutes())}${pad(date.getUTCSeconds())}Z`;
74
+ }
75
+ export function backupName(from, to, date) {
76
+ return `${stamp(date)}_${from}_to_${to}`;
77
+ }
78
+ async function existingFiles(file) {
79
+ const base = path.basename(file);
80
+ const present = new Set(await readdir(path.dirname(file)).catch(() => []));
81
+ return SQLITE_SUFFIXES.map((suffix) => `${base}${suffix}`).filter((name) => present.has(name));
82
+ }
83
+ async function readManifest(layout, backupRelative) {
84
+ try {
85
+ return JSON.parse(await readFile(path.join(layout.root, backupRelative, MANIFEST), 'utf8'));
86
+ }
87
+ catch {
88
+ return undefined;
89
+ }
90
+ }
91
+ /**
92
+ * Whether a backup holds a database that can be restored. An upgrade of an application on an external database backs
93
+ * up only `config.yml` and `app.env`, and a backup interrupted before its copy finished has no manifest at all.
94
+ */
95
+ export function backupHasDatabase(layout, backupRelative) {
96
+ const file = path.join(layout.root, backupRelative, MANIFEST);
97
+ if (!existsSync(file))
98
+ return false;
99
+ try {
100
+ const manifest = JSON.parse(readFileSync(file, 'utf8'));
101
+ return manifest.databases.some((database) => database.files.length > 0);
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ }
107
+ export async function removeBackup(layout, backupRelative) {
108
+ await rm(path.join(layout.root, backupRelative), {
109
+ recursive: true,
110
+ force: true,
111
+ });
112
+ }
113
+ /** Where a connection's files go inside a backup: one directory per connection, so equal file names never collide. */
114
+ function backupDatabaseDir(target, connection) {
115
+ return path.join(target, 'sqlite', connection);
116
+ }
117
+ /**
118
+ * Copies the application's SQLite databases, `config.yml` and `app.env` while the application is stopped, so the files
119
+ * are consistent. The manifest is written last: a backup cut short has none and is never restored from. This is what
120
+ * a failed upgrade restores, not a replacement for regular backups of `storage/`, whose uploaded files it leaves out.
121
+ */
122
+ export async function backupForUpgrade(layout, name, databases) {
123
+ const target = path.join(layout.backupsDir, name);
124
+ await mkdir(target, { recursive: true });
125
+ await copyFile(layout.configFile, path.join(target, 'config.yml'));
126
+ await copyFile(layout.appEnv, path.join(target, 'app.env'));
127
+ const copied = [];
128
+ for (const database of databases) {
129
+ const files = await existingFiles(database.file);
130
+ const directory = backupDatabaseDir(target, database.connection);
131
+ await mkdir(directory, { recursive: true });
132
+ for (const file of files) {
133
+ await copyFile(path.join(path.dirname(database.file), file), path.join(directory, file));
134
+ }
135
+ copied.push({ ...database, files });
136
+ }
137
+ const manifest = { databases: copied };
138
+ await writeFile(path.join(target, MANIFEST), `${JSON.stringify(manifest, null, 2)}\n`);
139
+ return { relative: path.relative(layout.root, target), databases: copied };
140
+ }
141
+ /**
142
+ * Puts backed-up databases back where the manifest says they were. Journal files that exist now but were not in the
143
+ * backup are removed, since SQLite would otherwise replay a newer write-ahead log onto the older database; a database
144
+ * that did not exist when the backup was taken is removed, since it was created by the release being undone.
145
+ */
146
+ export async function restoreDatabase(layout, backupRelative) {
147
+ const source = path.join(layout.root, backupRelative);
148
+ const manifest = await readManifest(layout, backupRelative);
149
+ if (!manifest ||
150
+ !manifest.databases.some((database) => database.files.length > 0)) {
151
+ throw new Error(`${source} holds no SQLite database to restore.`);
152
+ }
153
+ for (const database of manifest.databases) {
154
+ const directory = path.dirname(database.file);
155
+ await mkdir(directory, { recursive: true });
156
+ for (const leftover of await existingFiles(database.file)) {
157
+ if (!database.files.includes(leftover)) {
158
+ await rm(path.join(directory, leftover), { force: true });
159
+ }
160
+ }
161
+ for (const file of database.files) {
162
+ await copyFile(path.join(backupDatabaseDir(source, database.connection), file), path.join(directory, file));
163
+ }
164
+ }
165
+ return manifest.databases;
166
+ }
167
+ //# sourceMappingURL=backup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backup.js","sourceRoot":"","sources":["../../src/lib/backup.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { EXIT_INVALID, InstallerError } from "./errors.js";
3
+ /**
4
+ * Asks before an operation that takes the application down or discards data. `--yes` answers for scripts; without it, a run that
5
+ * cannot ask — `--json`, or no terminal — stops instead of guessing.
6
+ */
7
+ export async function confirm(lines, options) {
8
+ if (options.yes)
9
+ return;
10
+ const input = options.input ?? process.stdin;
11
+ const output = options.output ?? process.stderr;
12
+ if (options.json || !input.isTTY) {
13
+ throw new InstallerError('CONFIRMATION_REQUIRED', `${lines[0]} Pass --yes to proceed without a prompt.`, {
14
+ exitCode: EXIT_INVALID,
15
+ details: { notes: lines.slice(1) },
16
+ suggestions: [
17
+ { message: 'Run it again with --yes once you have read:' },
18
+ ...lines.slice(1).map((message) => ({ message })),
19
+ ],
20
+ });
21
+ }
22
+ output.write(`${lines.map((line) => ` ${line}`).join('\n')}\n`);
23
+ const readline = createInterface({ input, output });
24
+ try {
25
+ const answer = await readline.question('Continue? [y/N] ');
26
+ if (!/^y(es)?$/iu.test(answer.trim())) {
27
+ throw new InstallerError('CANCELLED', 'Cancelled; nothing was changed.', {
28
+ exitCode: EXIT_INVALID,
29
+ });
30
+ }
31
+ }
32
+ finally {
33
+ readline.close();
34
+ }
35
+ }
36
+ //# sourceMappingURL=confirm.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"confirm.js","sourceRoot":"","sources":["../../src/lib/confirm.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAS3D;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,KAAwB,EACxB,OAAuB;IAEvB,IAAI,OAAO,CAAC,GAAG;QAAE,OAAO;IACxB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,KAAK,CAAC;IAC7C,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,MAAM,CAAC;IAChD,IAAI,OAAO,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACjC,MAAM,IAAI,cAAc,CACtB,uBAAuB,EACvB,GAAG,KAAK,CAAC,CAAC,CAAC,0CAA0C,EACrD;YACE,QAAQ,EAAE,YAAY;YACtB,OAAO,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;YAClC,WAAW,EAAE;gBACX,EAAE,OAAO,EAAE,6CAA6C,EAAE;gBAC1D,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;aAClD;SACF,CACF,CAAC;IACJ,CAAC;IACD,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACjE,MAAM,QAAQ,GAAG,eAAe,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACpD,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CAAC;QAC3D,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;YACtC,MAAM,IAAI,cAAc,CAAC,WAAW,EAAE,iCAAiC,EAAE;gBACvE,QAAQ,EAAE,YAAY;aACvB,CAAC,CAAC;QACL,CAAC;IACH,CAAC;YAAS,CAAC;QACT,QAAQ,CAAC,KAAK,EAAE,CAAC;IACnB,CAAC;AACH,CAAC"}
@@ -0,0 +1,22 @@
1
+ import { readlink, rename, rm, symlink } from 'node:fs/promises';
2
+ /**
3
+ * Points `current` at a release in one step: the new link is created beside it and renamed over it, so `current`
4
+ * always names a complete release and never goes missing in between.
5
+ */
6
+ export async function switchCurrent(layout, linkTarget) {
7
+ const temporary = `${layout.current}.${process.pid}.tmp`;
8
+ await rm(temporary, { force: true });
9
+ await symlink(linkTarget, temporary, 'dir');
10
+ await rename(temporary, layout.current);
11
+ }
12
+ export async function readCurrent(layout) {
13
+ try {
14
+ return await readlink(layout.current);
15
+ }
16
+ catch (error) {
17
+ if (error.code === 'ENOENT')
18
+ return undefined;
19
+ throw error;
20
+ }
21
+ }
22
+ //# sourceMappingURL=current-link.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"current-link.js","sourceRoot":"","sources":["../../src/lib/current-link.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAGjE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,MAAc,EACd,UAAkB;IAElB,MAAM,SAAS,GAAG,GAAG,MAAM,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,MAAM,CAAC;IACzD,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrC,MAAM,OAAO,CAAC,UAAU,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC;IAC5C,MAAM,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,MAAc;IAC9C,IAAI,CAAC;QACH,OAAO,MAAM,QAAQ,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IACxC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAK,KAA+B,CAAC,IAAI,KAAK,QAAQ;YAAE,OAAO,SAAS,CAAC;QACzE,MAAM,KAAK,CAAC;IACd,CAAC;AACH,CAAC"}
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Builds `ecosystem.config.cjs`, the pm2 configuration every start goes through.
3
+ *
4
+ * pm2 runs `node launcher.mjs` rather than the application's entry file. The file names nothing that changes: which
5
+ * release runs and with which variables is decided by `launcher.mjs` each time the process starts, so a plain
6
+ * `pm2 restart` picks up a switched `current` and an edited `app.env`.
7
+ *
8
+ * pm2 runs `node` itself, with the script as its argument. Pointing `script` at a module instead has pm2's fork mode
9
+ * load it through `ProcessContainerFork.js`, where `import.meta.main` is false, so `standalone.js` never calls
10
+ * `startServer()` and the application silently never starts.
11
+ */
12
+ export function buildEcosystemConfig(options) {
13
+ return `// Written by app-installer. Starting the application always goes through this file.
14
+ const path = require('node:path');
15
+
16
+ const root = __dirname;
17
+
18
+ module.exports = {
19
+ apps: [
20
+ {
21
+ name: ${JSON.stringify(options.name)},
22
+ // Run node itself, so launcher.mjs, and the application after it, are the main module (import.meta.main).
23
+ script: ${JSON.stringify(options.nodePath)},
24
+ // launcher.mjs reads app.env and resolves \`current\` on every start, so \`pm2 restart\` applies changes to either.
25
+ args: [path.join(root, 'launcher.mjs')],
26
+ interpreter: 'none',
27
+ cwd: root,
28
+ autorestart: true,
29
+ min_uptime: '30s',
30
+ max_restarts: 10,
31
+ exp_backoff_restart_delay: 1000,
32
+ // pm2 sends SIGKILL 1.6s after SIGINT by default; the application finishes what it is doing first.
33
+ kill_timeout: 60000,
34
+ out_file: path.join(root, 'logs/app.out.log'),
35
+ error_file: path.join(root, 'logs/app.err.log'),
36
+ time: true,
37
+ },
38
+ ],
39
+ };
40
+ `;
41
+ }
42
+ /**
43
+ * Builds `launcher.mjs`, the script pm2 runs. On every start it reads `app.env`, resolves `current`, and replaces itself
44
+ * with `node <release>/dist/server/standalone.js` through `process.execve`.
45
+ *
46
+ * Replacing the process rather than starting a child keeps the application on the pid pm2 watches, receiving pm2's
47
+ * signals itself and running as the main module. A child would need its signals forwarded, and would outlive a
48
+ * launcher that pm2 kills, holding the port the next start needs. Node flags given to the launcher, such as
49
+ * `--max-old-space-size` in `args`, pass on to the application; pm2's `node_args` does not apply to an
50
+ * `interpreter: 'none'` process.
51
+ *
52
+ * pm2 gives the launcher an IPC channel, but the channel does not survive `process.execve`: the application starts
53
+ * without `process.send`, and pm2 options that rely on it, such as `wait_ready` and `shutdown_with_message`, are not
54
+ * available.
55
+ *
56
+ * The entry is resolved to its real path, so a running application keeps loading its modules from the release it
57
+ * started from even after `current` moves on.
58
+ */
59
+ export function buildLauncher() {
60
+ return `// Written by app-installer. pm2 runs this file; it starts the release \`current\` points at, with app.env applied.
61
+ import { readFileSync, realpathSync } from 'node:fs';
62
+ import path from 'node:path';
63
+ import { parseEnv } from 'node:util';
64
+
65
+ const root = import.meta.dirname;
66
+ const env = {
67
+ ...process.env,
68
+ ...parseEnv(readFileSync(path.join(root, 'app.env'), 'utf8')),
69
+ };
70
+ const entry = realpathSync(path.join(root, 'current/dist/server/standalone.js'));
71
+
72
+ // Become the application rather than start it as a child: same pid for pm2, signals delivered directly, and
73
+ // standalone.js is the main module, which is what makes it start the server. Node flags and arguments pass on
74
+ // unchanged.
75
+ process.execve(
76
+ process.execPath,
77
+ [process.execPath, ...process.execArgv, entry, ...process.argv.slice(2)],
78
+ env,
79
+ );
80
+ `;
81
+ }
82
+ //# sourceMappingURL=ecosystem.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ecosystem.js","sourceRoot":"","sources":["../../src/lib/ecosystem.ts"],"names":[],"mappings":"AAOA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,oBAAoB,CAAC,OAAyB;IAC5D,OAAO;;;;;;;;cAQK,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC;;gBAE1B,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,QAAQ,CAAC;;;;;;;;;;;;;;;;;CAiB/C,CAAC;AACF,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO;;;;;;;;;;;;;;;;;;;;CAoBR,CAAC;AACF,CAAC"}
@@ -0,0 +1,87 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { parseEnv } from 'node:util';
3
+ /**
4
+ * Builds `app.env`, the one set of runtime variables both the application and the installer's own CLI calls read.
5
+ * `launcher.mjs` reads it on every start, so an edit takes effect with `pm2 restart`.
6
+ *
7
+ * `APP_CONFIG_FILE` and `APP_STORAGE_DIR` are absolute on purpose. A built server treats the directory above `dist/` as
8
+ * its deployment root and keeps `config.yml` and `storage/` there by default, which here is the release directory an
9
+ * upgrade replaces. `dist/.env` carries neither variable, so leaving one out silently moves the application's data into
10
+ * the release.
11
+ */
12
+ export function buildAppEnv(layout, options) {
13
+ const entries = [
14
+ ['NODE_ENV', 'production'],
15
+ ...(options.basePath === undefined
16
+ ? []
17
+ : [['APP_BASE_PATH', options.basePath]]),
18
+ ['APP_CONFIG_FILE', layout.configFile],
19
+ ['APP_STORAGE_DIR', layout.storageDir],
20
+ ['APP_PUBLIC_ORIGIN', options.origin],
21
+ ['APP_SERVER_HOST', options.host],
22
+ ['APP_SERVER_PORT', String(options.port)],
23
+ // A failed start exits non-zero, so pm2 restarts it and the installer sees the failure.
24
+ ['NOCOBASE_STRICT_STARTUP', 'true'],
25
+ ];
26
+ return [
27
+ '# Written by app-installer. Read by launcher.mjs on every start and by every app-installer command.',
28
+ ...entries.map(([key, value]) => `${key}=${quote(value)}`),
29
+ '',
30
+ ].join('\n');
31
+ }
32
+ function quote(value) {
33
+ return /^[\w@%+=:,./-]*$/u.test(value) ? value : JSON.stringify(value);
34
+ }
35
+ export async function readAppEnv(layout) {
36
+ return parseEnv(await readFile(layout.appEnv, 'utf8'));
37
+ }
38
+ const DEFAULT_HOST = '127.0.0.1';
39
+ const DEFAULT_PORT = '13000';
40
+ // The server mounts an application that sets no APP_BASE_PATH at `DEFAULT_APP_BASE_PATH` from
41
+ // `@nocobase/app-server/support`. app-installer runs as its own npx tool, without the server's code, so it repeats the
42
+ // value only to tell where such an installation is reached; it never writes it.
43
+ const DEFAULT_BASE_PATH = '/main';
44
+ function parsePort(value) {
45
+ const port = Number(value.trim());
46
+ return value.trim() !== '' &&
47
+ Number.isInteger(port) &&
48
+ port >= 1 &&
49
+ port <= 65535
50
+ ? port
51
+ : null;
52
+ }
53
+ /** The path the application is mounted at, as a URL prefix: `/crm` stays, and the origin root is `''`. */
54
+ export function mountPathOf(env) {
55
+ return basePrefix(env);
56
+ }
57
+ /** The base path as a URL prefix: `/crm` stays, and the root `/` contributes nothing. */
58
+ function basePrefix(env) {
59
+ const trimmed = (env.APP_BASE_PATH ?? DEFAULT_BASE_PATH)
60
+ .trim()
61
+ .replace(/^\/+|\/+$/gu, '');
62
+ return trimmed ? `/${trimmed}` : '';
63
+ }
64
+ /**
65
+ * Where the application is reached and where it listens, as `app.env` says. The file is edited by hand to change
66
+ * either, so a trailing slash on the origin is dropped, as `install` drops it from `--origin`, and a port that is not
67
+ * one reads as `null` rather than as a number the application is not using.
68
+ */
69
+ export function endpointsOf(env) {
70
+ const host = env.APP_SERVER_HOST ?? DEFAULT_HOST;
71
+ const rawPort = env.APP_SERVER_PORT ?? DEFAULT_PORT;
72
+ const origin = (env.APP_PUBLIC_ORIGIN ?? `http://${host}:${rawPort}`).replace(/\/+$/u, '');
73
+ return {
74
+ url: `${origin}${basePrefix(env)}/`,
75
+ origin,
76
+ host,
77
+ port: parsePort(rawPort),
78
+ };
79
+ }
80
+ /** The address the installer checks health on: the listening address, with a wildcard bind reached through loopback. */
81
+ export function healthUrl(env) {
82
+ const { host, port } = endpointsOf(env);
83
+ const reachable = host === '0.0.0.0' || host === '::' || host === '' ? '127.0.0.1' : host;
84
+ const hostPart = reachable.includes(':') ? `[${reachable}]` : reachable;
85
+ return `http://${hostPart}:${port ?? env.APP_SERVER_PORT}${basePrefix(env)}/api/healthz`;
86
+ }
87
+ //# sourceMappingURL=env-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"env-file.js","sourceRoot":"","sources":["../../src/lib/env-file.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Exit codes are part of the contract: CI and agents branch on them.
3
+ *
4
+ * - `0` success, including a run that found nothing to do
5
+ * - `1` the operation failed, and the running application was not touched
6
+ * - `2` invalid usage or a failed precheck, before anything was written
7
+ * - `3` an upgrade failed after the switch and was rolled back; the previous release is running again
8
+ * - `4` rolling back failed too; the application needs a person, and the error says what to do
9
+ */
10
+ export const EXIT_OK = 0;
11
+ export const EXIT_FAILED = 1;
12
+ export const EXIT_INVALID = 2;
13
+ export const EXIT_ROLLED_BACK = 3;
14
+ export const EXIT_ROLLBACK_FAILED = 4;
15
+ export class InstallerError extends Error {
16
+ code;
17
+ exitCode;
18
+ suggestions;
19
+ details;
20
+ constructor(code, message, options = {}) {
21
+ super(message, { cause: options.cause });
22
+ this.name = 'InstallerError';
23
+ this.code = code;
24
+ this.exitCode = options.exitCode ?? EXIT_FAILED;
25
+ this.suggestions = options.suggestions ?? [];
26
+ this.details = options.details;
27
+ }
28
+ }
29
+ export function isInstallerError(error) {
30
+ return error instanceof InstallerError;
31
+ }
32
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/lib/errors.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAC;AACzB,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,CAAC;AAC7B,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAC9B,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC;AAClC,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAatC,MAAM,OAAO,cAAe,SAAQ,KAAK;IACvB,IAAI,CAAS;IACb,QAAQ,CAAS;IACjB,WAAW,CAAe;IAC1B,OAAO,CAAsC;IAE7D,YACE,IAAY,EACZ,OAAe,EACf,UAAiC,EAAE;QAEnC,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACzC,IAAI,CAAC,IAAI,GAAG,gBAAgB,CAAC;QAC7B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,WAAW,CAAC;QAChD,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC;QAC7C,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;IACjC,CAAC;CACF;AAED,MAAM,UAAU,gBAAgB,CAAC,KAAc;IAC7C,OAAO,KAAK,YAAY,cAAc,CAAC;AACzC,CAAC"}
@@ -0,0 +1,55 @@
1
+ import { interruptError, takeInterrupt } from "./interrupt.js";
2
+ /** One request to the application's health route; true only for a response whose body says `ok: true`. */
3
+ export async function checkHealth(url, fetchImpl = fetch) {
4
+ try {
5
+ const response = await fetchImpl(url, {
6
+ signal: AbortSignal.timeout(3_000),
7
+ });
8
+ if (!response.ok)
9
+ return false;
10
+ const body = (await response.json());
11
+ return body.ok === true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ /**
18
+ * Polls the health route until it answers, the timeout passes, or `failed` says there is no point waiting. The server
19
+ * listens only once startup, migrations included, has finished, so a healthy answer means the application is ready.
20
+ */
21
+ export async function waitForHealthy(url, options) {
22
+ const now = options.now ?? Date.now;
23
+ const sleep = options.sleep ??
24
+ ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
25
+ const deadline = now() + options.timeoutMs;
26
+ for (;;) {
27
+ if (takeInterrupt())
28
+ throw interruptError();
29
+ if (await checkHealth(url, options.fetchImpl))
30
+ return true;
31
+ if (now() >= deadline)
32
+ return false;
33
+ if (options.failed && (await options.failed()))
34
+ return false;
35
+ await sleep(options.intervalMs ?? 1_000);
36
+ }
37
+ }
38
+ /**
39
+ * Restarts pm2 has made before the application answered, beyond which a start counts as failed.
40
+ * `NOCOBASE_STRICT_STARTUP` makes a failed start exit, so a crash during startup is a real failure rather than a slow
41
+ * start.
42
+ */
43
+ const MAX_STARTUP_RESTARTS = 2;
44
+ /** A `failed` probe for `waitForHealthy`: the pm2 process has crashed repeatedly, or pm2 has given up on it. */
45
+ export function pm2StartFailed(pm2, name) {
46
+ return async () => {
47
+ const process = await pm2.describe(name).catch(() => undefined);
48
+ if (!process)
49
+ return false;
50
+ return (process.status === 'errored' ||
51
+ process.status === 'stopped' ||
52
+ process.restarts > MAX_STARTUP_RESTARTS);
53
+ };
54
+ }
55
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../src/lib/health.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAc/D,0GAA0G;AAC1G,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,GAAW,EACX,YAAuB,KAAK;IAE5B,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,EAAE;YACpC,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC;SACnC,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE;YAAE,OAAO,KAAK,CAAC;QAC/B,MAAM,IAAI,GAAG,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,CAAqB,CAAC;QACzD,OAAO,IAAI,CAAC,EAAE,KAAK,IAAI,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,GAAW,EACX,OAAsB;IAEtB,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACpC,MAAM,KAAK,GACT,OAAO,CAAC,KAAK;QACb,CAAC,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC;IAC5E,MAAM,QAAQ,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC;IAC3C,SAAS,CAAC;QACR,IAAI,aAAa,EAAE;YAAE,MAAM,cAAc,EAAE,CAAC;QAC5C,IAAI,MAAM,WAAW,CAAC,GAAG,EAAE,OAAO,CAAC,SAAS,CAAC;YAAE,OAAO,IAAI,CAAC;QAC3D,IAAI,GAAG,EAAE,IAAI,QAAQ;YAAE,OAAO,KAAK,CAAC;QACpC,IAAI,OAAO,CAAC,MAAM,IAAI,CAAC,MAAM,OAAO,CAAC,MAAM,EAAE,CAAC;YAAE,OAAO,KAAK,CAAC;QAC7D,MAAM,KAAK,CAAC,OAAO,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAE/B,gHAAgH;AAChH,MAAM,UAAU,cAAc,CAAC,GAAQ,EAAE,IAAY;IACnD,OAAO,KAAK,IAAI,EAAE;QAChB,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChE,IAAI,CAAC,OAAO;YAAE,OAAO,KAAK,CAAC;QAC3B,OAAO,CACL,OAAO,CAAC,MAAM,KAAK,SAAS;YAC5B,OAAO,CAAC,MAAM,KAAK,SAAS;YAC5B,OAAO,CAAC,QAAQ,GAAG,oBAAoB,CACxC,CAAC;IACJ,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { parse } from 'yaml';
3
+ /** The `config.yml` section the application creates its first administrator from. */
4
+ export const INITIAL_ADMIN_KEY = 'users.initialAdmin';
5
+ async function readSection(file) {
6
+ try {
7
+ const document = parse(await readFile(file, 'utf8'), {
8
+ logLevel: 'silent',
9
+ });
10
+ return document?.users?.initialAdmin ?? undefined;
11
+ }
12
+ catch {
13
+ return undefined;
14
+ }
15
+ }
16
+ function text(value) {
17
+ return typeof value === 'string' || typeof value === 'number'
18
+ ? String(value)
19
+ : null;
20
+ }
21
+ /**
22
+ * Reads the first administrator from `config.yml`, and compares its password with the one in the release's
23
+ * `config.example.yml`, which `config init` copies unless `--set` replaced it. The password itself never leaves here.
24
+ */
25
+ export async function readInitialAdmin(configFile, exampleFile) {
26
+ const configured = await readSection(configFile);
27
+ const example = await readSection(exampleFile);
28
+ const password = text(configured?.password);
29
+ const examplePassword = text(example?.password);
30
+ return {
31
+ key: INITIAL_ADMIN_KEY,
32
+ username: text(configured?.username),
33
+ email: text(configured?.email),
34
+ defaultPassword: password === null || examplePassword === null
35
+ ? null
36
+ : password === examplePassword,
37
+ };
38
+ }
39
+ //# sourceMappingURL=initial-admin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"initial-admin.js","sourceRoot":"","sources":["../../src/lib/initial-admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,MAAM,CAAC;AAE7B,qFAAqF;AACrF,MAAM,CAAC,MAAM,iBAAiB,GAAG,oBAAoB,CAAC;AAqBtD,KAAK,UAAU,WAAW,CACxB,IAAY;IAEZ,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;YACnD,QAAQ,EAAE,QAAQ;SACnB,CAA8D,CAAC;QAChE,OAAO,QAAQ,EAAE,KAAK,EAAE,YAAY,IAAI,SAAS,CAAC;IACpD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,SAAS,IAAI,CAAC,KAAc;IAC1B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ;QAC3D,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QACf,CAAC,CAAC,IAAI,CAAC;AACX,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,UAAkB,EAClB,WAAmB;IAEnB,MAAM,UAAU,GAAG,MAAM,WAAW,CAAC,UAAU,CAAC,CAAC;IACjD,MAAM,OAAO,GAAG,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IAC/C,MAAM,QAAQ,GAAG,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAC5C,MAAM,eAAe,GAAG,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAChD,OAAO;QACL,GAAG,EAAE,iBAAiB;QACtB,QAAQ,EAAE,IAAI,CAAC,UAAU,EAAE,QAAQ,CAAC;QACpC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,KAAK,CAAC;QAC9B,eAAe,EACb,QAAQ,KAAK,IAAI,IAAI,eAAe,KAAK,IAAI;YAC3C,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,QAAQ,KAAK,eAAe;KACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,50 @@
1
+ import { EXIT_FAILED, InstallerError } from "./errors.js";
2
+ /**
3
+ * SIGINT and SIGTERM stop the step that is running, not the installer. The step fails with `INTERRUPTED`, and the
4
+ * command's own failure handling runs as it would for any other failure: an install removes what it wrote, an upgrade
5
+ * after the switch rolls back. A second signal exits at once, whatever that leaves behind.
6
+ */
7
+ let pending = false;
8
+ let received = 0;
9
+ const listeners = new Set();
10
+ export function interruptError() {
11
+ return new InstallerError('INTERRUPTED', 'Interrupted; the step that was running was stopped.', { exitCode: EXIT_FAILED });
12
+ }
13
+ /** Reports a pending interrupt once. The step that sees it fails; the recovery steps after it run normally. */
14
+ export function takeInterrupt() {
15
+ if (!pending)
16
+ return false;
17
+ pending = false;
18
+ return true;
19
+ }
20
+ export function throwIfInterrupted() {
21
+ if (takeInterrupt())
22
+ throw interruptError();
23
+ }
24
+ /** Called for each interrupt, so running child processes can be stopped. */
25
+ export function onInterrupt(listener) {
26
+ listeners.add(listener);
27
+ return () => listeners.delete(listener);
28
+ }
29
+ export function installInterruptHandlers(stderr = process.stderr) {
30
+ const handler = (signal) => {
31
+ received += 1;
32
+ if (received > 1) {
33
+ stderr.write(`\n${signal} again; exiting now.\n`);
34
+ process.exit(130);
35
+ }
36
+ pending = true;
37
+ stderr.write(`\n${signal} received; stopping the current step and cleaning up. Send it again to exit immediately.\n`);
38
+ for (const listener of listeners)
39
+ listener();
40
+ };
41
+ process.on('SIGINT', handler);
42
+ process.on('SIGTERM', handler);
43
+ return () => {
44
+ process.off('SIGINT', handler);
45
+ process.off('SIGTERM', handler);
46
+ pending = false;
47
+ received = 0;
48
+ };
49
+ }
50
+ //# sourceMappingURL=interrupt.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interrupt.js","sourceRoot":"","sources":["../../src/lib/interrupt.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAE1D;;;;GAIG;AACH,IAAI,OAAO,GAAG,KAAK,CAAC;AACpB,IAAI,QAAQ,GAAG,CAAC,CAAC;AACjB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;AAExC,MAAM,UAAU,cAAc;IAC5B,OAAO,IAAI,cAAc,CACvB,aAAa,EACb,qDAAqD,EACrD,EAAE,QAAQ,EAAE,WAAW,EAAE,CAC1B,CAAC;AACJ,CAAC;AAED,+GAA+G;AAC/G,MAAM,UAAU,aAAa;IAC3B,IAAI,CAAC,OAAO;QAAE,OAAO,KAAK,CAAC;IAC3B,OAAO,GAAG,KAAK,CAAC;IAChB,OAAO,IAAI,CAAC;AACd,CAAC;AAED,MAAM,UAAU,kBAAkB;IAChC,IAAI,aAAa,EAAE;QAAE,MAAM,cAAc,EAAE,CAAC;AAC9C,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,WAAW,CAAC,QAAoB;IAC9C,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IACxB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,wBAAwB,CACtC,SAAgC,OAAO,CAAC,MAAM;IAE9C,MAAM,OAAO,GAAG,CAAC,MAAsB,EAAE,EAAE;QACzC,QAAQ,IAAI,CAAC,CAAC;QACd,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YACjB,MAAM,CAAC,KAAK,CAAC,KAAK,MAAM,wBAAwB,CAAC,CAAC;YAClD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACpB,CAAC;QACD,OAAO,GAAG,IAAI,CAAC;QACf,MAAM,CAAC,KAAK,CACV,KAAK,MAAM,4FAA4F,CACxG,CAAC;QACF,KAAK,MAAM,QAAQ,IAAI,SAAS;YAAE,QAAQ,EAAE,CAAC;IAC/C,CAAC,CAAC;IACF,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC9B,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC/B,OAAO,GAAG,EAAE;QACV,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/B,OAAO,CAAC,GAAG,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;QAChC,OAAO,GAAG,KAAK,CAAC;QAChB,QAAQ,GAAG,CAAC,CAAC;IACf,CAAC,CAAC;AACJ,CAAC"}