@helyx/cli 1.2.3 → 1.4.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/CHANGELOG.md +45 -19
- package/README.md +43 -2
- package/dist/cli.js +62 -20
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/self-host/catalogue.d.ts +28 -0
- package/dist/self-host/catalogue.js +25 -0
- package/dist/self-host/command.js +7 -4
- package/dist/self-host/database-adapters.d.ts +9 -0
- package/dist/self-host/database-adapters.js +11 -0
- package/dist/self-host/manifest.d.ts +6 -2
- package/dist/self-host/manifest.js +20 -6
- package/dist/self-host/project-generator.d.ts +3 -1
- package/dist/self-host/project-generator.js +60 -24
- package/dist/self-host/update.d.ts +4 -3
- package/dist/self-host/update.js +72 -25
- package/dist/setup.d.ts +1 -0
- package/dist/setup.js +6 -0
- package/package.json +8 -5
- package/self-host-catalogue.json +169 -98
- package/templates/self-host/.env.example +15 -11
- package/templates/self-host/DOCUMENTATION.md +6 -0
- package/templates/self-host/Dockerfile +1 -1
- package/templates/self-host/README.md +142 -23
- package/templates/self-host/compose.mysql.yaml +6 -0
|
@@ -4,6 +4,7 @@ import { mkdir, readFile, readdir, rm, stat, writeFile, } from "node:fs/promises
|
|
|
4
4
|
import { dirname, parse, resolve } from "node:path";
|
|
5
5
|
import { isSelfHostModuleCustomerSelectable, } from "./catalogue.js";
|
|
6
6
|
import { parseSelfHostBuildManifest, } from "./manifest.js";
|
|
7
|
+
import { databaseAdaptersForCatalogue, } from "./database-adapters.js";
|
|
7
8
|
const generatedFileNames = [
|
|
8
9
|
".env.example",
|
|
9
10
|
".gitignore",
|
|
@@ -16,6 +17,7 @@ const generatedFileNames = [
|
|
|
16
17
|
export async function generateSelfHostProject(options) {
|
|
17
18
|
const manifest = parseSelfHostBuildManifest(options.manifest, options.catalogue);
|
|
18
19
|
const targetDirectory = resolve(options.targetDirectory);
|
|
20
|
+
const databaseAdapters = options.databaseAdapters ?? databaseAdaptersForCatalogue(options.catalogue);
|
|
19
21
|
const signal = options.signal ?? AbortSignal.timeout(120_000);
|
|
20
22
|
signal.throwIfAborted();
|
|
21
23
|
assertSafeTargetDirectory(targetDirectory);
|
|
@@ -24,7 +26,7 @@ export async function generateSelfHostProject(options) {
|
|
|
24
26
|
try {
|
|
25
27
|
await mkdir(targetDirectory);
|
|
26
28
|
created = true;
|
|
27
|
-
await writeGeneratedFiles(targetDirectory, manifest, options.catalogue);
|
|
29
|
+
await writeGeneratedFiles(targetDirectory, manifest, options.catalogue, databaseAdapters);
|
|
28
30
|
signal.throwIfAborted();
|
|
29
31
|
const runner = options.commandRunner ?? runNpmCommand;
|
|
30
32
|
await runner([
|
|
@@ -35,7 +37,7 @@ export async function generateSelfHostProject(options) {
|
|
|
35
37
|
"--no-fund",
|
|
36
38
|
], targetDirectory, signal);
|
|
37
39
|
signal.throwIfAborted();
|
|
38
|
-
await validateGeneratedLockfile(targetDirectory, manifest);
|
|
40
|
+
await validateGeneratedLockfile(targetDirectory, manifest, databaseAdapters);
|
|
39
41
|
signal.throwIfAborted();
|
|
40
42
|
if (options.install) {
|
|
41
43
|
await runner(["ci", "--no-audit", "--no-fund"], targetDirectory, signal);
|
|
@@ -56,10 +58,14 @@ export async function generateSelfHostProject(options) {
|
|
|
56
58
|
throw error;
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
|
-
async function writeGeneratedFiles(targetDirectory, manifest, catalogue) {
|
|
61
|
+
async function writeGeneratedFiles(targetDirectory, manifest, catalogue, databaseAdapters) {
|
|
62
|
+
const databaseEngine = "databaseEngine" in manifest
|
|
63
|
+
? (manifest.databaseEngine ?? "postgresql")
|
|
64
|
+
: "postgresql";
|
|
60
65
|
const dependencyEntries = [
|
|
61
66
|
[manifest.core.package, manifest.core.version],
|
|
62
67
|
[manifest.generator.package, manifest.generator.version],
|
|
68
|
+
...databaseAdapters.map((adapter) => [adapter.package, adapter.version]),
|
|
63
69
|
...manifest.modules.map((module) => [module.package, module.version]),
|
|
64
70
|
];
|
|
65
71
|
dependencyEntries.sort(([left], [right]) => left.localeCompare(right, "en"));
|
|
@@ -85,20 +91,36 @@ async function writeGeneratedFiles(targetDirectory, manifest, catalogue) {
|
|
|
85
91
|
const moduleCatalogue = {
|
|
86
92
|
schemaVersion: 1,
|
|
87
93
|
packages: manifest.modules.map((module) => module.package),
|
|
94
|
+
...(databaseEngine === "mysql" ? { databaseEngine } : {}),
|
|
88
95
|
};
|
|
89
96
|
await writeFile(resolve(targetDirectory, "package.json"), formatJson(packageJson), "utf8");
|
|
90
97
|
await writeFile(resolve(targetDirectory, "helyx.modules.json"), formatJson(moduleCatalogue), "utf8");
|
|
91
98
|
for (const fileName of generatedFileNames) {
|
|
92
|
-
const template = await readFile(
|
|
99
|
+
const template = await readFile(fileName === "compose.yaml" && databaseEngine === "mysql"
|
|
100
|
+
? mysqlComposeTemplateUrl()
|
|
101
|
+
: templateUrl(fileName), "utf8");
|
|
102
|
+
const rendered = fileName === ".env.example"
|
|
103
|
+
? renderEnvironmentExample(template, databaseEngine)
|
|
104
|
+
: fileName === "README.md"
|
|
105
|
+
? renderReadme(template, manifest, catalogue, databaseEngine)
|
|
106
|
+
: template;
|
|
93
107
|
if (fileName === ".env.example")
|
|
94
|
-
validateEnvironmentTemplate(
|
|
95
|
-
const content =
|
|
96
|
-
? renderReadme(template, manifest, catalogue)
|
|
97
|
-
: normalizeCrLf(template);
|
|
108
|
+
validateEnvironmentTemplate(rendered, databaseEngine);
|
|
109
|
+
const content = normalizeCrLf(rendered);
|
|
98
110
|
await writeFile(resolve(targetDirectory, fileName), content, "utf8");
|
|
99
111
|
}
|
|
100
112
|
}
|
|
101
|
-
function
|
|
113
|
+
function renderEnvironmentExample(template, databaseEngine) {
|
|
114
|
+
if (databaseEngine === "postgresql")
|
|
115
|
+
return template;
|
|
116
|
+
return template
|
|
117
|
+
.replaceAll("\r\n", "\n")
|
|
118
|
+
.replace("# Docker: keep the URL. Direct Node.js/managed hosting: use your provider URL.\nDATABASE_ENGINE=postgresql\nDATABASE_URL=postgresql://database:5432/helyx", "# Use the MySQL-compatible database URL supplied by your provider.\n# The included Compose file runs only the bot; it never starts a second database.\nDATABASE_ENGINE=mysql\nDATABASE_URL=mysql://user:password@host:3306/helyx")
|
|
119
|
+
.replace(/# Included Docker database[^\n]*\nPGUSER=helyx\nPGPASSWORD=replace-with-a-strong-password\nPOSTGRES_DB=helyx\nPOSTGRES_USER=helyx\nPOSTGRES_PASSWORD=replace-with-a-strong-password\n\n/u, "")
|
|
120
|
+
.replace("HELYX_SHARD_MODE=off\n", "# MySQL-compatible self-hosting currently supports only this unsharded mode.\nHELYX_SHARD_MODE=off\n")
|
|
121
|
+
.replace("DATABASE_SSL=disable", "DATABASE_SSL=require");
|
|
122
|
+
}
|
|
123
|
+
function renderReadme(template, manifest, catalogue, databaseEngine) {
|
|
102
124
|
const modulesById = new Map(catalogue.modules.map((module) => [module.id, module]));
|
|
103
125
|
const selected = manifest.modules.length
|
|
104
126
|
? manifest.modules
|
|
@@ -111,7 +133,19 @@ function renderReadme(template, manifest, catalogue) {
|
|
|
111
133
|
.filter((line) => line !== null)
|
|
112
134
|
.join("\n")
|
|
113
135
|
: "- No feature modules selected. This is a core-only installation.";
|
|
114
|
-
|
|
136
|
+
let rendered = template
|
|
137
|
+
.replaceAll("\r\n", "\n")
|
|
138
|
+
.replace("{{SELECTED_MODULES}}", selected);
|
|
139
|
+
if (databaseEngine === "mysql") {
|
|
140
|
+
rendered = rendered
|
|
141
|
+
.replace("3. Edit `.env`, replace every placeholder, set `DATABASE_ENGINE` to `postgresql` or `mysql`, and set the matching `DATABASE_URL`. Omitting `DATABASE_ENGINE` selects PostgreSQL.", "3. Edit `.env`, replace every placeholder, keep `DATABASE_ENGINE=mysql` and `HELYX_SHARD_MODE=off`, and set `DATABASE_URL` to the MySQL-compatible URL supplied by your provider. Percent-encode special characters in URL credentials.")
|
|
142
|
+
.replace("Use this path when Docker with Compose support is available. The included configuration runs both Helyx and PostgreSQL 17.", "Use this path when Docker with Compose support is available. The included configuration runs only Helyx; it connects to your existing MySQL-compatible database and does not create a second database service.")
|
|
143
|
+
.replace("2. Edit `.env` and replace all placeholder credentials and passwords.\n3. Keep `DATABASE_ENGINE=postgresql` with the supplied internal `DATABASE_URL` and `DATABASE_SSL=disable`.\n4. Run `docker compose up --build -d`.\n5. Review startup with `docker compose logs -f bot`.\n6. Stop services with `docker compose down`. Do not add `--volumes` unless you deliberately intend to delete the database volume.", "2. Edit `.env` and replace all placeholder credentials. Keep `DATABASE_ENGINE=mysql` and supply your provider's reachable `DATABASE_URL` and required `DATABASE_SSL` setting.\n3. Run `docker compose up --build -d`.\n4. Review startup with `docker compose logs -f bot`.\n5. Stop the bot with `docker compose down`. Your external database is not managed by this Compose file.");
|
|
144
|
+
}
|
|
145
|
+
return rendered;
|
|
146
|
+
}
|
|
147
|
+
function mysqlComposeTemplateUrl() {
|
|
148
|
+
return new URL("../../templates/self-host/compose.mysql.yaml", import.meta.url);
|
|
115
149
|
}
|
|
116
150
|
function templateUrl(fileName) {
|
|
117
151
|
const templateName = fileName === ".npmrc"
|
|
@@ -130,19 +164,26 @@ function normalizeCrLf(value) {
|
|
|
130
164
|
.replaceAll("\r", "\n")
|
|
131
165
|
.replaceAll("\n", "\r\n");
|
|
132
166
|
}
|
|
133
|
-
function validateEnvironmentTemplate(template) {
|
|
167
|
+
function validateEnvironmentTemplate(template, databaseEngine) {
|
|
134
168
|
const lines = new Set(template
|
|
135
169
|
.replaceAll("\r\n", "\n")
|
|
136
170
|
.split("\n")
|
|
137
171
|
.map((line) => line.trim()));
|
|
138
172
|
for (const [key, definition] of Object.entries(selfHostEnvironmentContract)) {
|
|
139
|
-
const
|
|
173
|
+
const value = key === "DATABASE_ENGINE"
|
|
174
|
+
? databaseEngine
|
|
175
|
+
: key === "DATABASE_URL" && databaseEngine === "mysql"
|
|
176
|
+
? "mysql://user:password@host:3306/helyx"
|
|
177
|
+
: key === "DATABASE_SSL" && databaseEngine === "mysql"
|
|
178
|
+
? "require"
|
|
179
|
+
: definition.value;
|
|
180
|
+
const expected = `${definition.optional ? "# " : ""}${key}=${value}`;
|
|
140
181
|
if (!lines.has(expected)) {
|
|
141
182
|
throw new Error(`Self-host environment template does not match the bot contract: ${key}`);
|
|
142
183
|
}
|
|
143
184
|
}
|
|
144
185
|
}
|
|
145
|
-
async function validateGeneratedLockfile(targetDirectory, manifest) {
|
|
186
|
+
async function validateGeneratedLockfile(targetDirectory, manifest, databaseAdapters) {
|
|
146
187
|
const lockfilePath = resolve(targetDirectory, "package-lock.json");
|
|
147
188
|
const lockfile = JSON.parse(await readFile(lockfilePath, "utf8"));
|
|
148
189
|
if (!isRecord(lockfile) || !isRecord(lockfile.packages)) {
|
|
@@ -159,6 +200,7 @@ async function validateGeneratedLockfile(targetDirectory, manifest) {
|
|
|
159
200
|
const expected = new Map([
|
|
160
201
|
[manifest.core.package, manifest.core.version],
|
|
161
202
|
[manifest.generator.package, manifest.generator.version],
|
|
203
|
+
...databaseAdapters.map((adapter) => [adapter.package, adapter.version]),
|
|
162
204
|
...manifest.modules.map((module) => [module.package, module.version]),
|
|
163
205
|
]);
|
|
164
206
|
if (Object.keys(lockedDependencies).length !== expected.size ||
|
|
@@ -265,14 +307,17 @@ function isRecord(value) {
|
|
|
265
307
|
// This compile-time map intentionally stays adjacent to the generator. Adding a
|
|
266
308
|
// bot environment field makes the generated contract fail to compile until the
|
|
267
309
|
// self-host example receives an explicit safe value or omission decision.
|
|
268
|
-
//
|
|
269
|
-
// hosted-only and are deliberately excluded from generated
|
|
310
|
+
// Request My Data, support service authentication, staff delivery, and
|
|
311
|
+
// Verification remain hosted-only and are deliberately excluded from generated
|
|
312
|
+
// self-host projects.
|
|
270
313
|
export const selfHostEnvironmentContract = {
|
|
314
|
+
DATABASE_ENGINE: { value: "postgresql", optional: false },
|
|
271
315
|
DATABASE_URL: {
|
|
272
316
|
value: "postgresql://database:5432/helyx",
|
|
273
317
|
optional: false,
|
|
274
318
|
},
|
|
275
319
|
DATABASE_SSL: { value: "disable", optional: false },
|
|
320
|
+
HELYX_DATABASE_STARTUP_MODE: { value: "apply", optional: true },
|
|
276
321
|
HELYX_RUNTIME_PROFILE: { value: "self-hosted", optional: false },
|
|
277
322
|
HELYX_DEPLOYMENT_ENVIRONMENT: { value: "self-hosted", optional: false },
|
|
278
323
|
DISCORD_TOKEN: { value: "replace-me", optional: false },
|
|
@@ -324,16 +369,7 @@ export const selfHostEnvironmentContract = {
|
|
|
324
369
|
HELYX_GUILD_COMMAND_RELEASE_ID: { value: "", optional: true },
|
|
325
370
|
HELYX_GLOBAL_COMMAND_MODE: { value: "legacy", optional: false },
|
|
326
371
|
HELYX_GUILD_COMMAND_WRITE_GUILD_IDS: { value: "", optional: true },
|
|
327
|
-
HELYX_PRIVACY_ENABLED: { value: "false", optional: false },
|
|
328
372
|
HELYX_GUILD_MEMBERS_ENABLED: { value: "false", optional: false },
|
|
329
|
-
HELYX_PRIVACY_EXECUTOR_DATABASE_URL: {
|
|
330
|
-
value: "<dedicated-privacy-executor-postgresql-url>",
|
|
331
|
-
optional: true,
|
|
332
|
-
},
|
|
333
|
-
HELYX_PRIVACY_EXECUTOR_DATABASE_SSL: {
|
|
334
|
-
value: "disable",
|
|
335
|
-
optional: true,
|
|
336
|
-
},
|
|
337
373
|
HELYX_CLOUD_CONNECT_ENABLED: { value: "true", optional: false },
|
|
338
374
|
HELYX_CLOUD_ENROLMENT_TOKEN: {
|
|
339
375
|
value: "replace-with-a-short-lived-enrolment-code",
|
|
@@ -2,14 +2,15 @@ import { type SelfHostCatalogue } from "./catalogue.js";
|
|
|
2
2
|
export declare const DEFAULT_SELF_HOST_CATALOGUE_URL = "https://api.helyx.gg/self-host/catalogue";
|
|
3
3
|
export interface SelfHostUpdateChange {
|
|
4
4
|
package: string;
|
|
5
|
-
currentVersion: string;
|
|
6
|
-
targetVersion: string;
|
|
7
|
-
direction: "upgrade" | "downgrade";
|
|
5
|
+
currentVersion: string | null;
|
|
6
|
+
targetVersion: string | null;
|
|
7
|
+
direction: "add" | "upgrade" | "downgrade" | "remove";
|
|
8
8
|
}
|
|
9
9
|
export interface SelfHostUpdatePlan {
|
|
10
10
|
projectDirectory: string;
|
|
11
11
|
catalogueVersion: number;
|
|
12
12
|
changes: SelfHostUpdateChange[];
|
|
13
|
+
moduleSelectionChanged: boolean;
|
|
13
14
|
upToDate: boolean;
|
|
14
15
|
}
|
|
15
16
|
export type SelfHostUpdateCommandRunner = (arguments_: readonly string[], workingDirectory: string) => Promise<void>;
|
package/dist/self-host/update.js
CHANGED
|
@@ -4,7 +4,9 @@ import { spawn } from "node:child_process";
|
|
|
4
4
|
import { copyFile, mkdir, readFile, readdir, rename, stat, writeFile, } from "node:fs/promises";
|
|
5
5
|
import { parse, resolve } from "node:path";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
-
import { parseSelfHostCatalogue } from "./catalogue.js";
|
|
7
|
+
import { isSelfHostModuleCustomerSelectable, parseSelfHostCatalogue, selfHostDatabaseEngineSchema, } from "./catalogue.js";
|
|
8
|
+
import { databaseAdaptersForCatalogue } from "./database-adapters.js";
|
|
9
|
+
import { createSelfHostBuildManifest } from "./manifest.js";
|
|
8
10
|
export const DEFAULT_SELF_HOST_CATALOGUE_URL = "https://api.helyx.gg/self-host/catalogue";
|
|
9
11
|
const maximumCatalogueBytes = 512 * 1_024;
|
|
10
12
|
const catalogueTimeoutMilliseconds = 15_000;
|
|
@@ -24,7 +26,8 @@ const packageJsonSchema = z
|
|
|
24
26
|
const moduleSelectionSchema = z
|
|
25
27
|
.object({
|
|
26
28
|
schemaVersion: z.literal(1),
|
|
27
|
-
packages: z.array(modulePackage)
|
|
29
|
+
packages: z.array(modulePackage),
|
|
30
|
+
databaseEngine: selfHostDatabaseEngineSchema.optional(),
|
|
28
31
|
})
|
|
29
32
|
.strict()
|
|
30
33
|
.superRefine((value, context) => {
|
|
@@ -53,14 +56,16 @@ export async function updateSelfHostProject(options = {}) {
|
|
|
53
56
|
? Promise.resolve(options.catalogue)
|
|
54
57
|
: (options.catalogueLoader ?? loadCurrentSelfHostCatalogue)(),
|
|
55
58
|
]);
|
|
56
|
-
const
|
|
57
|
-
validateManagedDependencies(packageJson.dependencies,
|
|
58
|
-
const changes = collectChanges(packageJson.dependencies,
|
|
59
|
+
const target = targetHelyxDependencies(catalogue, selection);
|
|
60
|
+
validateManagedDependencies(packageJson.dependencies, target.dependencies, selection.packages, target.addedDependencyPackages);
|
|
61
|
+
const changes = collectChanges(packageJson.dependencies, target.dependencies);
|
|
62
|
+
const selectionChanged = JSON.stringify(selection.packages) !== JSON.stringify(target.packages);
|
|
59
63
|
const plan = {
|
|
60
64
|
projectDirectory,
|
|
61
65
|
catalogueVersion: catalogue.catalogueVersion,
|
|
62
66
|
changes,
|
|
63
|
-
|
|
67
|
+
moduleSelectionChanged: selectionChanged,
|
|
68
|
+
upToDate: changes.length === 0 && !selectionChanged,
|
|
64
69
|
};
|
|
65
70
|
if (!options.apply || plan.upToDate)
|
|
66
71
|
return plan;
|
|
@@ -75,11 +80,16 @@ export async function updateSelfHostProject(options = {}) {
|
|
|
75
80
|
update: "helyx self-host update --confirm && npm ci",
|
|
76
81
|
},
|
|
77
82
|
dependencies: {
|
|
78
|
-
...packageJson.dependencies,
|
|
79
|
-
...Object.fromEntries(
|
|
83
|
+
...Object.fromEntries(Object.entries(packageJson.dependencies).filter(([name]) => !name.startsWith("@helyx/"))),
|
|
84
|
+
...Object.fromEntries(target.dependencies),
|
|
80
85
|
},
|
|
81
86
|
};
|
|
82
87
|
await atomicWriteJson(resolve(projectDirectory, "package.json"), nextPackageJson);
|
|
88
|
+
if (selectionChanged)
|
|
89
|
+
await atomicWriteJson(resolve(projectDirectory, "helyx.modules.json"), {
|
|
90
|
+
...selection,
|
|
91
|
+
packages: target.packages,
|
|
92
|
+
});
|
|
83
93
|
const runner = options.commandRunner ?? runNpmCommand;
|
|
84
94
|
await runner([
|
|
85
95
|
"install",
|
|
@@ -88,7 +98,7 @@ export async function updateSelfHostProject(options = {}) {
|
|
|
88
98
|
"--no-audit",
|
|
89
99
|
"--no-fund",
|
|
90
100
|
], projectDirectory);
|
|
91
|
-
await validateUpdatedLockfile(projectDirectory,
|
|
101
|
+
await validateUpdatedLockfile(projectDirectory, target.dependencies);
|
|
92
102
|
await writeChecksums(projectDirectory);
|
|
93
103
|
return plan;
|
|
94
104
|
}
|
|
@@ -185,41 +195,74 @@ function targetHelyxDependencies(catalogue, selection) {
|
|
|
185
195
|
module.version === null) {
|
|
186
196
|
throw new Error(`Selected module is not in the current reviewed free live release: ${selectedPackage}`);
|
|
187
197
|
}
|
|
188
|
-
return
|
|
198
|
+
return module;
|
|
189
199
|
});
|
|
190
|
-
|
|
200
|
+
if (selection.databaseEngine === "mysql" && catalogue.schemaVersion !== 2) {
|
|
201
|
+
throw new Error("The reviewed self-host catalogue lacks MySQL compatibility metadata");
|
|
202
|
+
}
|
|
203
|
+
const modules = catalogue.schemaVersion === 2
|
|
204
|
+
? createSelfHostBuildManifest(catalogue, selected
|
|
205
|
+
.filter(isSelfHostModuleCustomerSelectable)
|
|
206
|
+
.map(({ id }) => id), selection.databaseEngine ?? "postgresql").modules
|
|
207
|
+
: selected;
|
|
208
|
+
const dependencies = new Map([
|
|
191
209
|
[catalogue.core.package, catalogue.core.version],
|
|
192
210
|
[catalogue.generator.package, catalogue.generator.version],
|
|
193
|
-
...
|
|
211
|
+
...databaseAdaptersForCatalogue(catalogue).map((adapter) => [adapter.package, adapter.version]),
|
|
212
|
+
...modules.map((module) => [module.package, module.version]),
|
|
194
213
|
]);
|
|
214
|
+
return {
|
|
215
|
+
dependencies,
|
|
216
|
+
packages: modules.map(({ package: name }) => name),
|
|
217
|
+
addedDependencyPackages: modules
|
|
218
|
+
.filter((module) => "moduleKind" in module &&
|
|
219
|
+
module.moduleKind === "internal_dependency" &&
|
|
220
|
+
!selection.packages.includes(module.package))
|
|
221
|
+
.map(({ package: name }) => name),
|
|
222
|
+
};
|
|
195
223
|
}
|
|
196
|
-
function validateManagedDependencies(current, target) {
|
|
224
|
+
function validateManagedDependencies(current, target, selectedPackages, addedDependencyPackages) {
|
|
197
225
|
for (const [name, version] of Object.entries(current)) {
|
|
198
226
|
if (!name.startsWith("@helyx/"))
|
|
199
227
|
continue;
|
|
200
228
|
packageName.parse(name);
|
|
201
229
|
exactVersion.parse(version);
|
|
202
|
-
if (!target.has(name)) {
|
|
230
|
+
if (!target.has(name) && !selectedPackages.includes(name)) {
|
|
203
231
|
throw new Error(`Unexpected Helyx dependency is not controlled by helyx.modules.json: ${name}`);
|
|
204
232
|
}
|
|
205
233
|
}
|
|
206
234
|
for (const name of target.keys()) {
|
|
207
|
-
if (!(name in current)
|
|
235
|
+
if (!(name in current) &&
|
|
236
|
+
!addedDependencyPackages.includes(name) &&
|
|
237
|
+
name !== "@helyx/database" &&
|
|
238
|
+
name !== "@helyx/database-mysql") {
|
|
208
239
|
throw new Error(`Generated project is missing Helyx dependency: ${name}`);
|
|
209
240
|
}
|
|
210
241
|
}
|
|
211
242
|
}
|
|
212
243
|
function collectChanges(current, target) {
|
|
213
|
-
|
|
214
|
-
.
|
|
215
|
-
.
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
244
|
+
const names = new Set([
|
|
245
|
+
...target.keys(),
|
|
246
|
+
...Object.keys(current).filter((name) => name.startsWith("@helyx/")),
|
|
247
|
+
]);
|
|
248
|
+
return [...names]
|
|
249
|
+
.filter((name) => current[name] !== target.get(name))
|
|
250
|
+
.map((name) => {
|
|
251
|
+
const version = target.get(name) ?? null;
|
|
252
|
+
const currentVersion = current[name] ?? null;
|
|
253
|
+
return {
|
|
254
|
+
package: name,
|
|
255
|
+
currentVersion,
|
|
256
|
+
targetVersion: version,
|
|
257
|
+
direction: version === null
|
|
258
|
+
? "remove"
|
|
259
|
+
: currentVersion === null
|
|
260
|
+
? "add"
|
|
261
|
+
: semver.gt(version, currentVersion)
|
|
262
|
+
? "upgrade"
|
|
263
|
+
: "downgrade",
|
|
264
|
+
};
|
|
265
|
+
})
|
|
223
266
|
.sort((left, right) => left.package.localeCompare(right.package, "en"));
|
|
224
267
|
}
|
|
225
268
|
async function readProjectJson(projectDirectory) {
|
|
@@ -277,6 +320,10 @@ async function validateUpdatedLockfile(projectDirectory, target) {
|
|
|
277
320
|
throw new Error(`Updated package-lock.json does not pin ${name}@${version}`);
|
|
278
321
|
}
|
|
279
322
|
}
|
|
323
|
+
for (const name of Object.keys(root.dependencies)) {
|
|
324
|
+
if (name.startsWith("@helyx/") && !target.has(name))
|
|
325
|
+
throw new Error(`Updated package-lock.json retains ${name}`);
|
|
326
|
+
}
|
|
280
327
|
}
|
|
281
328
|
async function writeChecksums(projectDirectory) {
|
|
282
329
|
const fileNames = (await readdir(projectDirectory))
|
package/dist/setup.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ export interface SetupSelfHostEnvironmentResult {
|
|
|
7
7
|
created: boolean;
|
|
8
8
|
dataEncryptionKeyCreated: boolean;
|
|
9
9
|
dockerComposeAvailable: boolean;
|
|
10
|
+
databaseEngine: "postgresql" | "mysql";
|
|
10
11
|
environmentPath: string;
|
|
11
12
|
}
|
|
12
13
|
export declare function setupSelfHostEnvironment(options?: SetupSelfHostEnvironmentOptions): Promise<SetupSelfHostEnvironmentResult>;
|
package/dist/setup.js
CHANGED
|
@@ -12,6 +12,11 @@ export async function setupSelfHostEnvironment(options = {}) {
|
|
|
12
12
|
await assertGeneratedStarter(cwd);
|
|
13
13
|
const examplePath = resolve(cwd, ".env.example");
|
|
14
14
|
const environmentPath = resolve(cwd, ".env");
|
|
15
|
+
const example = parseEnv(await readFile(examplePath, "utf8"));
|
|
16
|
+
const databaseEngine = example.DATABASE_ENGINE ?? "postgresql";
|
|
17
|
+
if (databaseEngine !== "postgresql" && databaseEngine !== "mysql") {
|
|
18
|
+
throw new Error("The starter has an unsupported DATABASE_ENGINE value");
|
|
19
|
+
}
|
|
15
20
|
let created = false;
|
|
16
21
|
try {
|
|
17
22
|
await copyFile(examplePath, environmentPath, constants.COPYFILE_EXCL);
|
|
@@ -27,6 +32,7 @@ export async function setupSelfHostEnvironment(options = {}) {
|
|
|
27
32
|
created,
|
|
28
33
|
dataEncryptionKeyCreated,
|
|
29
34
|
dockerComposeAvailable: await (options.detectDockerCompose ?? detectDockerCompose)(),
|
|
35
|
+
databaseEngine,
|
|
30
36
|
environmentPath,
|
|
31
37
|
};
|
|
32
38
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@helyx/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.4.0",
|
|
4
4
|
"description": "Command-line tools for running and generating self-hosted Helyx deployments.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"helyx",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|
|
27
27
|
"access": "public",
|
|
28
|
-
"provenance":
|
|
28
|
+
"provenance": false
|
|
29
29
|
},
|
|
30
30
|
"type": "module",
|
|
31
31
|
"files": [
|
|
@@ -52,12 +52,15 @@
|
|
|
52
52
|
"build": "tsc -b"
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@helyx/bot": ">=1.
|
|
56
|
-
"@helyx/config": ">=1.
|
|
57
|
-
"@helyx/database": ">=2.
|
|
55
|
+
"@helyx/bot": ">=1.4.0 <2.0.0",
|
|
56
|
+
"@helyx/config": ">=1.4.0 <2.0.0",
|
|
57
|
+
"@helyx/database": ">=2.4.0 <3.0.0",
|
|
58
58
|
"semver": "7.8.5",
|
|
59
59
|
"zod": "4.4.3"
|
|
60
60
|
},
|
|
61
|
+
"optionalDependencies": {
|
|
62
|
+
"@helyx/database-mysql": ">=0.2.0 <0.3.0"
|
|
63
|
+
},
|
|
61
64
|
"devDependencies": {
|
|
62
65
|
"@types/semver": "7.7.1"
|
|
63
66
|
}
|