@hot-updater/cli-tools 0.29.5 → 0.29.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +500 -88
- package/dist/index.d.cts +71 -3
- package/dist/index.d.mts +71 -3
- package/dist/index.mjs +439 -32
- package/package.json +4 -4
package/dist/index.mjs
CHANGED
|
@@ -24,13 +24,14 @@ import Stream, { Duplex, PassThrough as PassThrough$1, Readable as Readable$1, T
|
|
|
24
24
|
import { StringDecoder } from "node:string_decoder";
|
|
25
25
|
import assert$1 from "node:assert";
|
|
26
26
|
import { randomBytes } from "node:crypto";
|
|
27
|
-
import
|
|
27
|
+
import fsPromises from "node:fs/promises";
|
|
28
28
|
import { fileURLToPath } from "node:url";
|
|
29
29
|
import { ChildProcess, execFile, spawn, spawnSync } from "node:child_process";
|
|
30
30
|
import { scheduler, setImmediate as setImmediate$1, setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
31
31
|
import { serialize } from "node:v8";
|
|
32
32
|
import { finished } from "node:stream/promises";
|
|
33
33
|
import { Buffer as Buffer$2 } from "node:buffer";
|
|
34
|
+
import ts from "typescript";
|
|
34
35
|
import { loadConfig as loadConfig$1 } from "unconfig";
|
|
35
36
|
import { transformSync } from "oxc-transform";
|
|
36
37
|
//#region ../../node_modules/.pnpm/picocolors@1.1.1/node_modules/picocolors/picocolors.js
|
|
@@ -21882,6 +21883,53 @@ const printBanner = (version) => {
|
|
|
21882
21883
|
};
|
|
21883
21884
|
//#endregion
|
|
21884
21885
|
//#region src/ConfigBuilder.ts
|
|
21886
|
+
const normalizeImportInfos = (imports) => {
|
|
21887
|
+
const collectedImports = /* @__PURE__ */ new Map();
|
|
21888
|
+
for (const info of imports) {
|
|
21889
|
+
const existing = collectedImports.get(info.pkg);
|
|
21890
|
+
if (existing) {
|
|
21891
|
+
if (info.named) for (const namedImport of info.named) existing.named.add(namedImport);
|
|
21892
|
+
if (info.defaultOrNamespace && !existing.defaultOrNamespace) existing.defaultOrNamespace = info.defaultOrNamespace;
|
|
21893
|
+
if (info.sideEffect && !existing.sideEffect) existing.sideEffect = true;
|
|
21894
|
+
continue;
|
|
21895
|
+
}
|
|
21896
|
+
collectedImports.set(info.pkg, {
|
|
21897
|
+
named: new Set(info.named ?? []),
|
|
21898
|
+
defaultOrNamespace: info.defaultOrNamespace,
|
|
21899
|
+
sideEffect: info.sideEffect ?? false
|
|
21900
|
+
});
|
|
21901
|
+
}
|
|
21902
|
+
return Array.from(collectedImports.entries()).sort(([a], [b]) => {
|
|
21903
|
+
const isABuild = a.startsWith("@hot-updater/");
|
|
21904
|
+
if (isABuild !== b.startsWith("@hot-updater/")) return isABuild ? -1 : 1;
|
|
21905
|
+
if (a === "dotenv/config") return -1;
|
|
21906
|
+
if (b === "dotenv/config") return 1;
|
|
21907
|
+
const isAdminA = a === "firebase-admin";
|
|
21908
|
+
if (isAdminA !== (b === "firebase-admin")) return isAdminA ? -1 : 1;
|
|
21909
|
+
return a.localeCompare(b);
|
|
21910
|
+
}).map(([pkg, info]) => ({
|
|
21911
|
+
pkg,
|
|
21912
|
+
named: Array.from(info.named).sort(),
|
|
21913
|
+
defaultOrNamespace: info.defaultOrNamespace,
|
|
21914
|
+
sideEffect: info.sideEffect ?? false
|
|
21915
|
+
}));
|
|
21916
|
+
};
|
|
21917
|
+
const renderImportStatements = (imports) => {
|
|
21918
|
+
const importLines = [];
|
|
21919
|
+
for (const info of normalizeImportInfos(imports)) {
|
|
21920
|
+
if (info.sideEffect) {
|
|
21921
|
+
importLines.push(`import "${info.pkg}";`);
|
|
21922
|
+
continue;
|
|
21923
|
+
}
|
|
21924
|
+
if (info.defaultOrNamespace) {
|
|
21925
|
+
if (info.pkg === "firebase-admin" && (info.named?.length ?? 0) > 0) importLines.push(`import ${info.defaultOrNamespace}, { ${info.named.join(", ")} } from "${info.pkg}";`);
|
|
21926
|
+
else importLines.push(`import ${info.defaultOrNamespace} from "${info.pkg}";`);
|
|
21927
|
+
continue;
|
|
21928
|
+
}
|
|
21929
|
+
if ((info.named?.length ?? 0) > 0) importLines.push(`import { ${info.named.join(", ")} } from "${info.pkg}";`);
|
|
21930
|
+
}
|
|
21931
|
+
return importLines.join("\n");
|
|
21932
|
+
};
|
|
21885
21933
|
var ConfigBuilder = class {
|
|
21886
21934
|
buildType = null;
|
|
21887
21935
|
storageInfo = null;
|
|
@@ -21915,28 +21963,13 @@ var ConfigBuilder = class {
|
|
|
21915
21963
|
addImports(imports) {
|
|
21916
21964
|
for (const imp of imports) this.addImport(imp);
|
|
21917
21965
|
}
|
|
21918
|
-
|
|
21919
|
-
|
|
21920
|
-
|
|
21921
|
-
|
|
21922
|
-
|
|
21923
|
-
|
|
21924
|
-
|
|
21925
|
-
const isAdminA = a === "firebase-admin";
|
|
21926
|
-
if (isAdminA !== (b === "firebase-admin")) return isAdminA ? -1 : 1;
|
|
21927
|
-
return a.localeCompare(b);
|
|
21928
|
-
});
|
|
21929
|
-
for (const pkg of sortedPackages) {
|
|
21930
|
-
const info = this.collectedImports.get(pkg);
|
|
21931
|
-
if (info.sideEffect) importLines.push(`import "${pkg}";`);
|
|
21932
|
-
else if (info.defaultOrNamespace) if (pkg === "firebase-admin" && info.named.size > 0) importLines.push(`import ${info.defaultOrNamespace}, { ${Array.from(info.named).sort().join(", ")} } from "${pkg}";`);
|
|
21933
|
-
else importLines.push(`import ${info.defaultOrNamespace} from "${pkg}";`);
|
|
21934
|
-
else if (info.named.size > 0) {
|
|
21935
|
-
const namedPart = Array.from(info.named).sort().join(", ");
|
|
21936
|
-
importLines.push(`import { ${namedPart} } from "${pkg}";`);
|
|
21937
|
-
}
|
|
21938
|
-
}
|
|
21939
|
-
return importLines.join("\n");
|
|
21966
|
+
getImportInfos() {
|
|
21967
|
+
return normalizeImportInfos(Array.from(this.collectedImports.entries()).map(([pkg, info]) => ({
|
|
21968
|
+
pkg,
|
|
21969
|
+
named: Array.from(info.named),
|
|
21970
|
+
defaultOrNamespace: info.defaultOrNamespace,
|
|
21971
|
+
sideEffect: info.sideEffect ?? false
|
|
21972
|
+
})));
|
|
21940
21973
|
}
|
|
21941
21974
|
generateBuildConfigString() {
|
|
21942
21975
|
if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
|
|
@@ -21978,13 +22011,14 @@ var ConfigBuilder = class {
|
|
|
21978
22011
|
this.intermediateCode = code.trim();
|
|
21979
22012
|
return this;
|
|
21980
22013
|
}
|
|
21981
|
-
|
|
22014
|
+
getScaffold() {
|
|
21982
22015
|
if (!this.buildType) throw new Error("Build type must be set using .setBuildType()");
|
|
21983
22016
|
if (!this.storageInfo) throw new Error("Storage config must be set using .setStorage()");
|
|
21984
22017
|
if (!this.databaseInfo) throw new Error("Database config must be set using .setDatabase()");
|
|
21985
|
-
const
|
|
22018
|
+
const imports = this.getImportInfos();
|
|
22019
|
+
const importStatements = renderImportStatements(imports);
|
|
21986
22020
|
const buildConfigString = this.generateBuildConfigString();
|
|
21987
|
-
|
|
22021
|
+
const text = `
|
|
21988
22022
|
${importStatements}
|
|
21989
22023
|
|
|
21990
22024
|
config({ path: ".env.hotupdater" });
|
|
@@ -21997,6 +22031,17 @@ export default defineConfig({
|
|
|
21997
22031
|
updateStrategy: "appVersion", // or "fingerprint"
|
|
21998
22032
|
});
|
|
21999
22033
|
`.trim();
|
|
22034
|
+
return {
|
|
22035
|
+
imports,
|
|
22036
|
+
buildConfigString,
|
|
22037
|
+
storageConfigString: this.storageInfo.configString,
|
|
22038
|
+
databaseConfigString: this.databaseInfo.configString,
|
|
22039
|
+
intermediateCode: this.intermediateCode,
|
|
22040
|
+
text
|
|
22041
|
+
};
|
|
22042
|
+
}
|
|
22043
|
+
getResult() {
|
|
22044
|
+
return this.getScaffold().text;
|
|
22000
22045
|
}
|
|
22001
22046
|
};
|
|
22002
22047
|
//#endregion
|
|
@@ -25943,7 +25988,7 @@ const mkdir = (dir, opt, cb) => {
|
|
|
25943
25988
|
else cb();
|
|
25944
25989
|
};
|
|
25945
25990
|
if (dir === cwd) return checkCwd(dir, done);
|
|
25946
|
-
if (preserve) return
|
|
25991
|
+
if (preserve) return fsPromises.mkdir(dir, {
|
|
25947
25992
|
mode,
|
|
25948
25993
|
recursive: true
|
|
25949
25994
|
}).then((made) => done(null, made ?? void 0), done);
|
|
@@ -42010,7 +42055,7 @@ async function findUp(name, { cwd = N.cwd(), type = "file", stopAt } = {}) {
|
|
|
42010
42055
|
while (directory) {
|
|
42011
42056
|
const filePath = isAbsoluteName ? name : path$1.join(directory, name);
|
|
42012
42057
|
try {
|
|
42013
|
-
const stats = await
|
|
42058
|
+
const stats = await fsPromises.stat(filePath);
|
|
42014
42059
|
if (type === "file" && stats.isFile() || type === "directory" && stats.isDirectory()) return filePath;
|
|
42015
42060
|
} catch {}
|
|
42016
42061
|
if (directory === stopAt || directory === root) break;
|
|
@@ -47079,7 +47124,7 @@ const _readPackage = (file, normalize) => {
|
|
|
47079
47124
|
return json;
|
|
47080
47125
|
};
|
|
47081
47126
|
async function readPackage({ cwd, normalize = true } = {}) {
|
|
47082
|
-
return _readPackage(await
|
|
47127
|
+
return _readPackage(await fsPromises.readFile(getPackagePath(cwd), "utf8"), normalize);
|
|
47083
47128
|
}
|
|
47084
47129
|
//#endregion
|
|
47085
47130
|
//#region ../../node_modules/.pnpm/read-package-up@11.0.0/node_modules/read-package-up/index.js
|
|
@@ -47538,6 +47583,363 @@ const getReactNativeMetadatas = (cwd) => {
|
|
|
47538
47583
|
}
|
|
47539
47584
|
};
|
|
47540
47585
|
//#endregion
|
|
47586
|
+
//#region src/hotUpdaterConfig.ts
|
|
47587
|
+
const HOT_UPDATER_CONFIG_PATH = "hot-updater.config.ts";
|
|
47588
|
+
const WRAP_PREFIX = "const __hotUpdaterValue = ";
|
|
47589
|
+
const WRAP_SUFFIX = ";";
|
|
47590
|
+
const MANAGED_IMPORT_PACKAGES = new Set([
|
|
47591
|
+
"dotenv",
|
|
47592
|
+
"firebase-admin",
|
|
47593
|
+
"hot-updater",
|
|
47594
|
+
"@aws-sdk/credential-provider-sso",
|
|
47595
|
+
"@hot-updater/aws",
|
|
47596
|
+
"@hot-updater/bare",
|
|
47597
|
+
"@hot-updater/cloudflare",
|
|
47598
|
+
"@hot-updater/expo",
|
|
47599
|
+
"@hot-updater/firebase",
|
|
47600
|
+
"@hot-updater/rock",
|
|
47601
|
+
"@hot-updater/supabase"
|
|
47602
|
+
]);
|
|
47603
|
+
const MANAGED_HELPER_NAMES = new Set(["commonOptions", "credential"]);
|
|
47604
|
+
const KNOWN_BUILD_CALLEES = new Set([
|
|
47605
|
+
"bare",
|
|
47606
|
+
"expo",
|
|
47607
|
+
"rock"
|
|
47608
|
+
]);
|
|
47609
|
+
const createSnippetSourceFile = (code) => ts.createSourceFile("hot-updater-config-snippet.ts", code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
|
|
47610
|
+
const extractCallIdentifier = (initializer) => {
|
|
47611
|
+
const match = /^\s*([A-Za-z_$][\w$]*)\s*\(/.exec(initializer);
|
|
47612
|
+
if (!match) throw new Error(`Failed to extract call identifier from "${initializer}"`);
|
|
47613
|
+
return match[1];
|
|
47614
|
+
};
|
|
47615
|
+
const wrapExpression = (expression) => `${WRAP_PREFIX}${expression}${WRAP_SUFFIX}`;
|
|
47616
|
+
const getWrappedObjectLiteral = (text) => {
|
|
47617
|
+
const sourceFile = createSnippetSourceFile(wrapExpression(text));
|
|
47618
|
+
const statement = sourceFile.statements[0];
|
|
47619
|
+
if (!statement || !ts.isVariableStatement(statement)) return null;
|
|
47620
|
+
const declaration = statement.declarationList.declarations[0];
|
|
47621
|
+
if (!declaration || !declaration.initializer) return null;
|
|
47622
|
+
if (!ts.isObjectLiteralExpression(declaration.initializer)) return null;
|
|
47623
|
+
return {
|
|
47624
|
+
sourceFile,
|
|
47625
|
+
objectLiteral: declaration.initializer,
|
|
47626
|
+
offset: 26
|
|
47627
|
+
};
|
|
47628
|
+
};
|
|
47629
|
+
const parseVariableStatement = (code) => {
|
|
47630
|
+
const sourceFile = createSnippetSourceFile(code);
|
|
47631
|
+
const statement = sourceFile.statements.find((node) => ts.isVariableStatement(node));
|
|
47632
|
+
if (!statement || !ts.isVariableStatement(statement)) return null;
|
|
47633
|
+
const declaration = statement.declarationList.declarations[0];
|
|
47634
|
+
if (!declaration || !ts.isIdentifier(declaration.name) || !declaration.initializer) return null;
|
|
47635
|
+
return {
|
|
47636
|
+
sourceFile,
|
|
47637
|
+
statement,
|
|
47638
|
+
declaration
|
|
47639
|
+
};
|
|
47640
|
+
};
|
|
47641
|
+
const getPropertyName = (property) => {
|
|
47642
|
+
if (ts.isIdentifier(property) || ts.isStringLiteral(property) || ts.isNumericLiteral(property)) return property.text;
|
|
47643
|
+
if (ts.isSpreadAssignment(property)) return null;
|
|
47644
|
+
if (ts.isShorthandPropertyAssignment(property)) return property.name.text;
|
|
47645
|
+
if (ts.isPropertyAssignment(property) || ts.isMethodDeclaration(property) || ts.isGetAccessorDeclaration(property) || ts.isSetAccessorDeclaration(property)) {
|
|
47646
|
+
const { name } = property;
|
|
47647
|
+
if (ts.isIdentifier(name) || ts.isStringLiteral(name)) return name.text;
|
|
47648
|
+
if (ts.isNumericLiteral(name)) return name.text;
|
|
47649
|
+
}
|
|
47650
|
+
return null;
|
|
47651
|
+
};
|
|
47652
|
+
const trimStatementText = (text) => text.trim();
|
|
47653
|
+
const getStatementText = (sourceText, statement) => trimStatementText(sourceText.slice(statement.getFullStart(), statement.end));
|
|
47654
|
+
const getObjectTrailingComma = (text) => {
|
|
47655
|
+
const closeBraceIndex = text.lastIndexOf("}");
|
|
47656
|
+
if (closeBraceIndex === -1) return false;
|
|
47657
|
+
let index = closeBraceIndex - 1;
|
|
47658
|
+
while (index >= 0 && /\s/.test(text[index] ?? "")) index -= 1;
|
|
47659
|
+
return (text[index] ?? "") === ",";
|
|
47660
|
+
};
|
|
47661
|
+
const dedentBlock = (text) => {
|
|
47662
|
+
const lines = text.replace(/\s+$/, "").split("\n");
|
|
47663
|
+
const indents = lines.filter((line) => line.trim() !== "").map((line) => line.match(/^\s*/)[0].length);
|
|
47664
|
+
const minIndent = indents.length > 0 ? Math.min(...indents) : 0;
|
|
47665
|
+
return lines.map((line) => line.slice(minIndent)).join("\n");
|
|
47666
|
+
};
|
|
47667
|
+
const indentBlock = (text, indent) => dedentBlock(text).split("\n").map((line) => `${indent}${line}`).join("\n");
|
|
47668
|
+
const appendMissingProperties = (objectText, propertyTexts, hasExistingProperties) => {
|
|
47669
|
+
if (propertyTexts.length === 0) return objectText;
|
|
47670
|
+
const closingIndent = /\n([ \t]*)\}$/.exec(objectText)?.[1] ?? "";
|
|
47671
|
+
const childIndent = objectText.match(/\n([ \t]+)[^\s]/)?.[1] ?? `${closingIndent} `;
|
|
47672
|
+
const formattedProperties = propertyTexts.map((propertyText) => indentBlock(propertyText, childIndent)).join(",\n");
|
|
47673
|
+
const closeBraceIndex = objectText.lastIndexOf("}");
|
|
47674
|
+
if (closeBraceIndex === -1) return objectText;
|
|
47675
|
+
const prefix = hasExistingProperties ? getObjectTrailingComma(objectText) ? "\n" : ",\n" : "\n";
|
|
47676
|
+
const suffix = `,\n${closingIndent}`;
|
|
47677
|
+
return `${objectText.slice(0, closeBraceIndex)}${prefix}${formattedProperties}${suffix}${objectText.slice(closeBraceIndex)}`;
|
|
47678
|
+
};
|
|
47679
|
+
const mergeObjectLiteralText = (existingText, newText) => {
|
|
47680
|
+
const existingWrapped = getWrappedObjectLiteral(existingText);
|
|
47681
|
+
const newWrapped = getWrappedObjectLiteral(newText);
|
|
47682
|
+
if (!existingWrapped || !newWrapped) return null;
|
|
47683
|
+
const existingPropertyNames = /* @__PURE__ */ new Set();
|
|
47684
|
+
const existingSpreadTexts = /* @__PURE__ */ new Set();
|
|
47685
|
+
const edits = [];
|
|
47686
|
+
for (const property of existingWrapped.objectLiteral.properties) {
|
|
47687
|
+
if (ts.isSpreadAssignment(property)) {
|
|
47688
|
+
existingSpreadTexts.add(property.expression.getText(existingWrapped.sourceFile).trim());
|
|
47689
|
+
continue;
|
|
47690
|
+
}
|
|
47691
|
+
const propertyName = getPropertyName(property);
|
|
47692
|
+
if (!propertyName) continue;
|
|
47693
|
+
existingPropertyNames.add(propertyName);
|
|
47694
|
+
const nextProperty = newWrapped.objectLiteral.properties.find((candidate) => {
|
|
47695
|
+
if (ts.isSpreadAssignment(candidate)) return false;
|
|
47696
|
+
return getPropertyName(candidate) === propertyName;
|
|
47697
|
+
});
|
|
47698
|
+
if (!nextProperty || !ts.isPropertyAssignment(property) || !ts.isPropertyAssignment(nextProperty)) continue;
|
|
47699
|
+
if (ts.isObjectLiteralExpression(property.initializer) && ts.isObjectLiteralExpression(nextProperty.initializer)) {
|
|
47700
|
+
const mergedInitializer = mergeObjectLiteralText(property.initializer.getText(existingWrapped.sourceFile), nextProperty.initializer.getText(newWrapped.sourceFile));
|
|
47701
|
+
if (!mergedInitializer) return null;
|
|
47702
|
+
edits.push({
|
|
47703
|
+
start: property.initializer.getStart(existingWrapped.sourceFile) - existingWrapped.offset,
|
|
47704
|
+
end: property.initializer.end - existingWrapped.offset,
|
|
47705
|
+
text: mergedInitializer
|
|
47706
|
+
});
|
|
47707
|
+
}
|
|
47708
|
+
}
|
|
47709
|
+
let mergedText = existingText;
|
|
47710
|
+
for (const edit of edits.sort((a, b) => b.start - a.start)) mergedText = mergedText.slice(0, edit.start) + edit.text + mergedText.slice(edit.end);
|
|
47711
|
+
const missingPropertyTexts = newWrapped.objectLiteral.properties.filter((property) => {
|
|
47712
|
+
if (ts.isSpreadAssignment(property)) return !existingSpreadTexts.has(property.expression.getText(newWrapped.sourceFile).trim());
|
|
47713
|
+
const propertyName = getPropertyName(property);
|
|
47714
|
+
return propertyName ? !existingPropertyNames.has(propertyName) : false;
|
|
47715
|
+
}).map((property) => property.getText(newWrapped.sourceFile));
|
|
47716
|
+
return appendMissingProperties(mergedText, missingPropertyTexts, existingWrapped.objectLiteral.properties.length > 0);
|
|
47717
|
+
};
|
|
47718
|
+
const buildMergedCallInitializer = (existingCall, existingSourceFile, newCall, newSourceFile) => {
|
|
47719
|
+
const existingCallee = existingCall.expression.getText(existingSourceFile);
|
|
47720
|
+
const [existingArg] = existingCall.arguments;
|
|
47721
|
+
const [newArg] = newCall.arguments;
|
|
47722
|
+
if (existingCall.arguments.length === 1 && newCall.arguments.length === 1 && existingArg && newArg && ts.isObjectLiteralExpression(existingArg) && ts.isObjectLiteralExpression(newArg)) {
|
|
47723
|
+
const mergedObjectLiteral = mergeObjectLiteralText(existingArg.getText(existingSourceFile), newArg.getText(newSourceFile));
|
|
47724
|
+
if (!mergedObjectLiteral) return null;
|
|
47725
|
+
return `${existingCallee}(${mergedObjectLiteral})`;
|
|
47726
|
+
}
|
|
47727
|
+
return existingCall.getText(existingSourceFile);
|
|
47728
|
+
};
|
|
47729
|
+
const findDefineConfigObject = (sourceFile) => {
|
|
47730
|
+
const exportAssignment = sourceFile.statements.find((statement) => {
|
|
47731
|
+
if (!ts.isExportAssignment(statement)) return false;
|
|
47732
|
+
const expression = statement.expression;
|
|
47733
|
+
if (!ts.isCallExpression(expression)) return false;
|
|
47734
|
+
return ts.isIdentifier(expression.expression) && expression.expression.text === "defineConfig" && expression.arguments.length > 0 && ts.isObjectLiteralExpression(expression.arguments[0]);
|
|
47735
|
+
});
|
|
47736
|
+
if (!exportAssignment || !ts.isExportAssignment(exportAssignment)) return null;
|
|
47737
|
+
const expression = exportAssignment.expression;
|
|
47738
|
+
if (!ts.isCallExpression(expression)) return null;
|
|
47739
|
+
const [argument] = expression.arguments;
|
|
47740
|
+
if (!argument || !ts.isObjectLiteralExpression(argument)) return null;
|
|
47741
|
+
return {
|
|
47742
|
+
exportAssignment,
|
|
47743
|
+
objectLiteral: argument
|
|
47744
|
+
};
|
|
47745
|
+
};
|
|
47746
|
+
const findManagedProperty = (objectLiteral, propertyName) => objectLiteral.properties.find((property) => ts.isPropertyAssignment(property) && getPropertyName(property.name) === propertyName);
|
|
47747
|
+
const getCallCallee = (expression) => {
|
|
47748
|
+
if (!ts.isCallExpression(expression) || !ts.isIdentifier(expression.expression)) return null;
|
|
47749
|
+
return expression.expression.text;
|
|
47750
|
+
};
|
|
47751
|
+
const isConfigCallStatement = (statement) => ts.isExpressionStatement(statement) && ts.isCallExpression(statement.expression) && ts.isIdentifier(statement.expression.expression) && statement.expression.expression.text === "config";
|
|
47752
|
+
const isManagedHelperStatement = (statement) => {
|
|
47753
|
+
if (!ts.isVariableStatement(statement)) return null;
|
|
47754
|
+
const declaration = statement.declarationList.declarations[0];
|
|
47755
|
+
if (!declaration || !ts.isIdentifier(declaration.name)) return null;
|
|
47756
|
+
return MANAGED_HELPER_NAMES.has(declaration.name.text) ? declaration.name.text : null;
|
|
47757
|
+
};
|
|
47758
|
+
const mergeHelperStatement = (existingStatementText, helper) => {
|
|
47759
|
+
if (helper.strategy === "preserve-existing") return existingStatementText;
|
|
47760
|
+
if (helper.strategy === "replace") return helper.code.trim();
|
|
47761
|
+
const existingStatement = parseVariableStatement(existingStatementText);
|
|
47762
|
+
const nextStatement = parseVariableStatement(helper.code);
|
|
47763
|
+
if (!existingStatement || !nextStatement) return null;
|
|
47764
|
+
const existingInitializer = existingStatement.declaration.initializer;
|
|
47765
|
+
const newInitializer = nextStatement.declaration.initializer;
|
|
47766
|
+
if (!existingInitializer || !newInitializer || !ts.isObjectLiteralExpression(existingInitializer) || !ts.isObjectLiteralExpression(newInitializer)) return null;
|
|
47767
|
+
const mergedInitializer = mergeObjectLiteralText(existingInitializer.getText(existingStatement.sourceFile), newInitializer.getText(nextStatement.sourceFile));
|
|
47768
|
+
if (!mergedInitializer) return null;
|
|
47769
|
+
return `${ts.tokenToString(existingStatement.statement.declarationList.flags & ts.NodeFlags.Const ? ts.SyntaxKind.ConstKeyword : existingStatement.statement.declarationList.flags & ts.NodeFlags.Let ? ts.SyntaxKind.LetKeyword : ts.SyntaxKind.VarKeyword) ?? "const"} ${helper.name} = ${mergedInitializer};`;
|
|
47770
|
+
};
|
|
47771
|
+
const updateManagedObject = (existingText, existingObject, newObject, existingSourceFile, newSourceFile) => {
|
|
47772
|
+
const objectStart = existingObject.getStart(existingSourceFile);
|
|
47773
|
+
const objectText = existingText.slice(objectStart, existingObject.end);
|
|
47774
|
+
const propertyEdits = [];
|
|
47775
|
+
const missingPropertyTexts = [];
|
|
47776
|
+
for (const propertyName of [
|
|
47777
|
+
"build",
|
|
47778
|
+
"storage",
|
|
47779
|
+
"database"
|
|
47780
|
+
]) {
|
|
47781
|
+
const existingProperty = findManagedProperty(existingObject, propertyName);
|
|
47782
|
+
const nextProperty = findManagedProperty(newObject, propertyName);
|
|
47783
|
+
if (!nextProperty) continue;
|
|
47784
|
+
if (!existingProperty) {
|
|
47785
|
+
missingPropertyTexts.push(nextProperty.getText(newSourceFile));
|
|
47786
|
+
continue;
|
|
47787
|
+
}
|
|
47788
|
+
if (!ts.isCallExpression(existingProperty.initializer)) return null;
|
|
47789
|
+
if (!ts.isCallExpression(nextProperty.initializer)) return null;
|
|
47790
|
+
const existingCallee = getCallCallee(existingProperty.initializer);
|
|
47791
|
+
const nextCallee = getCallCallee(nextProperty.initializer);
|
|
47792
|
+
if (!existingCallee || !nextCallee) return null;
|
|
47793
|
+
let nextInitializerText = nextProperty.initializer.getText(newSourceFile);
|
|
47794
|
+
if (propertyName === "build") {
|
|
47795
|
+
if (existingCallee === nextCallee) continue;
|
|
47796
|
+
if (!KNOWN_BUILD_CALLEES.has(existingCallee)) return null;
|
|
47797
|
+
} else if (existingCallee === nextCallee) {
|
|
47798
|
+
const mergedInitializer = buildMergedCallInitializer(existingProperty.initializer, existingSourceFile, nextProperty.initializer, newSourceFile);
|
|
47799
|
+
if (mergedInitializer === null) return null;
|
|
47800
|
+
nextInitializerText = mergedInitializer;
|
|
47801
|
+
}
|
|
47802
|
+
propertyEdits.push({
|
|
47803
|
+
start: existingProperty.initializer.getStart(existingSourceFile) - objectStart,
|
|
47804
|
+
end: existingProperty.initializer.end - objectStart,
|
|
47805
|
+
text: nextInitializerText
|
|
47806
|
+
});
|
|
47807
|
+
}
|
|
47808
|
+
let mergedText = objectText;
|
|
47809
|
+
for (const edit of propertyEdits.sort((a, b) => b.start - a.start)) mergedText = mergedText.slice(0, edit.start) + edit.text + mergedText.slice(edit.end);
|
|
47810
|
+
return appendMissingProperties(mergedText, missingPropertyTexts, existingObject.properties.length > 0);
|
|
47811
|
+
};
|
|
47812
|
+
const rebuildImportBlock = (sourceText, sourceFile, scaffold) => {
|
|
47813
|
+
const importDeclarations = sourceFile.statements.filter(ts.isImportDeclaration);
|
|
47814
|
+
if (importDeclarations.length === 0) return {
|
|
47815
|
+
start: 0,
|
|
47816
|
+
end: 0,
|
|
47817
|
+
text: `${renderImportStatements(scaffold.imports)}\n\n`
|
|
47818
|
+
};
|
|
47819
|
+
const preservedImportTexts = importDeclarations.filter((declaration) => {
|
|
47820
|
+
const moduleSpecifier = declaration.moduleSpecifier;
|
|
47821
|
+
return ts.isStringLiteral(moduleSpecifier) && !MANAGED_IMPORT_PACKAGES.has(moduleSpecifier.text);
|
|
47822
|
+
}).map((declaration) => trimStatementText(sourceText.slice(declaration.getFullStart(), declaration.end)));
|
|
47823
|
+
const managedImportText = renderImportStatements(scaffold.imports);
|
|
47824
|
+
const nextImportBlock = [...preservedImportTexts, managedImportText].filter(Boolean).join("\n");
|
|
47825
|
+
return {
|
|
47826
|
+
start: importDeclarations[0].getFullStart(),
|
|
47827
|
+
end: importDeclarations.at(-1).end,
|
|
47828
|
+
text: `${nextImportBlock}\n\n`
|
|
47829
|
+
};
|
|
47830
|
+
};
|
|
47831
|
+
const rebuildManagedBody = (sourceText, sourceFile, exportAssignment, scaffold) => {
|
|
47832
|
+
const statementsBeforeExport = sourceFile.statements.filter((statement) => !ts.isImportDeclaration(statement) && statement.pos < exportAssignment.pos);
|
|
47833
|
+
const managedHelpers = new Map(scaffold.helperStatements.map((statement) => [statement.name, statement]));
|
|
47834
|
+
const emittedHelpers = /* @__PURE__ */ new Set();
|
|
47835
|
+
const bodyStatements = [];
|
|
47836
|
+
for (const statement of statementsBeforeExport) {
|
|
47837
|
+
if (isConfigCallStatement(statement)) continue;
|
|
47838
|
+
const helperName = isManagedHelperStatement(statement);
|
|
47839
|
+
if (!helperName) {
|
|
47840
|
+
bodyStatements.push(getStatementText(sourceText, statement));
|
|
47841
|
+
continue;
|
|
47842
|
+
}
|
|
47843
|
+
const helper = managedHelpers.get(helperName);
|
|
47844
|
+
if (!helper) continue;
|
|
47845
|
+
const mergedHelperStatement = mergeHelperStatement(getStatementText(sourceText, statement), helper);
|
|
47846
|
+
if (!mergedHelperStatement) return null;
|
|
47847
|
+
emittedHelpers.add(helperName);
|
|
47848
|
+
bodyStatements.push(mergedHelperStatement);
|
|
47849
|
+
}
|
|
47850
|
+
for (const helper of scaffold.helperStatements) if (!emittedHelpers.has(helper.name)) bodyStatements.push(helper.code.trim());
|
|
47851
|
+
const bodyText = bodyStatements.filter(Boolean).join("\n\n");
|
|
47852
|
+
const configStatement = `config({ path: ".env.hotupdater" });`;
|
|
47853
|
+
const managedBody = bodyText ? `\n\n${configStatement}\n\n${bodyText}\n\n` : `\n\n${configStatement}\n\n`;
|
|
47854
|
+
return {
|
|
47855
|
+
start: sourceFile.statements.filter(ts.isImportDeclaration).at(-1)?.end ?? 0,
|
|
47856
|
+
end: exportAssignment.getFullStart(),
|
|
47857
|
+
text: managedBody
|
|
47858
|
+
};
|
|
47859
|
+
};
|
|
47860
|
+
const createHotUpdaterConfigScaffold = ({ build, storage, database, extraImports = [], helperStatements = [], updateStrategy = "appVersion" }) => {
|
|
47861
|
+
const intermediateCode = helperStatements.map((statement) => statement.code.trim()).filter(Boolean).join("\n\n");
|
|
47862
|
+
const builder = new ConfigBuilder().setBuildType(build).setStorage(storage).setDatabase(database);
|
|
47863
|
+
for (const extraImport of extraImports) builder.addImport(extraImport);
|
|
47864
|
+
if (intermediateCode) builder.setIntermediateCode(intermediateCode);
|
|
47865
|
+
return createHotUpdaterConfigScaffoldFromBuilder(builder, {
|
|
47866
|
+
helperStatements,
|
|
47867
|
+
updateStrategy
|
|
47868
|
+
});
|
|
47869
|
+
};
|
|
47870
|
+
const createHotUpdaterConfigScaffoldFromBuilder = (builder, { helperStatements = [], updateStrategy = "appVersion" } = {}) => {
|
|
47871
|
+
const scaffold = builder.getScaffold();
|
|
47872
|
+
return {
|
|
47873
|
+
text: updateStrategy === "appVersion" ? scaffold.text : scaffold.text.replace("updateStrategy: \"appVersion\"", `updateStrategy: "${updateStrategy}"`),
|
|
47874
|
+
imports: scaffold.imports,
|
|
47875
|
+
build: {
|
|
47876
|
+
initializer: scaffold.buildConfigString,
|
|
47877
|
+
callee: extractCallIdentifier(scaffold.buildConfigString)
|
|
47878
|
+
},
|
|
47879
|
+
storage: {
|
|
47880
|
+
initializer: scaffold.storageConfigString,
|
|
47881
|
+
callee: extractCallIdentifier(scaffold.storageConfigString)
|
|
47882
|
+
},
|
|
47883
|
+
database: {
|
|
47884
|
+
initializer: scaffold.databaseConfigString,
|
|
47885
|
+
callee: extractCallIdentifier(scaffold.databaseConfigString)
|
|
47886
|
+
},
|
|
47887
|
+
helperStatements,
|
|
47888
|
+
updateStrategy: `"${updateStrategy}"`
|
|
47889
|
+
};
|
|
47890
|
+
};
|
|
47891
|
+
const writeHotUpdaterConfig = async (scaffold, filePath = HOT_UPDATER_CONFIG_PATH) => {
|
|
47892
|
+
const existingText = await fs$1.readFile(filePath, "utf-8").catch((error) => {
|
|
47893
|
+
if (error.code === "ENOENT") return null;
|
|
47894
|
+
throw error;
|
|
47895
|
+
});
|
|
47896
|
+
if (existingText === null) {
|
|
47897
|
+
await fs$1.writeFile(filePath, `${scaffold.text}\n`, "utf-8");
|
|
47898
|
+
return {
|
|
47899
|
+
status: "created",
|
|
47900
|
+
path: filePath
|
|
47901
|
+
};
|
|
47902
|
+
}
|
|
47903
|
+
const existingSourceFile = createSnippetSourceFile(existingText);
|
|
47904
|
+
const existingConfig = findDefineConfigObject(existingSourceFile);
|
|
47905
|
+
const nextSourceFile = createSnippetSourceFile(scaffold.text);
|
|
47906
|
+
const nextConfig = findDefineConfigObject(nextSourceFile);
|
|
47907
|
+
if (!existingConfig || !nextConfig) return {
|
|
47908
|
+
status: "skipped",
|
|
47909
|
+
path: filePath,
|
|
47910
|
+
reason: "Existing config is not a supported `export default defineConfig({ ... })` shape."
|
|
47911
|
+
};
|
|
47912
|
+
const nextObjectText = updateManagedObject(existingText, existingConfig.objectLiteral, nextConfig.objectLiteral, existingSourceFile, nextSourceFile);
|
|
47913
|
+
if (!nextObjectText) return {
|
|
47914
|
+
status: "skipped",
|
|
47915
|
+
path: filePath,
|
|
47916
|
+
reason: "Existing config uses dynamic build/storage/database expressions that cannot be merged safely."
|
|
47917
|
+
};
|
|
47918
|
+
const objectEdit = {
|
|
47919
|
+
start: existingConfig.objectLiteral.getStart(existingSourceFile),
|
|
47920
|
+
end: existingConfig.objectLiteral.end,
|
|
47921
|
+
text: nextObjectText
|
|
47922
|
+
};
|
|
47923
|
+
const importEdit = rebuildImportBlock(existingText, existingSourceFile, scaffold);
|
|
47924
|
+
const bodyEdit = rebuildManagedBody(existingText, existingSourceFile, existingConfig.exportAssignment, scaffold);
|
|
47925
|
+
if (!bodyEdit) return {
|
|
47926
|
+
status: "skipped",
|
|
47927
|
+
path: filePath,
|
|
47928
|
+
reason: "Existing helper declarations could not be merged safely."
|
|
47929
|
+
};
|
|
47930
|
+
let mergedText = existingText;
|
|
47931
|
+
for (const edit of [
|
|
47932
|
+
objectEdit,
|
|
47933
|
+
bodyEdit,
|
|
47934
|
+
importEdit
|
|
47935
|
+
].sort((a, b) => b.start - a.start)) mergedText = mergedText.slice(0, edit.start) + edit.text + mergedText.slice(edit.end);
|
|
47936
|
+
await fs$1.writeFile(filePath, mergedText, "utf-8");
|
|
47937
|
+
return {
|
|
47938
|
+
status: "merged",
|
|
47939
|
+
path: filePath
|
|
47940
|
+
};
|
|
47941
|
+
};
|
|
47942
|
+
//#endregion
|
|
47541
47943
|
//#region ../../node_modules/.pnpm/es-toolkit@1.32.0/node_modules/es-toolkit/dist/predicate/isPlainObject.mjs
|
|
47542
47944
|
function isPlainObject(value) {
|
|
47543
47945
|
if (!value || typeof value !== "object") return false;
|
|
@@ -47659,8 +48061,9 @@ const log = {
|
|
|
47659
48061
|
};
|
|
47660
48062
|
//#endregion
|
|
47661
48063
|
//#region src/makeEnv.ts
|
|
47662
|
-
const makeEnv = async (newEnvVars, filePath = ".env.hotupdater") => {
|
|
48064
|
+
const makeEnv = async (newEnvVars, filePath = ".env.hotupdater", options) => {
|
|
47663
48065
|
try {
|
|
48066
|
+
const preserveKeys = new Set(options?.preserveKeys ?? []);
|
|
47664
48067
|
const existingContent = await fs$1.readFile(filePath, "utf-8").catch(() => "");
|
|
47665
48068
|
const lines = existingContent ? existingContent.split("\n") : [];
|
|
47666
48069
|
const processedKeys = /* @__PURE__ */ new Set();
|
|
@@ -47677,7 +48080,7 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater") => {
|
|
|
47677
48080
|
const nextLine = (lines[i + 1] ?? "").trim();
|
|
47678
48081
|
if (nextLine && !nextLine.startsWith("#") && nextLine.includes("=")) {
|
|
47679
48082
|
const [possibleKey = ""] = nextLine.split("=");
|
|
47680
|
-
if (Object.hasOwn(newEnvVars, possibleKey.trim())) continue;
|
|
48083
|
+
if (Object.hasOwn(newEnvVars, possibleKey.trim()) && !preserveKeys.has(possibleKey.trim())) continue;
|
|
47681
48084
|
}
|
|
47682
48085
|
}
|
|
47683
48086
|
updatedLines.push(line);
|
|
@@ -47688,6 +48091,10 @@ const makeEnv = async (newEnvVars, filePath = ".env.hotupdater") => {
|
|
|
47688
48091
|
const key = keyPart?.trim() ?? "";
|
|
47689
48092
|
if (Object.hasOwn(newEnvVars, key)) {
|
|
47690
48093
|
processedKeys.add(key);
|
|
48094
|
+
if (preserveKeys.has(key)) {
|
|
48095
|
+
updatedLines.push(line);
|
|
48096
|
+
continue;
|
|
48097
|
+
}
|
|
47691
48098
|
const newValue = newEnvVars[key];
|
|
47692
48099
|
if (typeof newValue === "object" && newValue !== null) {
|
|
47693
48100
|
updatedLines.push(`# ${newValue.comment}`);
|
|
@@ -47775,4 +48182,4 @@ function transformTemplate(templateString, values) {
|
|
|
47775
48182
|
}
|
|
47776
48183
|
//#endregion
|
|
47777
48184
|
var colors = import_picocolors.default;
|
|
47778
|
-
export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, banner, colors, copyDirToTmp, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, dist_exports as p, printBanner, resolveHotUpdaterServerVersion, resolvePackageVersion, stripAnsi, transformEnv, transformTemplate };
|
|
48185
|
+
export { BuildLogger, ConfigBuilder, HOT_UPDATER_SERVER_PACKAGE_VERSION_ENV, HotUpdateDirUtil, banner, colors, copyDirToTmp, createHotUpdaterConfigScaffold, createHotUpdaterConfigScaffoldFromBuilder, createLogWriter, createTarBr, createTarBrTargetFiles, createTarGz, createTarGzTargetFiles, createZip, createZipTargetFiles, decryptJson, encryptJson, ensureInstallPackages, getAndroidSdkPath, getCwd, getPackageManager, getReactNativeMetadatas, link, loadConfig, log, makeEnv, dist_exports as p, printBanner, renderImportStatements, resolveHotUpdaterServerVersion, resolvePackageVersion, stripAnsi, transformEnv, transformTemplate, writeHotUpdaterConfig };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hot-updater/cli-tools",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI utilities for Hot Updater",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -40,8 +40,9 @@
|
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
42
|
"oxc-transform": "0.121.0",
|
|
43
|
+
"typescript": "6.0.2",
|
|
43
44
|
"unconfig": "7.5.0",
|
|
44
|
-
"@hot-updater/plugin-core": "0.29.
|
|
45
|
+
"@hot-updater/plugin-core": "0.29.6"
|
|
45
46
|
},
|
|
46
47
|
"devDependencies": {
|
|
47
48
|
"@clack/prompts": "1.0.1",
|
|
@@ -56,9 +57,8 @@
|
|
|
56
57
|
"read-package-up": "^11.0.0",
|
|
57
58
|
"semver": "^7.6.3",
|
|
58
59
|
"tar": "^7.5.1",
|
|
59
|
-
"typescript": "6.0.2",
|
|
60
60
|
"workspace-tools": "^0.36.4",
|
|
61
|
-
"@hot-updater/test-utils": "0.29.
|
|
61
|
+
"@hot-updater/test-utils": "0.29.6"
|
|
62
62
|
},
|
|
63
63
|
"inlinedDependencies": {
|
|
64
64
|
"@babel/code-frame": "7.29.0",
|