@hot-updater/cli-tools 1.0.0-rc.43 → 1.0.0-rc.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +10 -8
- package/dist/index.mjs +168 -38
- package/package.json +5 -5
package/dist/index.d.mts
CHANGED
|
@@ -79,7 +79,7 @@ declare const printAppSetup: ({ baseURL, ...setup }: AppSetup & {
|
|
|
79
79
|
//#region src/assembleServer.d.ts
|
|
80
80
|
/**
|
|
81
81
|
* The server hot-updater.config.ts describes, as the CLI runs it: core and
|
|
82
|
-
* the plugins over the config's database
|
|
82
|
+
* the plugins over the config's database.
|
|
83
83
|
*/
|
|
84
84
|
interface AssembledServer {
|
|
85
85
|
/** The config's database: the server's own, or `standaloneRepository(...)`. */
|
|
@@ -91,8 +91,6 @@ interface AssembledServer {
|
|
|
91
91
|
* `standaloneRepository`, they go through the server's admin API.
|
|
92
92
|
*/
|
|
93
93
|
readonly core: HotUpdaterCoreApi;
|
|
94
|
-
/** Where the CLI uploads bundles; undefined when the config names none. */
|
|
95
|
-
readonly storage: StorageAdapter | undefined;
|
|
96
94
|
/** The plugins as configured, as a frozen copy. */
|
|
97
95
|
readonly plugins: readonly AnyHotUpdaterPlugin[];
|
|
98
96
|
/** Each plugin's API by id; undefined over standaloneRepository, whose plugins run on the server. */
|
|
@@ -109,6 +107,11 @@ interface AssembledServer {
|
|
|
109
107
|
/** What `assembleServer` runs: hot-updater.config.ts's `database`, `storage`, and `plugins`. */
|
|
110
108
|
interface AssembleServerOptions {
|
|
111
109
|
readonly database: ConfiguredDatabase;
|
|
110
|
+
/**
|
|
111
|
+
* The config's storage, through which core resolves file URLs and the
|
|
112
|
+
* plugins read stored files. Tooling that does neither, such as init
|
|
113
|
+
* provisioning a credential, leaves it out.
|
|
114
|
+
*/
|
|
112
115
|
readonly storage?: StorageAdapter;
|
|
113
116
|
readonly plugins?: readonly AnyHotUpdaterPlugin[];
|
|
114
117
|
}
|
|
@@ -194,7 +197,7 @@ declare class ConfigBuilder {
|
|
|
194
197
|
setStorage(storageConfig: ProviderConfig): this;
|
|
195
198
|
/** Sets the server's database and its imports. */
|
|
196
199
|
setDatabase(databaseConfig: ProviderConfig): this;
|
|
197
|
-
/** Sets the
|
|
200
|
+
/** Sets the server's plugin factory array and its imports. */
|
|
198
201
|
setPlugins(pluginsConfig: ProviderConfig): this;
|
|
199
202
|
/** Sets the code between the environment loading and the config, such as a credentials helper. */
|
|
200
203
|
setIntermediateCode(code: string): this;
|
|
@@ -343,7 +346,7 @@ type HotUpdaterConfigScaffold = {
|
|
|
343
346
|
initializer: string;
|
|
344
347
|
callee: string;
|
|
345
348
|
};
|
|
346
|
-
/** The
|
|
349
|
+
/** The server's plugin factory array. */
|
|
347
350
|
plugins: {
|
|
348
351
|
initializer: string;
|
|
349
352
|
};
|
|
@@ -367,7 +370,8 @@ declare const writeHotUpdaterConfig: (scaffold: HotUpdaterConfigScaffold, filePa
|
|
|
367
370
|
* Writes hot-updater.config.ts and says what it did. `settings` names the
|
|
368
371
|
* provider in messages, such as "Supabase". A config it cannot merge is
|
|
369
372
|
* kept, with what to add to it. The plugins file an older init wrote is
|
|
370
|
-
* removed
|
|
373
|
+
* removed only after a successful config write and when the config no longer
|
|
374
|
+
* references it. A file the project wrote is kept with a cleanup notice.
|
|
371
375
|
*/
|
|
372
376
|
declare const writeHotUpdaterFiles: (scaffold: HotUpdaterConfigScaffold, { cwd, settings }: {
|
|
373
377
|
cwd?: string;
|
|
@@ -573,8 +577,6 @@ interface ServerClientEndpoint {
|
|
|
573
577
|
interface ServerDefinition {
|
|
574
578
|
/** The database as configured, with the tooling `hot-updater db` runs. */
|
|
575
579
|
readonly database: ToolingDatabase;
|
|
576
|
-
/** The storage as configured, in order. */
|
|
577
|
-
readonly storage: readonly StorageAdapter[];
|
|
578
580
|
readonly plugins: readonly AnyHotUpdaterPlugin[];
|
|
579
581
|
/** The client plugins an app adds for the plugins: what init prints. */
|
|
580
582
|
readonly clientPlugins: readonly PluginClientPlugin[];
|
package/dist/index.mjs
CHANGED
|
@@ -16,7 +16,7 @@ import l__default from "node:readline";
|
|
|
16
16
|
import tty, { ReadStream } from "node:tty";
|
|
17
17
|
import Ei, { appendFileSync, createReadStream, createWriteStream, existsSync, lstatSync, readFileSync, readdirSync, statSync, writeFileSync } from "node:fs";
|
|
18
18
|
import path$1, { basename, dirname as dirname$1, join, posix, win32 } from "node:path";
|
|
19
|
-
import { createBundleStorageKey, createMemoryAdapter, createStorageRootUriWithPath, getContentType, getManifestAssetDownloadPath, getManifestAssetStoragePath, isContentAddressedAssetFileHash, isRemoteDatabase, resolveManifestAssetStorageUri, toolingTargetOf } from "@hot-updater/plugin-core";
|
|
19
|
+
import { createBundleStorageKey, createMemoryAdapter, createStorageAdapter, createStorageRootUriWithPath, getContentType, getManifestAssetDownloadPath, getManifestAssetStoragePath, isContentAddressedAssetFileHash, isRemoteDatabase, resolveManifestAssetStorageUri, toolingTargetOf } from "@hot-updater/plugin-core";
|
|
20
20
|
import { createHotUpdater } from "@hot-updater/server";
|
|
21
21
|
import os, { constants } from "node:os";
|
|
22
22
|
import crypto$1, { createHash, randomBytes, randomUUID } from "node:crypto";
|
|
@@ -22970,6 +22970,11 @@ const printAppSetup = ({ baseURL, ...setup }) => {
|
|
|
22970
22970
|
};
|
|
22971
22971
|
//#endregion
|
|
22972
22972
|
//#region src/assembleServer.ts
|
|
22973
|
+
/** The storage of an assembly that reads no stored file: it owns no URI. */
|
|
22974
|
+
const noStorage = createStorageAdapter({
|
|
22975
|
+
name: "none",
|
|
22976
|
+
protocol: "none"
|
|
22977
|
+
});
|
|
22973
22978
|
/** `createHotUpdater`, with public client routes unless a plugin provides clientAuth. */
|
|
22974
22979
|
const createServer = (database, storage, plugins) => createHotUpdater({
|
|
22975
22980
|
database,
|
|
@@ -22977,27 +22982,25 @@ const createServer = (database, storage, plugins) => createHotUpdater({
|
|
|
22977
22982
|
plugins,
|
|
22978
22983
|
...plugins.some((plugin) => plugin.provides?.clientAuth) ? {} : { clientAccess: "public" }
|
|
22979
22984
|
});
|
|
22980
|
-
function assembleServer({ database, storage, plugins = [] }) {
|
|
22985
|
+
function assembleServer({ database, storage = noStorage, plugins = [] }) {
|
|
22981
22986
|
if (isRemoteDatabase(database)) {
|
|
22982
22987
|
const shape = createServer({
|
|
22983
22988
|
name: "memory",
|
|
22984
22989
|
adapter: createMemoryAdapter()
|
|
22985
|
-
},
|
|
22990
|
+
}, storage, plugins);
|
|
22986
22991
|
return {
|
|
22987
22992
|
database,
|
|
22988
22993
|
core: database.core,
|
|
22989
|
-
storage,
|
|
22990
22994
|
plugins: shape.plugins,
|
|
22991
22995
|
api: void 0,
|
|
22992
22996
|
clientPlugins: shape.clientPlugins,
|
|
22993
22997
|
...shape.clientAuth === void 0 ? {} : { clientAuth: shape.clientAuth }
|
|
22994
22998
|
};
|
|
22995
22999
|
}
|
|
22996
|
-
const server = createServer(database, storage
|
|
23000
|
+
const server = createServer(database, storage, plugins);
|
|
22997
23001
|
return {
|
|
22998
23002
|
database,
|
|
22999
23003
|
core: server.core,
|
|
23000
|
-
storage,
|
|
23001
23004
|
plugins: server.plugins,
|
|
23002
23005
|
api: server.api,
|
|
23003
23006
|
clientPlugins: server.clientPlugins,
|
|
@@ -25184,7 +25187,7 @@ var ConfigBuilder = class {
|
|
|
25184
25187
|
this.imports.push(...databaseConfig.imports);
|
|
25185
25188
|
return this;
|
|
25186
25189
|
}
|
|
25187
|
-
/** Sets the
|
|
25190
|
+
/** Sets the server's plugin factory array and its imports. */
|
|
25188
25191
|
setPlugins(pluginsConfig) {
|
|
25189
25192
|
this.pluginsInfo = pluginsConfig;
|
|
25190
25193
|
this.imports.push(...pluginsConfig.imports);
|
|
@@ -44123,6 +44126,7 @@ const MANAGED_IMPORT_PACKAGES = /* @__PURE__ */ new Set([
|
|
|
44123
44126
|
"firebase-admin",
|
|
44124
44127
|
"firebase-admin/app",
|
|
44125
44128
|
"hot-updater",
|
|
44129
|
+
"hot-updater/plugins",
|
|
44126
44130
|
"@aws-sdk/credential-provider-sso",
|
|
44127
44131
|
"@aws-sdk/credential-providers",
|
|
44128
44132
|
"@hot-updater/aws",
|
|
@@ -44131,6 +44135,7 @@ const MANAGED_IMPORT_PACKAGES = /* @__PURE__ */ new Set([
|
|
|
44131
44135
|
"@hot-updater/expo",
|
|
44132
44136
|
"@hot-updater/firebase",
|
|
44133
44137
|
"@hot-updater/rock",
|
|
44138
|
+
"@hot-updater/server/plugins",
|
|
44134
44139
|
"@hot-updater/supabase"
|
|
44135
44140
|
]);
|
|
44136
44141
|
const MANAGED_HELPER_NAMES = /* @__PURE__ */ new Set([
|
|
@@ -44149,7 +44154,7 @@ const BUILD_IMPORT_PACKAGES = new Set([...KNOWN_BUILD_CALLEES].map((callee) => `
|
|
|
44149
44154
|
const importDeclarationsOf = (source) => source.program.body.filter((statement) => statement.type === "ImportDeclaration");
|
|
44150
44155
|
const parseConfigSource = (text) => {
|
|
44151
44156
|
const result = parseSync(CONFIG_FILE_NAME, text, {
|
|
44152
|
-
astType: "
|
|
44157
|
+
astType: "ts",
|
|
44153
44158
|
lang: "ts",
|
|
44154
44159
|
preserveParens: false,
|
|
44155
44160
|
sourceType: "module",
|
|
@@ -44316,21 +44321,78 @@ const mergeObjectLiteralText = (existingObject, newObject, replaceIncompatiblePr
|
|
|
44316
44321
|
}).map((property) => getPropertyBlockText(newObject.source, property));
|
|
44317
44322
|
return applyTextEdits(existingText, [...edits, ...appendPropertiesEdits(existingObject.source, existingObject.objectExpression, missingPropertyTexts)]);
|
|
44318
44323
|
};
|
|
44319
|
-
|
|
44320
|
-
|
|
44324
|
+
/**
|
|
44325
|
+
* The edit that deletes `property` from its object: with its comma, and with
|
|
44326
|
+
* the rest of its line when the property starts one.
|
|
44327
|
+
*/
|
|
44328
|
+
const removePropertyEdit = (text, property) => {
|
|
44329
|
+
const comma = /^[ \t]*,/.exec(text.slice(property.end));
|
|
44330
|
+
const end = property.end + (comma?.[0].length ?? 0);
|
|
44331
|
+
if (startsLine(text, property)) {
|
|
44332
|
+
const rest = /^[ \t]*(?:\/\/[^\n]*)?\n/.exec(text.slice(end));
|
|
44333
|
+
return {
|
|
44334
|
+
start: lineStartOf(text, property.start),
|
|
44335
|
+
end: end + (rest?.[0].length ?? 0),
|
|
44336
|
+
text: ""
|
|
44337
|
+
};
|
|
44338
|
+
}
|
|
44339
|
+
const space = /^[ \t]*/.exec(text.slice(end));
|
|
44340
|
+
return {
|
|
44341
|
+
start: property.start,
|
|
44342
|
+
end: end + (space?.[0].length ?? 0),
|
|
44343
|
+
text: ""
|
|
44344
|
+
};
|
|
44345
|
+
};
|
|
44346
|
+
const parseCall = (text) => {
|
|
44347
|
+
const source = parseConfigSource(`(${text});`);
|
|
44348
|
+
const statement = source?.program.body[0];
|
|
44349
|
+
return source && statement?.type === "ExpressionStatement" && statement.expression.type === "CallExpression" ? {
|
|
44350
|
+
callExpression: statement.expression,
|
|
44351
|
+
source
|
|
44352
|
+
} : null;
|
|
44353
|
+
};
|
|
44354
|
+
/**
|
|
44355
|
+
* The project's call without its spreads of the managed helpers the rebuilt
|
|
44356
|
+
* config no longer declares.
|
|
44357
|
+
*/
|
|
44358
|
+
const withoutDroppedHelpers = (call, droppedHelpers) => {
|
|
44359
|
+
const [argument] = call.callExpression.arguments;
|
|
44360
|
+
if (call.callExpression.arguments.length !== 1 || argument?.type !== "ObjectExpression") return call;
|
|
44361
|
+
const callStart = call.callExpression.start;
|
|
44362
|
+
const edits = argument.properties.filter((property) => property.type === "SpreadElement" && property.argument.type === "Identifier" && droppedHelpers.has(property.argument.name)).map((property) => {
|
|
44363
|
+
const edit = removePropertyEdit(call.source.text, property);
|
|
44364
|
+
return {
|
|
44365
|
+
...edit,
|
|
44366
|
+
start: edit.start - callStart,
|
|
44367
|
+
end: edit.end - callStart
|
|
44368
|
+
};
|
|
44369
|
+
});
|
|
44370
|
+
if (edits.length === 0) return call;
|
|
44371
|
+
return parseCall(applyTextEdits(getNodeText(call.source, call.callExpression), edits));
|
|
44372
|
+
};
|
|
44373
|
+
/** Whether `text` uses one of `names`; text that doesn't parse counts as a use. */
|
|
44374
|
+
const usesAny = (text, names) => {
|
|
44375
|
+
if (names.size === 0) return false;
|
|
44376
|
+
const used = usedIdentifiers(`(${text});`);
|
|
44377
|
+
return used === null || [...names].some((name) => used.has(name));
|
|
44378
|
+
};
|
|
44379
|
+
const buildMergedCallInitializer = (existing, next, droppedHelpers) => {
|
|
44380
|
+
const project = withoutDroppedHelpers(existing, droppedHelpers);
|
|
44381
|
+
if (!project) return null;
|
|
44382
|
+
const [existingArgument] = project.callExpression.arguments;
|
|
44321
44383
|
const [nextArgument] = next.callExpression.arguments;
|
|
44322
|
-
if (
|
|
44384
|
+
if (project.callExpression.arguments.length === 1 && next.callExpression.arguments.length === 1 && existingArgument?.type === "ObjectExpression" && nextArgument?.type === "ObjectExpression") {
|
|
44323
44385
|
const mergedObjectLiteral = mergeObjectLiteralText({
|
|
44324
44386
|
objectExpression: existingArgument,
|
|
44325
|
-
source:
|
|
44387
|
+
source: project.source
|
|
44326
44388
|
}, {
|
|
44327
44389
|
objectExpression: nextArgument,
|
|
44328
44390
|
source: next.source
|
|
44329
44391
|
});
|
|
44330
44392
|
if (!mergedObjectLiteral) return null;
|
|
44331
|
-
return `${getNodeText(
|
|
44393
|
+
return `${getNodeText(project.source, project.callExpression.callee)}(${mergedObjectLiteral})`;
|
|
44332
44394
|
}
|
|
44333
|
-
return getNodeText(
|
|
44395
|
+
return getNodeText(project.source, project.callExpression);
|
|
44334
44396
|
};
|
|
44335
44397
|
const findManagedProperty = (objectExpression, propertyName) => {
|
|
44336
44398
|
const property = objectExpression.properties.find((candidate) => candidate.type === "Property" && candidate.kind === "init" && !candidate.method && !candidate.shorthand && getObjectPropertyName(candidate) === propertyName);
|
|
@@ -44369,10 +44431,12 @@ const mergeHelperStatement = (existingStatementText, helper) => {
|
|
|
44369
44431
|
* `plugins`: a call to the same adapter keeps the project's arguments and
|
|
44370
44432
|
* gains the scaffold's missing ones, and `plugins` is the scaffold's. A build
|
|
44371
44433
|
* that is not a plain build adapter, such as `withSentry(bare())`, stays the
|
|
44372
|
-
* project's (`keptBuild`).
|
|
44373
|
-
*
|
|
44434
|
+
* project's (`keptBuild`). The merged call loses its spreads of the managed
|
|
44435
|
+
* helpers the rebuilt config no longer declares (`droppedHelpers`), and a
|
|
44436
|
+
* call that still uses one takes the scaffold's. Null when `build`,
|
|
44437
|
+
* `storage`, or `database` is no call.
|
|
44374
44438
|
*/
|
|
44375
|
-
const updateManagedObject = (existing, next) => {
|
|
44439
|
+
const updateManagedObject = (existing, next, droppedHelpers) => {
|
|
44376
44440
|
const objectStart = existing.objectExpression.start;
|
|
44377
44441
|
const objectText = getNodeText(existing.source, existing.objectExpression);
|
|
44378
44442
|
const propertyEdits = [];
|
|
@@ -44408,9 +44472,9 @@ const updateManagedObject = (existing, next) => {
|
|
|
44408
44472
|
}, {
|
|
44409
44473
|
callExpression: nextProperty.value,
|
|
44410
44474
|
source: next.source
|
|
44411
|
-
});
|
|
44475
|
+
}, droppedHelpers);
|
|
44412
44476
|
if (!mergedInitializer) return null;
|
|
44413
|
-
nextInitializerText = mergedInitializer;
|
|
44477
|
+
if (!usesAny(mergedInitializer, droppedHelpers)) nextInitializerText = mergedInitializer;
|
|
44414
44478
|
}
|
|
44415
44479
|
propertyEdits.push({
|
|
44416
44480
|
start: existingProperty.value.start - objectStart,
|
|
@@ -44440,8 +44504,25 @@ const getManagedHelperName = (statement) => {
|
|
|
44440
44504
|
if (declaration?.id.type !== "Identifier") return null;
|
|
44441
44505
|
return declaration.id.name;
|
|
44442
44506
|
};
|
|
44443
|
-
/**
|
|
44444
|
-
const
|
|
44507
|
+
/** Identifier uses, excluding static property names, strings, and comments. */
|
|
44508
|
+
const usedIdentifiers = (text) => {
|
|
44509
|
+
const source = parseConfigSource(text);
|
|
44510
|
+
if (!source) return null;
|
|
44511
|
+
const names = /* @__PURE__ */ new Set();
|
|
44512
|
+
const propertyNames = /* @__PURE__ */ new Set();
|
|
44513
|
+
new Visitor({
|
|
44514
|
+
Property(property) {
|
|
44515
|
+
if (!property.computed && !property.shorthand) propertyNames.add(property.key.start);
|
|
44516
|
+
},
|
|
44517
|
+
MemberExpression(member) {
|
|
44518
|
+
if (!member.computed) propertyNames.add(member.property.start);
|
|
44519
|
+
},
|
|
44520
|
+
Identifier(identifier) {
|
|
44521
|
+
if (!propertyNames.has(identifier.start)) names.add(identifier.name);
|
|
44522
|
+
}
|
|
44523
|
+
}).visit(source.program);
|
|
44524
|
+
return names;
|
|
44525
|
+
};
|
|
44445
44526
|
/**
|
|
44446
44527
|
* What a managed package's existing imports bring that the rebuilt config
|
|
44447
44528
|
* still uses and the scaffold doesn't import, such as a project's own `cert`
|
|
@@ -44453,6 +44534,7 @@ const keptManagedImports = (declarations, scaffoldImports, usedText) => {
|
|
|
44453
44534
|
const bound = new Set(scaffoldImports.flatMap((info) => [...(info.named ?? []).map((name) => name.split(/\s+as\s+/).at(-1)), ...info.defaultOrNamespace === void 0 ? [] : [info.defaultOrNamespace.replace(/^\*\s+as\s+/, "")]]));
|
|
44454
44535
|
const imports = [];
|
|
44455
44536
|
const texts = [];
|
|
44537
|
+
const references = usedIdentifiers(usedText);
|
|
44456
44538
|
for (const declaration of declarations) {
|
|
44457
44539
|
const pkg = declaration.source.value;
|
|
44458
44540
|
const named = [];
|
|
@@ -44460,7 +44542,7 @@ const keptManagedImports = (declarations, scaffoldImports, usedText) => {
|
|
|
44460
44542
|
let namespaceName;
|
|
44461
44543
|
for (const specifier of declaration.specifiers) {
|
|
44462
44544
|
const local = specifier.local.name;
|
|
44463
|
-
if (bound.has(local) || !
|
|
44545
|
+
if (bound.has(local) || references && !references.has(local)) continue;
|
|
44464
44546
|
if (specifier.type === "ImportDefaultSpecifier") defaultName = local;
|
|
44465
44547
|
else if (specifier.type === "ImportNamespaceSpecifier") namespaceName = local;
|
|
44466
44548
|
else {
|
|
@@ -44471,7 +44553,9 @@ const keptManagedImports = (declarations, scaffoldImports, usedText) => {
|
|
|
44471
44553
|
}
|
|
44472
44554
|
if (named.length === 0 && !defaultName && !namespaceName) continue;
|
|
44473
44555
|
if (declaration.importKind === "type") {
|
|
44474
|
-
texts.push(`import type { ${named.join(", ")} } from "${pkg}";`);
|
|
44556
|
+
if (named.length > 0) texts.push(`import type { ${named.join(", ")} } from "${pkg}";`);
|
|
44557
|
+
if (namespaceName) texts.push(`import type * as ${namespaceName} from "${pkg}";`);
|
|
44558
|
+
if (defaultName) texts.push(`import type ${defaultName} from "${pkg}";`);
|
|
44475
44559
|
continue;
|
|
44476
44560
|
}
|
|
44477
44561
|
if (named.length > 0) imports.push({
|
|
@@ -44553,28 +44637,26 @@ const mergeHotUpdaterConfigText = (existingText, scaffold) => {
|
|
|
44553
44637
|
const existingConfig = findDefineConfigObject(existingSource);
|
|
44554
44638
|
const nextConfig = findDefineConfigObject(nextSource);
|
|
44555
44639
|
if (!existingConfig || !nextConfig) return { reason: "Existing config is not a supported `export default defineConfig({ ... })` shape." };
|
|
44640
|
+
const existingPlugins = findPluginsProperty(existingConfig.objectExpression);
|
|
44641
|
+
if (existingPlugins && existingConfig.objectExpression.properties.some((property) => property.start > existingPlugins.start && (property.type === "SpreadElement" || property.computed || getObjectPropertyName(property) === "plugins"))) return { reason: "A later spread or property can overwrite plugins. Move plugins after it before running init again." };
|
|
44642
|
+
const scaffoldHelpers = new Set(scaffold.helperStatements.map((helper) => helper.name));
|
|
44643
|
+
const droppedHelpers = new Set(existingSource.program.body.flatMap((statement) => {
|
|
44644
|
+
const name = statement.start < existingConfig.exportDeclaration.start ? getManagedHelperName(statement) : null;
|
|
44645
|
+
return name !== null && MANAGED_HELPER_NAMES.has(name) && !scaffoldHelpers.has(name) ? [name] : [];
|
|
44646
|
+
}));
|
|
44556
44647
|
const nextObject = updateManagedObject({
|
|
44557
44648
|
objectExpression: existingConfig.objectExpression,
|
|
44558
44649
|
source: existingSource
|
|
44559
44650
|
}, {
|
|
44560
44651
|
objectExpression: nextConfig.objectExpression,
|
|
44561
44652
|
source: nextSource
|
|
44562
|
-
});
|
|
44653
|
+
}, droppedHelpers);
|
|
44563
44654
|
if (!nextObject) return { reason: "Existing config uses dynamic build/storage/database expressions that cannot be merged safely." };
|
|
44564
44655
|
const exportFullStart = getTopLevelFullStart(existingSource, existingConfig.exportDeclaration);
|
|
44565
44656
|
const firstTriviaContent = existingText.slice(exportFullStart, existingConfig.exportDeclaration.start).search(/\S/);
|
|
44566
44657
|
const managedBodyEnd = firstTriviaContent === -1 ? existingConfig.exportDeclaration.start : exportFullStart + firstTriviaContent;
|
|
44567
44658
|
const bodyEdit = rebuildManagedBody(existingSource, managedBodyEnd, scaffold);
|
|
44568
44659
|
if (!bodyEdit) return { reason: "Existing helper declarations could not be merged safely." };
|
|
44569
|
-
const scaffoldNames = new Map(scaffold.imports.flatMap((info) => (info.named ?? []).map((name) => [name.split(/\s+as\s+/).at(-1), info.pkg])));
|
|
44570
|
-
for (const declaration of importDeclarationsOf(existingSource)) {
|
|
44571
|
-
const pkg = declaration.source.value;
|
|
44572
|
-
if (MANAGED_IMPORT_PACKAGES.has(pkg) && !(nextObject.keptBuild && BUILD_IMPORT_PACKAGES.has(pkg))) continue;
|
|
44573
|
-
for (const specifier of declaration.specifiers) {
|
|
44574
|
-
const scaffoldPackage = scaffoldNames.get(specifier.local.name);
|
|
44575
|
-
if (scaffoldPackage !== void 0 && scaffoldPackage !== pkg) return { reason: `The import of ${specifier.local.name} from "${pkg}" takes the name init imports from "${scaffoldPackage}".` };
|
|
44576
|
-
}
|
|
44577
|
-
}
|
|
44578
44660
|
const objectEdit = {
|
|
44579
44661
|
start: existingConfig.objectExpression.start,
|
|
44580
44662
|
end: existingConfig.objectExpression.end,
|
|
@@ -44589,6 +44671,43 @@ const mergeHotUpdaterConfigText = (existingText, scaffold) => {
|
|
|
44589
44671
|
text: ""
|
|
44590
44672
|
}))
|
|
44591
44673
|
]);
|
|
44674
|
+
const usedSource = parseConfigSource(usedText);
|
|
44675
|
+
const usedConfig = usedSource && findDefineConfigObject(usedSource);
|
|
44676
|
+
const plugins = usedConfig && findPluginsProperty(usedConfig.objectExpression);
|
|
44677
|
+
const keptReferences = usedIdentifiers(plugins ? applyTextEdits(usedText, [{
|
|
44678
|
+
start: plugins.start,
|
|
44679
|
+
end: plugins.end,
|
|
44680
|
+
text: "plugins: []"
|
|
44681
|
+
}]) : usedText);
|
|
44682
|
+
const scaffoldNames = new Map(scaffold.imports.flatMap((info) => (info.named ?? []).map((name) => {
|
|
44683
|
+
const parts = name.split(/\s+as\s+/);
|
|
44684
|
+
return [parts.at(-1), {
|
|
44685
|
+
pkg: info.pkg,
|
|
44686
|
+
imported: parts[0]
|
|
44687
|
+
}];
|
|
44688
|
+
})));
|
|
44689
|
+
const bindings = [];
|
|
44690
|
+
for (const statement of usedSource?.program.body ?? []) {
|
|
44691
|
+
const declaration = statement.type === "ExportNamedDeclaration" ? statement.declaration : statement;
|
|
44692
|
+
if (declaration?.type === "VariableDeclaration") bindings.push(...declaration.declarations.map(({ id }) => id));
|
|
44693
|
+
else if ((declaration?.type === "FunctionDeclaration" || declaration?.type === "ClassDeclaration") && declaration.id) bindings.push(declaration.id);
|
|
44694
|
+
}
|
|
44695
|
+
for (const binding of bindings) {
|
|
44696
|
+
if (binding.type === "Identifier" && scaffoldNames.has(binding.name)) return { reason: `The declaration of ${binding.name} conflicts with an import init needs.` };
|
|
44697
|
+
if (binding.type === "ObjectPattern") bindings.push(...binding.properties.map((property) => property.type === "RestElement" ? property.argument : property.value));
|
|
44698
|
+
else if (binding.type === "ArrayPattern") bindings.push(...binding.elements.filter((element) => element !== null));
|
|
44699
|
+
else if (binding.type === "AssignmentPattern") bindings.push(binding.left);
|
|
44700
|
+
else if (binding.type === "RestElement") bindings.push(binding.argument);
|
|
44701
|
+
}
|
|
44702
|
+
for (const declaration of importDeclarationsOf(existingSource)) {
|
|
44703
|
+
const pkg = declaration.source.value;
|
|
44704
|
+
for (const specifier of declaration.specifiers) {
|
|
44705
|
+
if (MANAGED_IMPORT_PACKAGES.has(pkg) && !(nextObject.keptBuild && BUILD_IMPORT_PACKAGES.has(pkg)) && keptReferences && !keptReferences.has(specifier.local.name)) continue;
|
|
44706
|
+
const scaffoldImport = scaffoldNames.get(specifier.local.name);
|
|
44707
|
+
const imported = specifier.type === "ImportSpecifier" && specifier.imported.type === "Identifier" ? specifier.imported.name : null;
|
|
44708
|
+
if (scaffoldImport !== void 0 && (scaffoldImport.imported !== imported || scaffoldImport.pkg !== pkg && !(scaffoldImport.pkg === "hot-updater/plugins" && pkg === "@hot-updater/server/plugins"))) return { reason: `The import of ${specifier.local.name} from "${pkg}" takes the name init imports from "${scaffoldImport.pkg}".` };
|
|
44709
|
+
}
|
|
44710
|
+
}
|
|
44592
44711
|
return { text: applyTextEdits(existingText, [
|
|
44593
44712
|
objectEdit,
|
|
44594
44713
|
bodyEdit,
|
|
@@ -44686,14 +44805,16 @@ const renderServerSettings = (scaffold) => {
|
|
|
44686
44805
|
const PLUGINS_FILE_PATH = "hotUpdater.plugins.ts";
|
|
44687
44806
|
/**
|
|
44688
44807
|
* Whether `text` is the plugins file an older init wrote: comments and a
|
|
44689
|
-
* re-export of a provider package's `plugins`, which the config
|
|
44808
|
+
* re-export of a provider package's former `plugins`, which the config's
|
|
44809
|
+
* `plugins` list replaces.
|
|
44690
44810
|
*/
|
|
44691
44811
|
const isGeneratedPluginsFile = (text) => /^export\{plugins\}from(["'])@hot-updater\/[\w-]+\1;?$/u.test(text.replace(/^\s*\/\/.*$/gmu, "").replace(/\s+/gu, ""));
|
|
44692
44812
|
/**
|
|
44693
44813
|
* Writes hot-updater.config.ts and says what it did. `settings` names the
|
|
44694
44814
|
* provider in messages, such as "Supabase". A config it cannot merge is
|
|
44695
44815
|
* kept, with what to add to it. The plugins file an older init wrote is
|
|
44696
|
-
* removed
|
|
44816
|
+
* removed only after a successful config write and when the config no longer
|
|
44817
|
+
* references it. A file the project wrote is kept with a cleanup notice.
|
|
44697
44818
|
*/
|
|
44698
44819
|
const writeHotUpdaterFiles = async (scaffold, { cwd = process.cwd(), settings }) => {
|
|
44699
44820
|
const config = await writeHotUpdaterConfig(scaffold, path.join(cwd, HOT_UPDATER_CONFIG_PATH));
|
|
@@ -44708,6 +44829,17 @@ const writeHotUpdaterFiles = async (scaffold, { cwd = process.cwd(), settings })
|
|
|
44708
44829
|
const pluginsPath = path.join(cwd, PLUGINS_FILE_PATH);
|
|
44709
44830
|
const pluginsText = await readTextFile(pluginsPath);
|
|
44710
44831
|
if (pluginsText === null) return { config };
|
|
44832
|
+
if (config.status === "skipped") return {
|
|
44833
|
+
config,
|
|
44834
|
+
pluginsFile: "kept"
|
|
44835
|
+
};
|
|
44836
|
+
if ((await fs$1.readFile(config.path, "utf-8")).includes("./hotUpdater.plugins")) {
|
|
44837
|
+
p.log.warn(`Kept '${PLUGINS_FILE_PATH}': '${HOT_UPDATER_CONFIG_PATH}' still references it.`);
|
|
44838
|
+
return {
|
|
44839
|
+
config,
|
|
44840
|
+
pluginsFile: "kept"
|
|
44841
|
+
};
|
|
44842
|
+
}
|
|
44711
44843
|
if (isGeneratedPluginsFile(pluginsText)) {
|
|
44712
44844
|
await fs$1.rm(pluginsPath);
|
|
44713
44845
|
p.log.success(`Removed '${PLUGINS_FILE_PATH}': \`plugins\` in '${HOT_UPDATER_CONFIG_PATH}' lists the plugins the server runs.`);
|
|
@@ -45126,8 +45258,6 @@ const loadPlatformConfigs = async (platforms, { channel }) => {
|
|
|
45126
45258
|
return configs;
|
|
45127
45259
|
};
|
|
45128
45260
|
const resolveConfig = (config) => {
|
|
45129
|
-
for (const key of ["authorityId", "catalogId"]) if (config && Object.hasOwn(config, key)) throw new Error(`Remove ${key} from hot-updater.config. Catalog identity is managed internally.`);
|
|
45130
|
-
if (config && Object.hasOwn(config, "compressStrategy")) throw new Error("Remove compressStrategy from hot-updater.config. OTA artifacts use manifest files with per-file Brotli compression.");
|
|
45131
45261
|
const mergedConfig = mergeConfigSources(config, getDefaultConfig());
|
|
45132
45262
|
const signing = normalizeSigningConfig(mergedConfig.signing);
|
|
45133
45263
|
return {
|
|
@@ -45143,7 +45273,7 @@ var ServerDefinitionError = class extends Error {
|
|
|
45143
45273
|
};
|
|
45144
45274
|
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
45145
45275
|
/** Whether `value` has the public properties tooling reads from a server definition. */
|
|
45146
|
-
const isServerDefinition = (value) => isRecord(value) && isRecord(value.database) && typeof value.database.name === "string" && Array.isArray(value.
|
|
45276
|
+
const isServerDefinition = (value) => isRecord(value) && isRecord(value.database) && typeof value.database.name === "string" && Array.isArray(value.plugins) && Array.isArray(value.clientPlugins) && Array.isArray(value.clientEndpoints) && isRecord(value.core) && isRecord(value.api);
|
|
45147
45277
|
/**
|
|
45148
45278
|
* `value` as a server definition, or a `ServerDefinitionError` that says
|
|
45149
45279
|
* what `source`, the module it came from, must export instead.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/cli-tools",
|
|
3
|
-
"version": "1.0.0-rc.
|
|
3
|
+
"version": "1.0.0-rc.45",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=20.19.0"
|
|
@@ -49,9 +49,9 @@
|
|
|
49
49
|
"oxc-parser": "0.141.0",
|
|
50
50
|
"oxc-transform": "0.141.0",
|
|
51
51
|
"unconfig": "7.5.0",
|
|
52
|
-
"@hot-updater/protocol": "1.0.0-rc.
|
|
53
|
-
"@hot-updater/
|
|
54
|
-
"@hot-updater/
|
|
52
|
+
"@hot-updater/protocol": "1.0.0-rc.45",
|
|
53
|
+
"@hot-updater/plugin-core": "1.0.0-rc.45",
|
|
54
|
+
"@hot-updater/server": "1.0.0-rc.45"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
57
|
"@clack/prompts": "1.7.0",
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"tar": "^7.5.16",
|
|
66
66
|
"verkit": "0.4.0",
|
|
67
67
|
"workspace-tools": "^0.41.7",
|
|
68
|
-
"@hot-updater/test-utils": "1.0.0-rc.
|
|
68
|
+
"@hot-updater/test-utils": "1.0.0-rc.45"
|
|
69
69
|
},
|
|
70
70
|
"inlinedDependencies": {
|
|
71
71
|
"@babel/code-frame": "7.29.0",
|