@trendify/cli 0.1.6 → 0.1.8
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/README.md +26 -0
- package/dist/app.component.d.ts +14 -0
- package/dist/app.component.d.ts.map +1 -0
- package/dist/app.component.js +448 -0
- package/dist/app.d.ts +9 -1
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +84 -27
- package/dist/cli-update-service.d.ts +29 -0
- package/dist/cli-update-service.d.ts.map +1 -0
- package/dist/cli-update-service.js +206 -0
- package/dist/cli.entry.d.ts +3 -0
- package/dist/cli.entry.d.ts.map +1 -0
- package/dist/cli.entry.js +51 -0
- package/dist/cli.js +24 -1
- package/dist/modules/auth/pages/login.page.d.ts +12 -0
- package/dist/modules/auth/pages/login.page.d.ts.map +1 -0
- package/dist/modules/auth/pages/login.page.js +22 -0
- package/dist/modules/auth/pages/profile.page.d.ts +12 -0
- package/dist/modules/auth/pages/profile.page.d.ts.map +1 -0
- package/dist/modules/auth/pages/profile.page.js +180 -0
- package/dist/modules/auth/services/auth-storage.service.d.ts +11 -0
- package/dist/modules/auth/services/auth-storage.service.d.ts.map +1 -0
- package/dist/modules/auth/services/auth-storage.service.js +65 -0
- package/dist/modules/auth/services/auth.service.d.ts +60 -0
- package/dist/modules/auth/services/auth.service.d.ts.map +1 -0
- package/dist/modules/auth/services/auth.service.js +494 -0
- package/dist/modules/auth/utils/auth-user.util.d.ts +3 -0
- package/dist/modules/auth/utils/auth-user.util.d.ts.map +1 -0
- package/dist/modules/auth/utils/auth-user.util.js +10 -0
- package/dist/modules/discovery/components/discovery-step-header.component.d.ts +7 -0
- package/dist/modules/discovery/components/discovery-step-header.component.d.ts.map +1 -0
- package/dist/modules/discovery/components/discovery-step-header.component.js +5 -0
- package/dist/modules/discovery/pages/discovery.page.d.ts +11 -0
- package/dist/modules/discovery/pages/discovery.page.d.ts.map +1 -0
- package/dist/modules/discovery/pages/discovery.page.js +58 -0
- package/dist/modules/profile/pages/profile.page.d.ts +12 -0
- package/dist/modules/profile/pages/profile.page.d.ts.map +1 -0
- package/dist/modules/profile/pages/profile.page.js +180 -0
- package/dist/shared/components/action-menu.component.d.ts +13 -0
- package/dist/shared/components/action-menu.component.d.ts.map +1 -0
- package/dist/shared/components/action-menu.component.js +7 -0
- package/dist/shared/components/app-logo.component.d.ts +2 -0
- package/dist/shared/components/app-logo.component.d.ts.map +1 -0
- package/dist/shared/components/app-logo.component.js +13 -0
- package/dist/shared/components/app-menu.component.d.ts +17 -0
- package/dist/shared/components/app-menu.component.d.ts.map +1 -0
- package/dist/shared/components/app-menu.component.js +85 -0
- package/dist/shared/components/app-shell.component.d.ts +12 -0
- package/dist/shared/components/app-shell.component.d.ts.map +1 -0
- package/dist/shared/components/app-shell.component.js +15 -0
- package/dist/shared/components/radio-select.component.d.ts +12 -0
- package/dist/shared/components/radio-select.component.d.ts.map +1 -0
- package/dist/shared/components/radio-select.component.js +16 -0
- package/dist/shared/components/step-header.component.d.ts +7 -0
- package/dist/shared/components/step-header.component.d.ts.map +1 -0
- package/dist/shared/components/step-header.component.js +5 -0
- package/dist/shared/components/text-field.component.d.ts +12 -0
- package/dist/shared/components/text-field.component.d.ts.map +1 -0
- package/dist/shared/components/text-field.component.js +6 -0
- package/dist/shared/config/app-paths.config.d.ts +4 -0
- package/dist/shared/config/app-paths.config.d.ts.map +1 -0
- package/dist/shared/config/app-paths.config.js +5 -0
- package/dist/shared/config/env.config.d.ts +14 -0
- package/dist/shared/config/env.config.d.ts.map +1 -0
- package/dist/shared/config/env.config.js +58 -0
- package/dist/shared/constants/app-version.constant.d.ts +2 -0
- package/dist/shared/constants/app-version.constant.d.ts.map +1 -0
- package/dist/shared/constants/app-version.constant.js +1 -0
- package/dist/shared/services/cli-update.service.d.ts +29 -0
- package/dist/shared/services/cli-update.service.d.ts.map +1 -0
- package/dist/shared/services/cli-update.service.js +206 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export type CliUpdateCheckResult = {
|
|
2
|
+
readonly currentVersion: string;
|
|
3
|
+
readonly packageName: string;
|
|
4
|
+
readonly status: 'checking';
|
|
5
|
+
} | {
|
|
6
|
+
readonly currentVersion: string;
|
|
7
|
+
readonly latestVersion: string;
|
|
8
|
+
readonly packageName: string;
|
|
9
|
+
readonly status: 'available';
|
|
10
|
+
} | {
|
|
11
|
+
readonly currentVersion: string;
|
|
12
|
+
readonly latestVersion: string;
|
|
13
|
+
readonly packageName: string;
|
|
14
|
+
readonly status: 'current';
|
|
15
|
+
} | {
|
|
16
|
+
readonly currentVersion: string;
|
|
17
|
+
readonly message: string;
|
|
18
|
+
readonly packageName: string;
|
|
19
|
+
readonly status: 'error';
|
|
20
|
+
};
|
|
21
|
+
export type CliSelfUpdateResult = {
|
|
22
|
+
readonly ok: true;
|
|
23
|
+
} | {
|
|
24
|
+
readonly error: string;
|
|
25
|
+
readonly ok: false;
|
|
26
|
+
};
|
|
27
|
+
export declare function checkForCliUpdate(currentVersion: string): Promise<CliUpdateCheckResult>;
|
|
28
|
+
export declare function runCliSelfUpdate(packageName: string, latestVersion: string): Promise<CliSelfUpdateResult>;
|
|
29
|
+
//# sourceMappingURL=cli-update-service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli-update-service.d.ts","sourceRoot":"","sources":["../src/cli-update-service.ts"],"names":[],"mappings":"AAMA,MAAM,MAAM,oBAAoB,GAC5B;IACE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;CAC7B,GACD;IACE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;CAC9B,GACD;IACE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;CAC5B,GACD;IACE,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CAC1B,CAAC;AAEN,MAAM,MAAM,mBAAmB,GAC3B;IACE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;CACnB,GACD;IACE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;CACpB,CAAC;AA4MN,wBAAsB,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,CAAC,CA6B7F;AAED,wBAAsB,gBAAgB,CAAC,WAAW,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAwB/G"}
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { spawn } from 'node:child_process';
|
|
3
|
+
const DEFAULT_PACKAGE_NAME = '@trendify/cli';
|
|
4
|
+
const VERSION_PATTERN = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/u;
|
|
5
|
+
function getPackageManifestPath() {
|
|
6
|
+
return new URL('../package.json', import.meta.url);
|
|
7
|
+
}
|
|
8
|
+
function getNpmCommand() {
|
|
9
|
+
return process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
10
|
+
}
|
|
11
|
+
function toErrorMessage(error) {
|
|
12
|
+
if (error instanceof Error && error.message.trim()) {
|
|
13
|
+
return error.message;
|
|
14
|
+
}
|
|
15
|
+
return 'Nao foi possivel concluir a operacao.';
|
|
16
|
+
}
|
|
17
|
+
function readInstalledPackageMetadata() {
|
|
18
|
+
try {
|
|
19
|
+
const manifestContents = readFileSync(getPackageManifestPath(), 'utf8');
|
|
20
|
+
const manifest = JSON.parse(manifestContents);
|
|
21
|
+
const packageName = typeof manifest.name === 'string' && manifest.name.trim().length > 0
|
|
22
|
+
? manifest.name.trim()
|
|
23
|
+
: DEFAULT_PACKAGE_NAME;
|
|
24
|
+
return { packageName };
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return { packageName: DEFAULT_PACKAGE_NAME };
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function parseVersion(version) {
|
|
31
|
+
const match = VERSION_PATTERN.exec(version.trim());
|
|
32
|
+
if (!match) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const [, major, minor, patch, prerelease] = match;
|
|
36
|
+
if (!major || !minor || !patch) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
major: Number.parseInt(major, 10),
|
|
41
|
+
minor: Number.parseInt(minor, 10),
|
|
42
|
+
patch: Number.parseInt(patch, 10),
|
|
43
|
+
prerelease: prerelease ?? null,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
function compareVersions(left, right) {
|
|
47
|
+
const parsedLeft = parseVersion(left);
|
|
48
|
+
const parsedRight = parseVersion(right);
|
|
49
|
+
if (!parsedLeft || !parsedRight) {
|
|
50
|
+
return left.localeCompare(right, undefined, { numeric: true, sensitivity: 'base' });
|
|
51
|
+
}
|
|
52
|
+
if (parsedLeft.major !== parsedRight.major) {
|
|
53
|
+
return parsedLeft.major - parsedRight.major;
|
|
54
|
+
}
|
|
55
|
+
if (parsedLeft.minor !== parsedRight.minor) {
|
|
56
|
+
return parsedLeft.minor - parsedRight.minor;
|
|
57
|
+
}
|
|
58
|
+
if (parsedLeft.patch !== parsedRight.patch) {
|
|
59
|
+
return parsedLeft.patch - parsedRight.patch;
|
|
60
|
+
}
|
|
61
|
+
if (parsedLeft.prerelease === parsedRight.prerelease) {
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
if (parsedLeft.prerelease === null) {
|
|
65
|
+
return 1;
|
|
66
|
+
}
|
|
67
|
+
if (parsedRight.prerelease === null) {
|
|
68
|
+
return -1;
|
|
69
|
+
}
|
|
70
|
+
return parsedLeft.prerelease.localeCompare(parsedRight.prerelease, undefined, {
|
|
71
|
+
numeric: true,
|
|
72
|
+
sensitivity: 'base',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function runCommand(command, args) {
|
|
76
|
+
return new Promise((resolve, reject) => {
|
|
77
|
+
const child = spawn(command, [...args], {
|
|
78
|
+
env: process.env,
|
|
79
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
80
|
+
});
|
|
81
|
+
let stdout = '';
|
|
82
|
+
let stderr = '';
|
|
83
|
+
child.stdout.on('data', (chunk) => {
|
|
84
|
+
stdout += chunk.toString();
|
|
85
|
+
});
|
|
86
|
+
child.stderr.on('data', (chunk) => {
|
|
87
|
+
stderr += chunk.toString();
|
|
88
|
+
});
|
|
89
|
+
child.on('error', (error) => {
|
|
90
|
+
reject(error);
|
|
91
|
+
});
|
|
92
|
+
child.on('close', (code) => {
|
|
93
|
+
resolve({
|
|
94
|
+
code: code ?? 1,
|
|
95
|
+
stderr,
|
|
96
|
+
stdout,
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function fetchLatestPublishedVersion(packageName) {
|
|
102
|
+
const commandResult = await runCommand(getNpmCommand(), ['view', packageName, 'version', '--json', '--silent']);
|
|
103
|
+
if (commandResult.code !== 0) {
|
|
104
|
+
const message = commandResult.stderr.trim() || commandResult.stdout.trim() || 'Falha ao consultar o npm.';
|
|
105
|
+
throw new Error(message);
|
|
106
|
+
}
|
|
107
|
+
const stdout = commandResult.stdout.trim();
|
|
108
|
+
if (!stdout) {
|
|
109
|
+
throw new Error('O npm nao retornou a versao publicada mais recente.');
|
|
110
|
+
}
|
|
111
|
+
try {
|
|
112
|
+
const parsedOutput = JSON.parse(stdout);
|
|
113
|
+
if (typeof parsedOutput === 'string' && parsedOutput.trim()) {
|
|
114
|
+
return parsedOutput.trim();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
if (stdout) {
|
|
119
|
+
return stdout.replace(/^"+|"+$/gu, '').trim();
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
throw new Error('Nao foi possivel interpretar a versao publicada retornada pelo npm.');
|
|
123
|
+
}
|
|
124
|
+
async function runCommandWithInheritedStdio(command, args) {
|
|
125
|
+
return new Promise((resolve, reject) => {
|
|
126
|
+
const child = spawn(command, [...args], {
|
|
127
|
+
env: process.env,
|
|
128
|
+
stdio: 'inherit',
|
|
129
|
+
});
|
|
130
|
+
child.on('error', (error) => {
|
|
131
|
+
reject(error);
|
|
132
|
+
});
|
|
133
|
+
child.on('close', (code) => {
|
|
134
|
+
resolve(code ?? 1);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
async function restartCli() {
|
|
139
|
+
const entrypoint = process.argv[1];
|
|
140
|
+
if (!entrypoint) {
|
|
141
|
+
throw new Error('Nao foi possivel identificar o entrypoint atual da CLI para reiniciar o processo.');
|
|
142
|
+
}
|
|
143
|
+
await new Promise((resolve, reject) => {
|
|
144
|
+
const child = spawn(process.execPath, [...process.execArgv, entrypoint, ...process.argv.slice(2)], {
|
|
145
|
+
env: process.env,
|
|
146
|
+
stdio: 'inherit',
|
|
147
|
+
});
|
|
148
|
+
child.on('error', (error) => {
|
|
149
|
+
reject(error);
|
|
150
|
+
});
|
|
151
|
+
child.on('spawn', () => {
|
|
152
|
+
resolve();
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
export async function checkForCliUpdate(currentVersion) {
|
|
157
|
+
const { packageName } = readInstalledPackageMetadata();
|
|
158
|
+
try {
|
|
159
|
+
const latestVersion = await fetchLatestPublishedVersion(packageName);
|
|
160
|
+
if (compareVersions(latestVersion, currentVersion) > 0) {
|
|
161
|
+
return {
|
|
162
|
+
currentVersion,
|
|
163
|
+
latestVersion,
|
|
164
|
+
packageName,
|
|
165
|
+
status: 'available',
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
currentVersion,
|
|
170
|
+
latestVersion,
|
|
171
|
+
packageName,
|
|
172
|
+
status: 'current',
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
return {
|
|
177
|
+
currentVersion,
|
|
178
|
+
message: toErrorMessage(error),
|
|
179
|
+
packageName,
|
|
180
|
+
status: 'error',
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
export async function runCliSelfUpdate(packageName, latestVersion) {
|
|
185
|
+
try {
|
|
186
|
+
const exitCode = await runCommandWithInheritedStdio(getNpmCommand(), [
|
|
187
|
+
'install',
|
|
188
|
+
'--global',
|
|
189
|
+
`${packageName}@${latestVersion}`,
|
|
190
|
+
]);
|
|
191
|
+
if (exitCode !== 0) {
|
|
192
|
+
return {
|
|
193
|
+
error: `O npm encerrou a atualizacao com codigo ${exitCode}.`,
|
|
194
|
+
ok: false,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
await restartCli();
|
|
198
|
+
return { ok: true };
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
return {
|
|
202
|
+
error: toErrorMessage(error),
|
|
203
|
+
ok: false,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.entry.d.ts","sourceRoot":"","sources":["../src/cli.entry.tsx"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { render } from 'ink';
|
|
4
|
+
import { App } from './app.component.js';
|
|
5
|
+
import { APP_VERSION } from './shared/constants/app-version.constant.js';
|
|
6
|
+
import { runCliSelfUpdate } from './shared/services/cli-update.service.js';
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const initialInput = args
|
|
9
|
+
.filter((arg) => !arg.startsWith('-'))
|
|
10
|
+
.join(' ')
|
|
11
|
+
.trim();
|
|
12
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
13
|
+
process.stdout.write(`CLI
|
|
14
|
+
|
|
15
|
+
Uso:
|
|
16
|
+
trendify [opcoes]
|
|
17
|
+
|
|
18
|
+
Opcoes:
|
|
19
|
+
-h, --help Mostra esta ajuda
|
|
20
|
+
-v, --version Mostra a versao instalada
|
|
21
|
+
`);
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
if (args.includes('--version') || args.includes('-v')) {
|
|
25
|
+
process.stdout.write(`${APP_VERSION}\n`);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
console.clear();
|
|
29
|
+
let appInstance;
|
|
30
|
+
function mountApp(feedback = null) {
|
|
31
|
+
appInstance = render(_jsx(App, { appVersion: APP_VERSION, initialInput: initialInput, initialNotification: feedback?.message ?? null, initialNotificationTone: feedback?.tone ?? 'success', onSelfUpdate: async (update) => {
|
|
32
|
+
appInstance.unmount();
|
|
33
|
+
console.clear();
|
|
34
|
+
process.stdout.write([
|
|
35
|
+
`Atualizando o CLI de v${update.currentVersion} para v${update.latestVersion}.`,
|
|
36
|
+
`Pacote npm: ${update.packageName}`,
|
|
37
|
+
'',
|
|
38
|
+
].join('\n'));
|
|
39
|
+
const result = await runCliSelfUpdate(update.packageName, update.latestVersion);
|
|
40
|
+
if (result.ok) {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write(`\nFalha ao atualizar automaticamente: ${result.error}\n`);
|
|
44
|
+
process.stdout.write('A CLI atual continuara disponivel.\n');
|
|
45
|
+
mountApp({
|
|
46
|
+
message: `Nao foi possivel atualizar a CLI automaticamente. ${result.error}`,
|
|
47
|
+
tone: 'error',
|
|
48
|
+
});
|
|
49
|
+
} }));
|
|
50
|
+
}
|
|
51
|
+
mountApp();
|
package/dist/cli.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
3
|
import { render } from 'ink';
|
|
4
4
|
import { App } from './app.js';
|
|
5
|
+
import { runCliSelfUpdate } from './cli-update-service.js';
|
|
5
6
|
import { APP_VERSION } from './version.js';
|
|
6
7
|
const args = process.argv.slice(2);
|
|
7
8
|
const initialInput = args
|
|
@@ -25,4 +26,26 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
25
26
|
process.exit(0);
|
|
26
27
|
}
|
|
27
28
|
console.clear();
|
|
28
|
-
|
|
29
|
+
let appInstance;
|
|
30
|
+
function mountApp(feedback = null) {
|
|
31
|
+
appInstance = render(_jsx(App, { appVersion: APP_VERSION, initialInput: initialInput, initialNotification: feedback?.message ?? null, initialNotificationTone: feedback?.tone ?? 'success', onSelfUpdate: async (update) => {
|
|
32
|
+
appInstance.unmount();
|
|
33
|
+
console.clear();
|
|
34
|
+
process.stdout.write([
|
|
35
|
+
`Atualizando o CLI de v${update.currentVersion} para v${update.latestVersion}.`,
|
|
36
|
+
`Pacote npm: ${update.packageName}`,
|
|
37
|
+
'',
|
|
38
|
+
].join('\n'));
|
|
39
|
+
const result = await runCliSelfUpdate(update.packageName, update.latestVersion);
|
|
40
|
+
if (result.ok) {
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
process.stdout.write(`\nFalha ao atualizar automaticamente: ${result.error}\n`);
|
|
44
|
+
process.stdout.write('A CLI atual continuara disponivel.\n');
|
|
45
|
+
mountApp({
|
|
46
|
+
message: `Nao foi possivel atualizar a CLI automaticamente. ${result.error}`,
|
|
47
|
+
tone: 'error',
|
|
48
|
+
});
|
|
49
|
+
} }));
|
|
50
|
+
}
|
|
51
|
+
mountApp();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type LoginPageProps = {
|
|
2
|
+
readonly busy: boolean;
|
|
3
|
+
readonly email: string;
|
|
4
|
+
readonly onEmailChange: (value: string) => void;
|
|
5
|
+
readonly onPasswordChange: (value: string) => void;
|
|
6
|
+
readonly onSubmit: () => void;
|
|
7
|
+
readonly password: string;
|
|
8
|
+
readonly sessionStorageFilePath: string;
|
|
9
|
+
};
|
|
10
|
+
export declare function LoginPage({ busy, email, onEmailChange, onPasswordChange, onSubmit, password, sessionStorageFilePath, }: LoginPageProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=login.page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"login.page.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/pages/login.page.tsx"],"names":[],"mappings":"AAIA,KAAK,cAAc,GAAG;IACpB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAChD,QAAQ,CAAC,gBAAgB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,MAAM,IAAI,CAAC;IAC9B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,sBAAsB,EAAE,MAAM,CAAC;CACzC,CAAC;AAIF,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,KAAK,EACL,aAAa,EACb,gBAAgB,EAChB,QAAQ,EACR,QAAQ,EACR,sBAAsB,GACvB,EAAE,cAAc,2CAsDhB"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { TextField } from '../../../shared/components/text-field.component.js';
|
|
5
|
+
export function LoginPage({ busy, email, onEmailChange, onPasswordChange, onSubmit, password, sessionStorageFilePath, }) {
|
|
6
|
+
const [focusField, setFocusField] = useState('email');
|
|
7
|
+
useInput((_input, key) => {
|
|
8
|
+
if (busy) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
if (key.tab || key.downArrow || key.upArrow) {
|
|
12
|
+
setFocusField((current) => (current === 'email' ? 'password' : 'email'));
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Entre com um usuario ja cadastrado no Supabase para liberar os comandos internos." }), _jsx(Text, { dimColor: true, children: "Apos o login, a CLI tambem valida o espaco de trabalho vinculado a sua conta." }), _jsx(Text, { dimColor: true, children: "Cadastro nao esta habilitado nesta CLI." }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: focusField === 'email' ? 'greenBright' : 'white', children: "Email" }), _jsx(TextField, { focus: focusField === 'email', placeholder: "voce@empresa.com", value: email, onChange: onEmailChange, onSubmit: (value) => {
|
|
16
|
+
if (value.trim()) {
|
|
17
|
+
setFocusField('password');
|
|
18
|
+
}
|
|
19
|
+
} })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: focusField === 'password' ? 'greenBright' : 'white', children: "Senha" }), _jsx(TextField, { focus: focusField === 'password', mask: "*", placeholder: "Sua senha", value: password, onChange: onPasswordChange, onSubmit: () => {
|
|
20
|
+
onSubmit();
|
|
21
|
+
} })] }), _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { dimColor: true, children: "Enter faz login. Tab ou setas alternam o campo ativo." }), _jsxs(Text, { dimColor: true, children: ["Sessao persistida localmente em: ", sessionStorageFilePath] })] })] }));
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { type AuthUser } from '@supabase/supabase-js';
|
|
2
|
+
import { type AuthService } from '../services/auth.service.js';
|
|
3
|
+
type NotificationTone = 'error' | 'info' | 'success';
|
|
4
|
+
type ProfilePageProps = {
|
|
5
|
+
readonly authService: AuthService;
|
|
6
|
+
readonly onBack: () => void;
|
|
7
|
+
readonly onNotificationChange: (notification: string | null, tone?: NotificationTone) => void;
|
|
8
|
+
readonly user: AuthUser;
|
|
9
|
+
};
|
|
10
|
+
export declare function ProfilePage({ authService, onBack, onNotificationChange, user }: ProfilePageProps): import("react/jsx-runtime").JSX.Element;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=profile.page.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"profile.page.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/pages/profile.page.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAO/D,KAAK,gBAAgB,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAKrD,KAAK,gBAAgB,GAAG;IACtB,QAAQ,CAAC,WAAW,EAAE,WAAW,CAAC;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;IAC5B,QAAQ,CAAC,oBAAoB,EAAE,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE,gBAAgB,KAAK,IAAI,CAAC;IAC9F,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;CACzB,CAAC;AAwBF,wBAAgB,WAAW,CAAC,EAAE,WAAW,EAAE,MAAM,EAAE,oBAAoB,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAwQhG"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Box, Text, useInput } from 'ink';
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import { getUserDisplayName } from '../utils/auth-user.util.js';
|
|
5
|
+
import { ActionMenuPage } from '../../../shared/components/action-menu.component.js';
|
|
6
|
+
import { StepHeader } from '../../../shared/components/step-header.component.js';
|
|
7
|
+
import { TextField } from '../../../shared/components/text-field.component.js';
|
|
8
|
+
const PROFILE_ACTIONS = [
|
|
9
|
+
{
|
|
10
|
+
value: 'change-display-name',
|
|
11
|
+
label: 'Alterar display name',
|
|
12
|
+
description: 'Atualiza o nome exibido diretamente no Supabase.',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
value: 'change-password',
|
|
16
|
+
label: 'Alterar senha',
|
|
17
|
+
description: 'Troca a senha da conta autenticada.',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
value: 'back',
|
|
21
|
+
label: 'Voltar ao menu',
|
|
22
|
+
description: 'Retorna ao menu principal da CLI.',
|
|
23
|
+
},
|
|
24
|
+
];
|
|
25
|
+
function isReauthenticationRequired(errorCode) {
|
|
26
|
+
return errorCode === 'reauthentication_needed' || errorCode === 'reauth_nonce_missing';
|
|
27
|
+
}
|
|
28
|
+
export function ProfilePage({ authService, onBack, onNotificationChange, user }) {
|
|
29
|
+
const currentDisplayName = getUserDisplayName(user);
|
|
30
|
+
const [step, setStep] = useState('menu');
|
|
31
|
+
const [selectedActionIndex, setSelectedActionIndex] = useState(0);
|
|
32
|
+
const [displayName, setDisplayName] = useState(currentDisplayName ?? '');
|
|
33
|
+
const [newPassword, setNewPassword] = useState('');
|
|
34
|
+
const [confirmPassword, setConfirmPassword] = useState('');
|
|
35
|
+
const [passwordFocus, setPasswordFocus] = useState('new-password');
|
|
36
|
+
const [nonce, setNonce] = useState('');
|
|
37
|
+
const [pendingPassword, setPendingPassword] = useState('');
|
|
38
|
+
const [busy, setBusy] = useState(false);
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
setDisplayName(currentDisplayName ?? '');
|
|
41
|
+
}, [currentDisplayName]);
|
|
42
|
+
function resetPasswordFlow() {
|
|
43
|
+
setNewPassword('');
|
|
44
|
+
setConfirmPassword('');
|
|
45
|
+
setNonce('');
|
|
46
|
+
setPendingPassword('');
|
|
47
|
+
setPasswordFocus('new-password');
|
|
48
|
+
}
|
|
49
|
+
function returnToProfileMenu() {
|
|
50
|
+
setStep('menu');
|
|
51
|
+
setSelectedActionIndex(0);
|
|
52
|
+
resetPasswordFlow();
|
|
53
|
+
setDisplayName(currentDisplayName ?? '');
|
|
54
|
+
}
|
|
55
|
+
async function handleDisplayNameSubmit() {
|
|
56
|
+
const trimmedDisplayName = displayName.trim();
|
|
57
|
+
if (!trimmedDisplayName) {
|
|
58
|
+
onNotificationChange('Informe um display name valido para continuar.', 'error');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (trimmedDisplayName === currentDisplayName) {
|
|
62
|
+
onNotificationChange('O display name informado ja esta em uso na sua conta.', 'info');
|
|
63
|
+
returnToProfileMenu();
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
setBusy(true);
|
|
67
|
+
const result = await authService.updateDisplayName(trimmedDisplayName);
|
|
68
|
+
setBusy(false);
|
|
69
|
+
if (result.error) {
|
|
70
|
+
onNotificationChange(result.error, 'error');
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
onNotificationChange(`Display name atualizado para "${trimmedDisplayName}".`, 'success');
|
|
74
|
+
returnToProfileMenu();
|
|
75
|
+
}
|
|
76
|
+
async function requestPasswordReauthentication(password) {
|
|
77
|
+
const reauthenticationResult = await authService.sendPasswordReauthenticationCode();
|
|
78
|
+
if (reauthenticationResult.error) {
|
|
79
|
+
onNotificationChange(reauthenticationResult.error, 'error');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
setPendingPassword(password);
|
|
83
|
+
setNonce('');
|
|
84
|
+
setStep('confirm-password-code');
|
|
85
|
+
onNotificationChange('O Supabase enviou um codigo de verificacao. Digite esse codigo para concluir a troca de senha.', 'info');
|
|
86
|
+
}
|
|
87
|
+
async function handlePasswordSubmit(password, currentNonce) {
|
|
88
|
+
if (!password.trim()) {
|
|
89
|
+
onNotificationChange('Informe a nova senha para continuar.', 'error');
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
setBusy(true);
|
|
93
|
+
const result = await authService.updatePassword(password, currentNonce);
|
|
94
|
+
setBusy(false);
|
|
95
|
+
if (result.error) {
|
|
96
|
+
if (isReauthenticationRequired(result.errorCode) && !currentNonce) {
|
|
97
|
+
await requestPasswordReauthentication(password);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
onNotificationChange(result.error, 'error');
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
onNotificationChange('Senha atualizada com sucesso.', 'success');
|
|
104
|
+
returnToProfileMenu();
|
|
105
|
+
}
|
|
106
|
+
async function handleNewPasswordSubmit() {
|
|
107
|
+
if (!newPassword.trim() || !confirmPassword.trim()) {
|
|
108
|
+
onNotificationChange('Preencha a nova senha e a confirmacao.', 'error');
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
if (newPassword !== confirmPassword) {
|
|
112
|
+
onNotificationChange('A confirmacao de senha nao confere.', 'error');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
await handlePasswordSubmit(newPassword);
|
|
116
|
+
}
|
|
117
|
+
async function handlePasswordNonceSubmit() {
|
|
118
|
+
const trimmedNonce = nonce.trim();
|
|
119
|
+
if (!trimmedNonce) {
|
|
120
|
+
onNotificationChange('Informe o codigo de verificacao enviado pelo Supabase.', 'error');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
await handlePasswordSubmit(pendingPassword, trimmedNonce);
|
|
124
|
+
}
|
|
125
|
+
useInput((_input, key) => {
|
|
126
|
+
if (busy) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (step === 'menu') {
|
|
130
|
+
if (key.escape) {
|
|
131
|
+
onBack();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (key.upArrow) {
|
|
135
|
+
setSelectedActionIndex((current) => (current === 0 ? PROFILE_ACTIONS.length - 1 : current - 1));
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
if (key.downArrow) {
|
|
139
|
+
setSelectedActionIndex((current) => (current === PROFILE_ACTIONS.length - 1 ? 0 : current + 1));
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (key.return) {
|
|
143
|
+
const action = PROFILE_ACTIONS[selectedActionIndex];
|
|
144
|
+
if (!action || action.value === 'back') {
|
|
145
|
+
onBack();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
onNotificationChange(null);
|
|
149
|
+
setStep(action.value);
|
|
150
|
+
}
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
if (key.escape) {
|
|
154
|
+
onNotificationChange(null);
|
|
155
|
+
returnToProfileMenu();
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (step === 'change-password' && (key.tab || key.upArrow || key.downArrow)) {
|
|
159
|
+
setPasswordFocus((current) => (current === 'new-password' ? 'confirm-password' : 'new-password'));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
if (step === 'menu') {
|
|
163
|
+
return (_jsxs(ActionMenuPage, { title: "Profile", subtitle: "Escolha uma acao para gerenciar sua conta autenticada.", options: PROFILE_ACTIONS, selectedIndex: selectedActionIndex, hintText: "Use as setas para navegar. Enter confirma. Esc volta ao menu.", children: [_jsxs(Text, { children: ["Email: ", user.email ?? 'nao informado'] }), _jsxs(Text, { children: ["Display name: ", currentDisplayName ?? 'nao definido'] })] }));
|
|
164
|
+
}
|
|
165
|
+
if (step === 'change-display-name') {
|
|
166
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StepHeader, { title: "Alterar display name", subtitle: `Atual: ${currentDisplayName ?? 'nao definido'}. Digite o novo nome e pressione Enter.` }), _jsx(TextField, { placeholder: "Ex.: Leonardo", value: displayName, onChange: setDisplayName, onSubmit: () => {
|
|
167
|
+
void handleDisplayNameSubmit();
|
|
168
|
+
} }), _jsx(Text, { dimColor: true, children: "Esc volta para o menu de profile." })] }));
|
|
169
|
+
}
|
|
170
|
+
if (step === 'confirm-password-code') {
|
|
171
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StepHeader, { title: "Confirmar troca de senha", subtitle: "Digite o codigo recebido por email ou telefone para concluir a troca da senha." }), _jsx(TextField, { placeholder: "Codigo de verificacao", value: nonce, onChange: setNonce, onSubmit: () => {
|
|
172
|
+
void handlePasswordNonceSubmit();
|
|
173
|
+
} }), _jsx(Text, { dimColor: true, children: "Esc volta para o menu de profile." })] }));
|
|
174
|
+
}
|
|
175
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(StepHeader, { title: "Alterar senha", subtitle: "Defina a nova senha. Se o Supabase pedir reautenticacao, um codigo sera enviado automaticamente." }), _jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Text, { color: passwordFocus === 'new-password' ? 'greenBright' : 'white', children: "Nova senha" }), _jsx(TextField, { focus: passwordFocus === 'new-password', mask: "*", placeholder: "Digite a nova senha", value: newPassword, onChange: setNewPassword, onSubmit: () => {
|
|
176
|
+
setPasswordFocus('confirm-password');
|
|
177
|
+
} })] }), _jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: passwordFocus === 'confirm-password' ? 'greenBright' : 'white', children: "Confirmar senha" }), _jsx(TextField, { focus: passwordFocus === 'confirm-password', mask: "*", placeholder: "Repita a nova senha", value: confirmPassword, onChange: setConfirmPassword, onSubmit: () => {
|
|
178
|
+
void handleNewPasswordSubmit();
|
|
179
|
+
} })] }), _jsx(Text, { dimColor: true, children: "Tab ou setas alternam o campo ativo. Enter envia. Esc volta ao menu de profile." })] }));
|
|
180
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type SupportedStorage } from '@supabase/supabase-js';
|
|
2
|
+
export declare class FileStorage implements SupportedStorage {
|
|
3
|
+
private readonly filePath;
|
|
4
|
+
private queue;
|
|
5
|
+
constructor(filePath: string);
|
|
6
|
+
getItem(key: string): Promise<string | null>;
|
|
7
|
+
removeItem(key: string): Promise<void>;
|
|
8
|
+
setItem(key: string, value: string): Promise<void>;
|
|
9
|
+
private runExclusive;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=auth-storage.service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-storage.service.d.ts","sourceRoot":"","sources":["../../../../src/modules/auth/services/auth-storage.service.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AA0C9D,qBAAa,WAAY,YAAW,gBAAgB;IAG/B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAF5C,OAAO,CAAC,KAAK,CAAuC;gBAEhB,QAAQ,EAAE,MAAM;IAEvC,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IAQ5C,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAatC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAS/D,OAAO,CAAC,YAAY;CAUrB"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { chmod, mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
async function ensureParentDirectory(filePath) {
|
|
4
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
5
|
+
}
|
|
6
|
+
async function readStorageMap(filePath) {
|
|
7
|
+
try {
|
|
8
|
+
const fileContents = await readFile(filePath, 'utf8');
|
|
9
|
+
const parsedContents = JSON.parse(fileContents);
|
|
10
|
+
if (!parsedContents || typeof parsedContents !== 'object' || Array.isArray(parsedContents)) {
|
|
11
|
+
return {};
|
|
12
|
+
}
|
|
13
|
+
return Object.fromEntries(Object.entries(parsedContents).filter((entry) => typeof entry[1] === 'string'));
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
17
|
+
return {};
|
|
18
|
+
}
|
|
19
|
+
return {};
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
async function writeStorageMap(filePath, data) {
|
|
23
|
+
await ensureParentDirectory(filePath);
|
|
24
|
+
const tempFilePath = `${filePath}.tmp`;
|
|
25
|
+
await writeFile(tempFilePath, JSON.stringify(data, null, 2), 'utf8');
|
|
26
|
+
await rename(tempFilePath, filePath);
|
|
27
|
+
if (process.platform !== 'win32') {
|
|
28
|
+
await chmod(filePath, 0o600).catch(() => undefined);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class FileStorage {
|
|
32
|
+
filePath;
|
|
33
|
+
queue = Promise.resolve();
|
|
34
|
+
constructor(filePath) {
|
|
35
|
+
this.filePath = filePath;
|
|
36
|
+
}
|
|
37
|
+
async getItem(key) {
|
|
38
|
+
return this.runExclusive(async () => {
|
|
39
|
+
const data = await readStorageMap(this.filePath);
|
|
40
|
+
return data[key] ?? null;
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async removeItem(key) {
|
|
44
|
+
await this.runExclusive(async () => {
|
|
45
|
+
const data = await readStorageMap(this.filePath);
|
|
46
|
+
if (!(key in data)) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
delete data[key];
|
|
50
|
+
await writeStorageMap(this.filePath, data);
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async setItem(key, value) {
|
|
54
|
+
await this.runExclusive(async () => {
|
|
55
|
+
const data = await readStorageMap(this.filePath);
|
|
56
|
+
data[key] = value;
|
|
57
|
+
await writeStorageMap(this.filePath, data);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
runExclusive(operation) {
|
|
61
|
+
const nextOperation = this.queue.then(operation, operation);
|
|
62
|
+
this.queue = nextOperation.then(() => undefined, () => undefined);
|
|
63
|
+
return nextOperation;
|
|
64
|
+
}
|
|
65
|
+
}
|