vkdeploy-push 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,98 @@
1
+ # vkdeploy-push
2
+
3
+ Biblioteca React Native para versionamento e atualizacao remota de bundle Android.
4
+
5
+ ## Escopo atual
6
+
7
+ - Consulta versao remota do bundle.
8
+ - Faz download do zip do bundle.
9
+ - Extrai e substitui `index.android.bundle`.
10
+ - Move assets Android `drawable-*`.
11
+ - Persiste a versao local aplicada.
12
+
13
+ Implementacao atual focada em Android.
14
+
15
+ ## Instalacao
16
+
17
+ ```bash
18
+ npm install vkdeploy-push
19
+ ```
20
+
21
+ O pacote instala automaticamente as dependencias usadas para filesystem, device info e unzip.
22
+
23
+ ## Uso
24
+
25
+ Import nomeado:
26
+
27
+ ```ts
28
+ import { getVersao, checkUpdate } from 'vkdeploy-push';
29
+ ```
30
+
31
+ Import default:
32
+
33
+ ```ts
34
+ import vkdeployPush from 'vkdeploy-push';
35
+
36
+ await vkdeployPush.getVersao();
37
+ ```
38
+
39
+ Exemplo de update:
40
+
41
+ ```ts
42
+ const result = await checkUpdate();
43
+
44
+ if (result.status === 'updated' && result.restartRequired) {
45
+ // O app decide como avisar o usuário e quando reiniciar.
46
+ }
47
+
48
+ if (result.status === 'error') {
49
+ console.log(result.error.code, result.error.message);
50
+ }
51
+ ```
52
+
53
+ ## Configuracao
54
+
55
+ Crie o arquivo `android/app/src/main/assets/versionamento-vktech-config.json`:
56
+
57
+ ```json
58
+ {
59
+ "app_secret": "SEU_PROJETO_ID"
60
+ }
61
+ ```
62
+
63
+ ## API
64
+
65
+ ### `getVersao()`
66
+
67
+ Retorna a versao local salva no dispositivo.
68
+
69
+ ### `checkUpdate(antes?, depois?)`
70
+
71
+ Consulta a versao remota e aplica update ou rollback quando houver diferenca.
72
+
73
+ Retorno:
74
+
75
+ - `status: 'up-to-date'` quando nao ha mudanca.
76
+ - `status: 'updated'` quando aplicou update ou rollback.
77
+ - `status: 'error'` quando houve falha tipada, com `error.code` estavel.
78
+
79
+ ### `atualizar(versao, projetoId, antes?, depois?)`
80
+
81
+ Baixa, extrai e aplica manualmente uma versao especifica do bundle.
82
+
83
+ Quando o update e aplicado com sucesso, a biblioteca retorna `restartRequired: true`.
84
+ Ela nao exibe `Alert` nem reinicia o app automaticamente.
85
+
86
+ ## Logger
87
+
88
+ Voce pode injetar um logger proprio:
89
+
90
+ ```ts
91
+ import { setLogger } from 'vkdeploy-push';
92
+
93
+ setLogger({
94
+ info: (message, context) => console.log(message, context),
95
+ warn: (message, context) => console.warn(message, context),
96
+ error: (message, context) => console.error(message, context),
97
+ });
98
+ ```
@@ -0,0 +1,8 @@
1
+ import type { RNFSModule } from './runtime';
2
+ import type { VkDeployError } from './types';
3
+ export declare function ensureTempDirectory(rnfs: RNFSModule, tempPath: string): Promise<VkDeployError | undefined>;
4
+ export declare function cleanupTempDirectory(rnfs: RNFSModule, tempPath: string): Promise<VkDeployError | undefined>;
5
+ export declare function removeFileIfExists(rnfs: RNFSModule, path: string): Promise<VkDeployError | undefined>;
6
+ export declare function unzipBundleArchive(rnfs: RNFSModule, unzipFile: (source: string, target: string) => Promise<unknown>, arquivo: string, destino: string): Promise<VkDeployError | undefined>;
7
+ export declare function moveExtractedAssets(rnfs: RNFSModule, tempRoot: string, finalRoot: string): Promise<VkDeployError | undefined>;
8
+ export declare function moveExtractedBundle(rnfs: RNFSModule, origem: string, destino: string): Promise<VkDeployError | undefined>;
package/dist/bundle.js ADDED
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureTempDirectory = ensureTempDirectory;
4
+ exports.cleanupTempDirectory = cleanupTempDirectory;
5
+ exports.removeFileIfExists = removeFileIfExists;
6
+ exports.unzipBundleArchive = unzipBundleArchive;
7
+ exports.moveExtractedAssets = moveExtractedAssets;
8
+ exports.moveExtractedBundle = moveExtractedBundle;
9
+ const logger_1 = require("./logger");
10
+ const DRAWABLE_DENSITIES = ['mdpi', 'hdpi', 'xhdpi', 'xxhdpi', 'xxxhdpi'];
11
+ async function ensureDirectory(rnfs, path) {
12
+ try {
13
+ if (!(await rnfs.exists(path))) {
14
+ await rnfs.mkdir(path);
15
+ }
16
+ return undefined;
17
+ }
18
+ catch (cause) {
19
+ return (0, logger_1.createError)('FILESYSTEM_ERROR', `Falha ao criar diretório: ${path}`, cause);
20
+ }
21
+ }
22
+ async function removePathIfExists(rnfs, path) {
23
+ try {
24
+ if (await rnfs.exists(path)) {
25
+ await rnfs.unlink(path);
26
+ }
27
+ return undefined;
28
+ }
29
+ catch (cause) {
30
+ return (0, logger_1.createError)('FILESYSTEM_ERROR', `Falha ao remover caminho temporário: ${path}`, cause);
31
+ }
32
+ }
33
+ async function moveDirectoryContents(rnfs, origem, destino) {
34
+ try {
35
+ const origemExiste = await rnfs.exists(origem);
36
+ if (!origemExiste) {
37
+ return undefined;
38
+ }
39
+ const origemInfo = await rnfs.stat(origem);
40
+ if (!origemInfo.isDirectory()) {
41
+ return (0, logger_1.createError)('FILESYSTEM_ERROR', `O caminho de origem não é uma pasta: ${origem}`);
42
+ }
43
+ const ensureDestinoError = await ensureDirectory(rnfs, destino);
44
+ if (ensureDestinoError) {
45
+ return ensureDestinoError;
46
+ }
47
+ const itens = await rnfs.readDir(origem);
48
+ for (const item of itens) {
49
+ const caminhoOrigem = item.path;
50
+ const caminhoDestino = `${destino}/${item.name}`;
51
+ if (item.isDirectory()) {
52
+ const moveError = await moveDirectoryContents(rnfs, caminhoOrigem, caminhoDestino);
53
+ if (moveError) {
54
+ return moveError;
55
+ }
56
+ continue;
57
+ }
58
+ if (item.isFile()) {
59
+ await rnfs.moveFile(caminhoOrigem, caminhoDestino);
60
+ }
61
+ }
62
+ await rnfs.unlink(origem);
63
+ return undefined;
64
+ }
65
+ catch (cause) {
66
+ return (0, logger_1.createError)('FILESYSTEM_ERROR', `Erro ao mover pasta de ${origem} para ${destino}.`, cause);
67
+ }
68
+ }
69
+ async function ensureTempDirectory(rnfs, tempPath) {
70
+ return ensureDirectory(rnfs, tempPath);
71
+ }
72
+ async function cleanupTempDirectory(rnfs, tempPath) {
73
+ return removePathIfExists(rnfs, tempPath);
74
+ }
75
+ async function removeFileIfExists(rnfs, path) {
76
+ return removePathIfExists(rnfs, path);
77
+ }
78
+ async function unzipBundleArchive(rnfs, unzipFile, arquivo, destino) {
79
+ try {
80
+ if (!(await rnfs.exists(arquivo))) {
81
+ return (0, logger_1.createError)('UNZIP_FAILED', `Arquivo ZIP não encontrado: ${arquivo}`);
82
+ }
83
+ await unzipFile(arquivo, destino);
84
+ const cleanupError = await removeFileIfExists(rnfs, arquivo);
85
+ if (cleanupError) {
86
+ return cleanupError;
87
+ }
88
+ return undefined;
89
+ }
90
+ catch (cause) {
91
+ return (0, logger_1.createError)('UNZIP_FAILED', 'Erro ao descompactar o bundle.', cause);
92
+ }
93
+ }
94
+ async function moveExtractedAssets(rnfs, tempRoot, finalRoot) {
95
+ for (const density of DRAWABLE_DENSITIES) {
96
+ const origem = `${tempRoot}/drawable-${density}`;
97
+ const destino = `${finalRoot}/drawable-${density}`;
98
+ const error = await moveDirectoryContents(rnfs, origem, destino);
99
+ if (error) {
100
+ return error;
101
+ }
102
+ }
103
+ return undefined;
104
+ }
105
+ async function moveExtractedBundle(rnfs, origem, destino) {
106
+ try {
107
+ if (!(await rnfs.exists(origem))) {
108
+ return (0, logger_1.createError)('FILESYSTEM_ERROR', `Bundle extraído não encontrado: ${origem}`);
109
+ }
110
+ await rnfs.moveFile(origem, destino);
111
+ return undefined;
112
+ }
113
+ catch (cause) {
114
+ return (0, logger_1.createError)('FILESYSTEM_ERROR', 'Erro ao mover o bundle atualizado.', cause);
115
+ }
116
+ }
@@ -0,0 +1,3 @@
1
+ import type { VersaoInfo } from '../types';
2
+ export declare function salvarVersao(versao: number): Promise<void>;
3
+ export declare function buscaVersao(): Promise<VersaoInfo>;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.salvarVersao = salvarVersao;
4
+ exports.buscaVersao = buscaVersao;
5
+ const runtime_1 = require("../runtime");
6
+ function getVersionPath(documentDirectoryPath) {
7
+ return `${documentDirectoryPath}/db.json`;
8
+ }
9
+ async function salvarVersao(versao) {
10
+ const rnfs = await (0, runtime_1.loadRNFS)();
11
+ if (!rnfs.ok) {
12
+ return;
13
+ }
14
+ try {
15
+ await rnfs.module.writeFile(getVersionPath(rnfs.module.DocumentDirectoryPath), JSON.stringify({ versao }), 'utf8');
16
+ }
17
+ catch (error) {
18
+ console.log(error);
19
+ }
20
+ }
21
+ async function buscaVersao() {
22
+ var _a;
23
+ const bundle = await (0, runtime_1.getSafeBundleVersion)();
24
+ const rnfs = await (0, runtime_1.loadRNFS)();
25
+ if (!rnfs.ok) {
26
+ return (0, runtime_1.buildVersionInfo)(bundle, 0);
27
+ }
28
+ try {
29
+ const versionPath = getVersionPath(rnfs.module.DocumentDirectoryPath);
30
+ const exists = await rnfs.module.exists(versionPath);
31
+ if (!exists) {
32
+ return (0, runtime_1.buildVersionInfo)(bundle, 0);
33
+ }
34
+ const fileContent = await rnfs.module.readFile(versionPath, 'utf8');
35
+ const jsonData = JSON.parse(fileContent);
36
+ return (0, runtime_1.buildVersionInfo)(bundle, (_a = jsonData.versao) !== null && _a !== void 0 ? _a : 0);
37
+ }
38
+ catch (error) {
39
+ console.log(error);
40
+ return (0, runtime_1.buildVersionInfo)(bundle, 0);
41
+ }
42
+ }
@@ -0,0 +1,15 @@
1
+ import { setLogger, resetLogger } from './logger';
2
+ import type { AtualizarResult, CheckUpdateResult, UpdateLifecycleCallback, VersaoInfo } from './types';
3
+ export declare function getVersao(): Promise<VersaoInfo>;
4
+ export declare function checkUpdate(antes?: UpdateLifecycleCallback, depois?: UpdateLifecycleCallback): Promise<CheckUpdateResult>;
5
+ export declare function atualizar(versao: number, projetoId: string, antes?: UpdateLifecycleCallback, depois?: UpdateLifecycleCallback): Promise<AtualizarResult>;
6
+ declare const vkdeployPush: {
7
+ getVersao: typeof getVersao;
8
+ checkUpdate: typeof checkUpdate;
9
+ atualizar: typeof atualizar;
10
+ setLogger: typeof setLogger;
11
+ resetLogger: typeof resetLogger;
12
+ };
13
+ export default vkdeployPush;
14
+ export type { AtualizarResult, CheckUpdateResult, ErrorCheckResult, ErrorUpdateResult, LoggerContext, RemoteVersionResponse, UpdateLifecycleCallback, UpdatedCheckResult, UpdatedResult, UpToDateCheckResult, VersaoInfo, VkDeployError, VkDeployErrorCode, VkDeployLogger, } from './types';
15
+ export { resetLogger, setLogger };
package/dist/index.js ADDED
@@ -0,0 +1,227 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setLogger = exports.resetLogger = void 0;
4
+ exports.getVersao = getVersao;
5
+ exports.checkUpdate = checkUpdate;
6
+ exports.atualizar = atualizar;
7
+ const bundle_1 = require("./bundle");
8
+ const DBSQL_1 = require("./database/DBSQL");
9
+ const logger_1 = require("./logger");
10
+ Object.defineProperty(exports, "setLogger", { enumerable: true, get: function () { return logger_1.setLogger; } });
11
+ Object.defineProperty(exports, "resetLogger", { enumerable: true, get: function () { return logger_1.resetLogger; } });
12
+ const network_1 = require("./network");
13
+ const runtime_1 = require("./runtime");
14
+ const functions_1 = require("./utils/functions");
15
+ const API_BASE_URL = 'https://versionamento-back-end-388770734965.us-central1.run.app';
16
+ function toCheckErrorResult(error, version) {
17
+ (0, logger_1.logError)(error.message, {
18
+ code: error.code,
19
+ cause: error.cause,
20
+ });
21
+ return {
22
+ status: 'error',
23
+ error,
24
+ version,
25
+ };
26
+ }
27
+ function toUpdateErrorResult(error) {
28
+ (0, logger_1.logError)(error.message, {
29
+ code: error.code,
30
+ cause: error.cause,
31
+ });
32
+ return {
33
+ status: 'error',
34
+ error,
35
+ };
36
+ }
37
+ async function runCallback(callback) {
38
+ if (typeof callback === 'function') {
39
+ await callback();
40
+ }
41
+ }
42
+ async function loadUpdateDependencies() {
43
+ const platformError = await (0, runtime_1.ensureSupportedPlatform)();
44
+ if (platformError) {
45
+ return {
46
+ ok: false,
47
+ error: platformError,
48
+ };
49
+ }
50
+ const deviceInfo = await (0, runtime_1.loadDeviceInfo)();
51
+ if (!deviceInfo.ok) {
52
+ return {
53
+ ok: false,
54
+ error: deviceInfo.error,
55
+ };
56
+ }
57
+ const rnfs = await (0, runtime_1.loadRNFS)();
58
+ if (!rnfs.ok) {
59
+ return {
60
+ ok: false,
61
+ error: rnfs.error,
62
+ };
63
+ }
64
+ const unzipFile = await (0, runtime_1.loadUnzip)();
65
+ if (!unzipFile.ok) {
66
+ return {
67
+ ok: false,
68
+ error: unzipFile.error,
69
+ };
70
+ }
71
+ return {
72
+ ok: true,
73
+ deviceInfo: deviceInfo.module,
74
+ rnfs: rnfs.module,
75
+ unzipFile: unzipFile.module,
76
+ };
77
+ }
78
+ async function getBuildVersion() {
79
+ const deviceInfo = await (0, runtime_1.loadDeviceInfo)();
80
+ if (!deviceInfo.ok) {
81
+ return deviceInfo.error;
82
+ }
83
+ try {
84
+ return deviceInfo.module.getVersion();
85
+ }
86
+ catch (cause) {
87
+ return (0, logger_1.createError)('INVALID_DEPENDENCY', 'Falha ao executar getVersion() de "react-native-device-info".', cause);
88
+ }
89
+ }
90
+ async function downloadBundleArchive(fromUrl, toFile) {
91
+ const rnfs = await (0, runtime_1.loadRNFS)();
92
+ if (!rnfs.ok) {
93
+ return rnfs.error;
94
+ }
95
+ try {
96
+ return await rnfs.module.downloadFile({
97
+ fromUrl,
98
+ toFile,
99
+ }).promise;
100
+ }
101
+ catch (cause) {
102
+ return (0, logger_1.createError)('DOWNLOAD_FAILED', 'Falha ao baixar o bundle remoto.', cause);
103
+ }
104
+ }
105
+ async function getVersao() {
106
+ return (0, DBSQL_1.buscaVersao)();
107
+ }
108
+ async function checkUpdate(antes, depois) {
109
+ const versaoLocal = await (0, DBSQL_1.buscaVersao)();
110
+ const platformError = await (0, runtime_1.ensureSupportedPlatform)();
111
+ if (platformError) {
112
+ return toCheckErrorResult(platformError, versaoLocal);
113
+ }
114
+ const projectIdResult = await (0, functions_1.buscProjetoId)();
115
+ if (!projectIdResult.ok) {
116
+ return toCheckErrorResult(projectIdResult.error, versaoLocal);
117
+ }
118
+ const buildVersion = await getBuildVersion();
119
+ if (typeof buildVersion !== 'string') {
120
+ return toCheckErrorResult(buildVersion, versaoLocal);
121
+ }
122
+ const remoteVersion = await (0, network_1.requestRemoteVersion)(API_BASE_URL, projectIdResult.projectId, buildVersion);
123
+ if (!remoteVersion.ok) {
124
+ return toCheckErrorResult(remoteVersion.error, versaoLocal);
125
+ }
126
+ const versaoAtual = versaoLocal.versao;
127
+ const versaoRemota = remoteVersion.data.versao;
128
+ if (versaoRemota === versaoAtual) {
129
+ return {
130
+ status: 'up-to-date',
131
+ version: versaoLocal,
132
+ };
133
+ }
134
+ const result = await atualizar(versaoRemota, projectIdResult.projectId, antes, depois);
135
+ if (result.status === 'error') {
136
+ return {
137
+ status: 'error',
138
+ error: result.error,
139
+ version: versaoLocal,
140
+ };
141
+ }
142
+ return {
143
+ status: 'updated',
144
+ direction: versaoRemota > versaoAtual ? 'upgrade' : 'rollback',
145
+ previousVersion: versaoLocal,
146
+ targetVersion: versaoRemota,
147
+ restartRequired: true,
148
+ };
149
+ }
150
+ async function atualizar(versao, projetoId, antes, depois) {
151
+ const dependencies = await loadUpdateDependencies();
152
+ if (!dependencies.ok) {
153
+ return toUpdateErrorResult(dependencies.error);
154
+ }
155
+ const { deviceInfo, rnfs, unzipFile } = dependencies;
156
+ let deviceId;
157
+ try {
158
+ deviceId = await deviceInfo.getUniqueId();
159
+ }
160
+ catch (cause) {
161
+ return toUpdateErrorResult((0, logger_1.createError)('INVALID_DEPENDENCY', 'Falha ao executar getUniqueId() de "react-native-device-info".', cause));
162
+ }
163
+ const localPath = rnfs.DocumentDirectoryPath;
164
+ const localPathBundle = `${localPath}/index.android.bundle`;
165
+ const localPathTemp = `${localPath}/temp-cdd`;
166
+ const localArchivePath = `${localPathTemp}/bundle-update.zip`;
167
+ const bundleUrl = `${API_BASE_URL}/bundle/downloadFile?projetoId=${projetoId}` +
168
+ `&versao=${versao}&deviceId=${deviceId}`;
169
+ try {
170
+ const tempDirError = await (0, bundle_1.ensureTempDirectory)(rnfs, localPathTemp);
171
+ if (tempDirError) {
172
+ return toUpdateErrorResult(tempDirError);
173
+ }
174
+ const localPathTempBundle = `${localPathTemp}/index.android.bundle`;
175
+ await runCallback(antes);
176
+ const downloadResult = await downloadBundleArchive(bundleUrl, localArchivePath);
177
+ if ('code' in downloadResult) {
178
+ return toUpdateErrorResult(downloadResult);
179
+ }
180
+ if (downloadResult.statusCode !== 200) {
181
+ const cleanupError = await (0, bundle_1.removeFileIfExists)(rnfs, localArchivePath);
182
+ if (cleanupError) {
183
+ return toUpdateErrorResult(cleanupError);
184
+ }
185
+ return toUpdateErrorResult((0, logger_1.createError)('DOWNLOAD_FAILED', `O download do bundle retornou HTTP ${downloadResult.statusCode}.`));
186
+ }
187
+ const unzipError = await (0, bundle_1.unzipBundleArchive)(rnfs, unzipFile, localArchivePath, localPathTemp);
188
+ if (unzipError) {
189
+ return toUpdateErrorResult(unzipError);
190
+ }
191
+ const moveAssetsError = await (0, bundle_1.moveExtractedAssets)(rnfs, localPathTemp, localPath);
192
+ if (moveAssetsError) {
193
+ return toUpdateErrorResult(moveAssetsError);
194
+ }
195
+ const moveBundleError = await (0, bundle_1.moveExtractedBundle)(rnfs, localPathTempBundle, localPathBundle);
196
+ if (moveBundleError) {
197
+ return toUpdateErrorResult(moveBundleError);
198
+ }
199
+ await (0, DBSQL_1.salvarVersao)(versao);
200
+ return {
201
+ status: 'updated',
202
+ appliedVersion: versao,
203
+ restartRequired: true,
204
+ };
205
+ }
206
+ catch (cause) {
207
+ return toUpdateErrorResult((0, logger_1.createError)('UNKNOWN_ERROR', 'Erro inesperado ao aplicar atualização.', cause));
208
+ }
209
+ finally {
210
+ const cleanupError = await (0, bundle_1.cleanupTempDirectory)(rnfs, localPathTemp);
211
+ if (cleanupError) {
212
+ (0, logger_1.logError)(cleanupError.message, {
213
+ code: cleanupError.code,
214
+ cause: cleanupError.cause,
215
+ });
216
+ }
217
+ await runCallback(depois);
218
+ }
219
+ }
220
+ const vkdeployPush = {
221
+ getVersao,
222
+ checkUpdate,
223
+ atualizar,
224
+ setLogger: logger_1.setLogger,
225
+ resetLogger: logger_1.resetLogger,
226
+ };
227
+ exports.default = vkdeployPush;
@@ -0,0 +1,8 @@
1
+ import type { LoggerContext, VkDeployError, VkDeployErrorCode, VkDeployLogger } from './types';
2
+ export declare function setLogger(logger: VkDeployLogger): void;
3
+ export declare function resetLogger(): void;
4
+ export declare function getLogger(): VkDeployLogger;
5
+ export declare function logInfo(message: string, context?: LoggerContext): void;
6
+ export declare function logWarn(message: string, context?: LoggerContext): void;
7
+ export declare function logError(message: string, context?: LoggerContext): void;
8
+ export declare function createError(code: VkDeployErrorCode, message: string, cause?: unknown): VkDeployError;
package/dist/logger.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setLogger = setLogger;
4
+ exports.resetLogger = resetLogger;
5
+ exports.getLogger = getLogger;
6
+ exports.logInfo = logInfo;
7
+ exports.logWarn = logWarn;
8
+ exports.logError = logError;
9
+ exports.createError = createError;
10
+ const PREFIX = '[vkdeploy-push]';
11
+ const defaultLogger = {
12
+ info(message, context) {
13
+ console.info(`${PREFIX} ${message}`, context !== null && context !== void 0 ? context : {});
14
+ },
15
+ warn(message, context) {
16
+ console.warn(`${PREFIX} ${message}`, context !== null && context !== void 0 ? context : {});
17
+ },
18
+ error(message, context) {
19
+ console.error(`${PREFIX} ${message}`, context !== null && context !== void 0 ? context : {});
20
+ },
21
+ };
22
+ let activeLogger = defaultLogger;
23
+ function setLogger(logger) {
24
+ activeLogger = logger;
25
+ }
26
+ function resetLogger() {
27
+ activeLogger = defaultLogger;
28
+ }
29
+ function getLogger() {
30
+ return activeLogger;
31
+ }
32
+ function logInfo(message, context) {
33
+ activeLogger.info(message, context);
34
+ }
35
+ function logWarn(message, context) {
36
+ activeLogger.warn(message, context);
37
+ }
38
+ function logError(message, context) {
39
+ activeLogger.error(message, context);
40
+ }
41
+ function createError(code, message, cause) {
42
+ return {
43
+ code,
44
+ message,
45
+ cause,
46
+ };
47
+ }
@@ -0,0 +1,11 @@
1
+ import type { RemoteVersionResponse, VkDeployError } from './types';
2
+ type Result<T> = {
3
+ ok: true;
4
+ data: T;
5
+ } | {
6
+ ok: false;
7
+ error: VkDeployError;
8
+ };
9
+ export declare function requestRemoteVersion(apiBaseUrl: string, projectId: string, buildVersion: string): Promise<Result<RemoteVersionResponse>>;
10
+ export declare function resolveBundleFileName(bundleUrl: string): Promise<string>;
11
+ export {};
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.requestRemoteVersion = requestRemoteVersion;
4
+ exports.resolveBundleFileName = resolveBundleFileName;
5
+ const logger_1 = require("./logger");
6
+ async function requestRemoteVersion(apiBaseUrl, projectId, buildVersion) {
7
+ try {
8
+ const response = await fetch(`${apiBaseUrl}/versao`, {
9
+ method: 'POST',
10
+ headers: {
11
+ Accept: 'application/json',
12
+ 'Content-Type': 'application/json',
13
+ },
14
+ body: JSON.stringify({
15
+ projetoId: projectId,
16
+ versaoBuild: buildVersion,
17
+ }),
18
+ });
19
+ if (!response.ok) {
20
+ return {
21
+ ok: false,
22
+ error: (0, logger_1.createError)('REMOTE_REQUEST_FAILED', `Erro ao consultar versão remota: HTTP ${response.status}`),
23
+ };
24
+ }
25
+ const data = (await response.json());
26
+ if (typeof data.versao !== 'number' || Number.isNaN(data.versao)) {
27
+ return {
28
+ ok: false,
29
+ error: (0, logger_1.createError)('INVALID_REMOTE_RESPONSE', 'A resposta da API de versão não contém um campo "versao" válido.'),
30
+ };
31
+ }
32
+ return {
33
+ ok: true,
34
+ data: {
35
+ versao: data.versao,
36
+ mandatori: data.mandatori,
37
+ },
38
+ };
39
+ }
40
+ catch (cause) {
41
+ return {
42
+ ok: false,
43
+ error: (0, logger_1.createError)('REMOTE_REQUEST_FAILED', 'Falha ao consultar a versão remota.', cause),
44
+ };
45
+ }
46
+ }
47
+ async function resolveBundleFileName(bundleUrl) {
48
+ var _a;
49
+ try {
50
+ const response = await fetch(bundleUrl, { method: 'HEAD' });
51
+ const contentDisposition = response.headers.get('Content-Disposition');
52
+ if (!contentDisposition) {
53
+ return 'index.android.bundle';
54
+ }
55
+ const match = contentDisposition.match(/filename="(.+?)"/);
56
+ return (_a = match === null || match === void 0 ? void 0 : match[1]) !== null && _a !== void 0 ? _a : 'index.android.bundle';
57
+ }
58
+ catch {
59
+ return 'index.android.bundle';
60
+ }
61
+ }
@@ -0,0 +1,53 @@
1
+ import type { VkDeployError, VersaoInfo } from './types';
2
+ export type RNFSDirEntry = {
3
+ path: string;
4
+ name: string;
5
+ isDirectory(): boolean;
6
+ isFile(): boolean;
7
+ };
8
+ export type RNFSStatResult = {
9
+ isDirectory(): boolean;
10
+ };
11
+ export type RNFSModule = {
12
+ DocumentDirectoryPath: string;
13
+ exists(path: string): Promise<boolean>;
14
+ writeFile(path: string, contents: string, encoding?: string): Promise<void>;
15
+ readFile(path: string, encoding?: string): Promise<string>;
16
+ stat(path: string): Promise<RNFSStatResult>;
17
+ mkdir(path: string): Promise<void>;
18
+ readDir(path: string): Promise<RNFSDirEntry[]>;
19
+ moveFile(from: string, to: string): Promise<void>;
20
+ unlink(path: string): Promise<void>;
21
+ existsAssets(path: string): Promise<boolean>;
22
+ readFileAssets(path: string, encoding?: string): Promise<string>;
23
+ downloadFile(options: {
24
+ fromUrl: string;
25
+ toFile: string;
26
+ }): {
27
+ promise: Promise<{
28
+ statusCode: number;
29
+ }>;
30
+ };
31
+ };
32
+ export type DeviceInfoModule = {
33
+ getVersion(): string;
34
+ getUniqueId(): string | Promise<string>;
35
+ };
36
+ type ZipArchiveModule = {
37
+ unzip(source: string, target: string): Promise<unknown>;
38
+ };
39
+ export type UnzipFunction = ZipArchiveModule['unzip'];
40
+ type ModuleLoadResult<T> = {
41
+ ok: true;
42
+ module: T;
43
+ } | {
44
+ ok: false;
45
+ error: VkDeployError;
46
+ };
47
+ export declare function buildVersionInfo(bundle: string, versao: number): VersaoInfo;
48
+ export declare function ensureSupportedPlatform(): Promise<VkDeployError | undefined>;
49
+ export declare function loadRNFS(): Promise<ModuleLoadResult<RNFSModule>>;
50
+ export declare function loadDeviceInfo(): Promise<ModuleLoadResult<DeviceInfoModule>>;
51
+ export declare function loadUnzip(): Promise<ModuleLoadResult<UnzipFunction>>;
52
+ export declare function getSafeBundleVersion(): Promise<string>;
53
+ export {};
@@ -0,0 +1,173 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildVersionInfo = buildVersionInfo;
37
+ exports.ensureSupportedPlatform = ensureSupportedPlatform;
38
+ exports.loadRNFS = loadRNFS;
39
+ exports.loadDeviceInfo = loadDeviceInfo;
40
+ exports.loadUnzip = loadUnzip;
41
+ exports.getSafeBundleVersion = getSafeBundleVersion;
42
+ const logger_1 = require("./logger");
43
+ const UNKNOWN_BUNDLE_VERSION = '0.0.0';
44
+ const loggedMessages = new Set();
45
+ function logOnce(key, level, message, details) {
46
+ if (loggedMessages.has(key)) {
47
+ return;
48
+ }
49
+ loggedMessages.add(key);
50
+ if (level === 'warn') {
51
+ (0, logger_1.logWarn)(message, details);
52
+ return;
53
+ }
54
+ (0, logger_1.logError)(message, details);
55
+ }
56
+ function normalizeModule(module) {
57
+ var _a;
58
+ return ((_a = module.default) !== null && _a !== void 0 ? _a : module);
59
+ }
60
+ async function importModule(moduleName) {
61
+ try {
62
+ const imported = (await Promise.resolve(`${moduleName}`).then(s => __importStar(require(s))));
63
+ return {
64
+ ok: true,
65
+ module: normalizeModule(imported),
66
+ };
67
+ }
68
+ catch (cause) {
69
+ const error = (0, logger_1.createError)('MISSING_DEPENDENCY', `Não foi possível carregar "${moduleName}". Verifique se a biblioteca está instalada e linkada corretamente no app consumidor.`, cause);
70
+ logOnce(`import:${moduleName}`, 'error', error.message, { code: error.code, cause });
71
+ return {
72
+ ok: false,
73
+ error,
74
+ };
75
+ }
76
+ }
77
+ function buildVersionInfo(bundle, versao) {
78
+ return {
79
+ bundle,
80
+ versao,
81
+ titulo: `${bundle}.${versao}`,
82
+ };
83
+ }
84
+ async function ensureSupportedPlatform() {
85
+ var _a;
86
+ const reactNative = await importModule('react-native');
87
+ if (!reactNative.ok) {
88
+ return reactNative.error;
89
+ }
90
+ const os = (_a = reactNative.module.Platform) === null || _a === void 0 ? void 0 : _a.OS;
91
+ if (os === 'android') {
92
+ return undefined;
93
+ }
94
+ const error = (0, logger_1.createError)('UNSUPPORTED_PLATFORM', `A biblioteca atualmente suporta apenas Android. Plataforma recebida: ${os !== null && os !== void 0 ? os : 'desconhecida'}.`);
95
+ logOnce('platform:unsupported', 'warn', error.message, { code: error.code, platform: os });
96
+ return error;
97
+ }
98
+ async function loadRNFS() {
99
+ const imported = await importModule('react-native-fs');
100
+ if (!imported.ok) {
101
+ return imported;
102
+ }
103
+ const rnfs = imported.module;
104
+ if (typeof rnfs.DocumentDirectoryPath !== 'string' ||
105
+ typeof rnfs.exists !== 'function' ||
106
+ typeof rnfs.readFile !== 'function' ||
107
+ typeof rnfs.writeFile !== 'function') {
108
+ const error = (0, logger_1.createError)('INVALID_DEPENDENCY', '"react-native-fs" foi carregado, mas a API esperada não está disponível.');
109
+ logOnce('validate:react-native-fs', 'error', error.message, { code: error.code });
110
+ return {
111
+ ok: false,
112
+ error,
113
+ };
114
+ }
115
+ return imported;
116
+ }
117
+ async function loadDeviceInfo() {
118
+ const imported = await importModule('react-native-device-info');
119
+ if (!imported.ok) {
120
+ return imported;
121
+ }
122
+ const deviceInfo = imported.module;
123
+ if (typeof deviceInfo.getVersion !== 'function' ||
124
+ typeof deviceInfo.getUniqueId !== 'function') {
125
+ const error = (0, logger_1.createError)('INVALID_DEPENDENCY', '"react-native-device-info" foi carregado, mas a API esperada não está disponível.');
126
+ logOnce('validate:react-native-device-info', 'error', error.message, {
127
+ code: error.code,
128
+ });
129
+ return {
130
+ ok: false,
131
+ error,
132
+ };
133
+ }
134
+ return imported;
135
+ }
136
+ async function loadUnzip() {
137
+ const imported = await importModule('react-native-zip-archive');
138
+ if (!imported.ok) {
139
+ return imported;
140
+ }
141
+ const unzip = imported.module.unzip;
142
+ if (typeof unzip !== 'function') {
143
+ const error = (0, logger_1.createError)('INVALID_DEPENDENCY', '"react-native-zip-archive" foi carregado, mas a API esperada não está disponível.');
144
+ logOnce('validate:react-native-zip-archive', 'error', error.message, {
145
+ code: error.code,
146
+ });
147
+ return {
148
+ ok: false,
149
+ error,
150
+ };
151
+ }
152
+ return {
153
+ ok: true,
154
+ module: unzip,
155
+ };
156
+ }
157
+ async function getSafeBundleVersion() {
158
+ const deviceInfo = await loadDeviceInfo();
159
+ if (!deviceInfo.ok) {
160
+ return UNKNOWN_BUNDLE_VERSION;
161
+ }
162
+ try {
163
+ return deviceInfo.module.getVersion();
164
+ }
165
+ catch (cause) {
166
+ const error = (0, logger_1.createError)('INVALID_DEPENDENCY', 'Falha ao executar getVersion() de "react-native-device-info".', cause);
167
+ logOnce('runtime:react-native-device-info:getVersion', 'error', error.message, {
168
+ code: error.code,
169
+ cause,
170
+ });
171
+ return UNKNOWN_BUNDLE_VERSION;
172
+ }
173
+ }
@@ -0,0 +1,54 @@
1
+ export type UpdateLifecycleCallback = () => void | Promise<void>;
2
+ export interface VersaoInfo {
3
+ bundle: string;
4
+ versao: number;
5
+ titulo: string;
6
+ }
7
+ export interface VersionamentoConfig {
8
+ app_secret: string;
9
+ }
10
+ export interface RemoteVersionResponse {
11
+ versao: number;
12
+ mandatori?: boolean;
13
+ }
14
+ export type VkDeployErrorCode = 'UNSUPPORTED_PLATFORM' | 'MISSING_DEPENDENCY' | 'INVALID_DEPENDENCY' | 'CONFIG_NOT_FOUND' | 'INVALID_CONFIG' | 'REMOTE_REQUEST_FAILED' | 'INVALID_REMOTE_RESPONSE' | 'DOWNLOAD_FAILED' | 'UNZIP_FAILED' | 'FILESYSTEM_ERROR' | 'UNKNOWN_ERROR';
15
+ export interface VkDeployError {
16
+ code: VkDeployErrorCode;
17
+ message: string;
18
+ cause?: unknown;
19
+ }
20
+ export interface LoggerContext {
21
+ [key: string]: unknown;
22
+ }
23
+ export interface VkDeployLogger {
24
+ info(message: string, context?: LoggerContext): void;
25
+ warn(message: string, context?: LoggerContext): void;
26
+ error(message: string, context?: LoggerContext): void;
27
+ }
28
+ export interface UpToDateCheckResult {
29
+ status: 'up-to-date';
30
+ version: VersaoInfo;
31
+ }
32
+ export interface UpdatedCheckResult {
33
+ status: 'updated';
34
+ direction: 'upgrade' | 'rollback';
35
+ previousVersion: VersaoInfo;
36
+ targetVersion: number;
37
+ restartRequired: true;
38
+ }
39
+ export interface ErrorCheckResult {
40
+ status: 'error';
41
+ error: VkDeployError;
42
+ version?: VersaoInfo;
43
+ }
44
+ export type CheckUpdateResult = UpToDateCheckResult | UpdatedCheckResult | ErrorCheckResult;
45
+ export interface UpdatedResult {
46
+ status: 'updated';
47
+ appliedVersion: number;
48
+ restartRequired: true;
49
+ }
50
+ export interface ErrorUpdateResult {
51
+ status: 'error';
52
+ error: VkDeployError;
53
+ }
54
+ export type AtualizarResult = UpdatedResult | ErrorUpdateResult;
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,10 @@
1
+ import type { VkDeployError } from '../types';
2
+ type ProjectIdResult = {
3
+ ok: true;
4
+ projectId: string;
5
+ } | {
6
+ ok: false;
7
+ error: VkDeployError;
8
+ };
9
+ export declare function buscProjetoId(): Promise<ProjectIdResult>;
10
+ export {};
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buscProjetoId = buscProjetoId;
4
+ const logger_1 = require("../logger");
5
+ const runtime_1 = require("../runtime");
6
+ const CONFIG_ASSET_PATH = 'versionamento-vktech-config.json';
7
+ async function buscProjetoId() {
8
+ var _a;
9
+ const rnfs = await (0, runtime_1.loadRNFS)();
10
+ if (!rnfs.ok) {
11
+ return {
12
+ ok: false,
13
+ error: rnfs.error,
14
+ };
15
+ }
16
+ const exists = await rnfs.module.existsAssets(CONFIG_ASSET_PATH);
17
+ if (!exists) {
18
+ return {
19
+ ok: false,
20
+ error: (0, logger_1.createError)('CONFIG_NOT_FOUND', 'Arquivo de configuração não encontrado em android/app/src/main/assets/versionamento-vktech-config.json.'),
21
+ };
22
+ }
23
+ try {
24
+ const fileContent = await rnfs.module.readFileAssets(CONFIG_ASSET_PATH, 'utf8');
25
+ const jsonData = JSON.parse(fileContent);
26
+ const appSecret = (_a = jsonData.app_secret) === null || _a === void 0 ? void 0 : _a.trim();
27
+ if (!appSecret) {
28
+ return {
29
+ ok: false,
30
+ error: (0, logger_1.createError)('INVALID_CONFIG', 'O campo "app_secret" não foi informado no arquivo de configuração.'),
31
+ };
32
+ }
33
+ return {
34
+ ok: true,
35
+ projectId: appSecret,
36
+ };
37
+ }
38
+ catch (cause) {
39
+ return {
40
+ ok: false,
41
+ error: (0, logger_1.createError)('INVALID_CONFIG', 'Não foi possível ler ou interpretar o arquivo de configuração.', cause),
42
+ };
43
+ }
44
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "vkdeploy-push",
3
+ "version": "1.0.0",
4
+ "description": "Biblioteca React Native para versionamento e atualizacao remota de bundle Android.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "react-native": "dist/index.js",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "LICENSE"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsc -p tsconfig.json",
21
+ "dev": "tsc -p tsconfig.json --watch",
22
+ "prepare": "npm run build",
23
+ "pack:check": "npm run build && npm pack --dry-run --cache /tmp/npm-cache",
24
+ "test": "npm run build && node --test tests/**/*.test.js"
25
+ },
26
+ "keywords": [
27
+ "react-native",
28
+ "android",
29
+ "bundle",
30
+ "ota",
31
+ "update"
32
+ ],
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "react-native-device-info": "^11.1.0",
36
+ "react-native-fs": "^2.20.0",
37
+ "react-native-zip-archive": "^7.0.1"
38
+ },
39
+ "peerDependencies": {
40
+ "react": ">=18",
41
+ "react-native": ">=0.71"
42
+ },
43
+ "devDependencies": {
44
+ "react": "19.2.3",
45
+ "react-native": "0.85.3",
46
+ "typescript": "^6.0.3"
47
+ }
48
+ }