@super-protocol/sp-cli 0.0.1-alpha.35 → 0.0.2

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.
Files changed (69) hide show
  1. package/README.md +260 -65
  2. package/bin/dev.cmd +1 -1
  3. package/bin/dev.js +5 -3
  4. package/bin/run.js +3 -2
  5. package/dist/commands/auth/login.d.ts +3 -2
  6. package/dist/commands/auth/login.js +52 -24
  7. package/dist/commands/auth/me.d.ts +3 -2
  8. package/dist/commands/auth/me.js +9 -4
  9. package/dist/commands/base.d.ts +18 -0
  10. package/dist/commands/base.js +45 -0
  11. package/dist/commands/config/add.d.ts +13 -0
  12. package/dist/commands/config/add.js +84 -0
  13. package/dist/commands/config/base.d.ts +14 -0
  14. package/dist/commands/config/base.js +62 -0
  15. package/dist/commands/config/create.d.ts +11 -0
  16. package/dist/commands/config/create.js +51 -0
  17. package/dist/commands/config/delete.d.ts +10 -0
  18. package/dist/commands/config/delete.js +25 -0
  19. package/dist/commands/config/index.d.ts +6 -0
  20. package/dist/commands/config/index.js +22 -0
  21. package/dist/commands/config/list.d.ts +6 -0
  22. package/dist/commands/config/list.js +39 -0
  23. package/dist/commands/config/show.d.ts +6 -0
  24. package/dist/commands/config/show.js +10 -0
  25. package/dist/commands/config/use.d.ts +6 -0
  26. package/dist/commands/config/use.js +25 -0
  27. package/dist/config/config-file.schema.d.ts +6 -0
  28. package/dist/config/config-file.schema.js +5 -0
  29. package/dist/config/config.schema.d.ts +2 -0
  30. package/dist/config/config.schema.js +12 -1
  31. package/dist/constants.d.ts +3 -0
  32. package/dist/constants.js +5 -0
  33. package/dist/hooks/init/init-container.d.ts +3 -0
  34. package/dist/hooks/init/init-container.js +6 -0
  35. package/dist/index.d.ts +2 -1
  36. package/dist/index.js +23 -1
  37. package/dist/interfaces/index.d.ts +0 -1
  38. package/dist/interfaces/index.js +0 -1
  39. package/dist/interfaces/manager.interface.d.ts +1 -1
  40. package/dist/lib/container.d.ts +40 -0
  41. package/dist/lib/container.js +177 -0
  42. package/dist/logger.d.ts +6 -0
  43. package/dist/logger.js +62 -0
  44. package/dist/managers/account-manager.d.ts +2 -2
  45. package/dist/managers/account-manager.js +18 -9
  46. package/dist/managers/config-file-manager.d.ts +50 -0
  47. package/dist/managers/config-file-manager.js +278 -0
  48. package/dist/managers/config-manager.d.ts +12 -6
  49. package/dist/managers/config-manager.js +38 -14
  50. package/dist/managers/index.d.ts +1 -2
  51. package/dist/managers/index.js +1 -2
  52. package/dist/middlewares/auth-middleware.d.ts +9 -0
  53. package/dist/middlewares/auth-middleware.js +87 -0
  54. package/dist/middlewares/cookies-middleware.d.ts +8 -0
  55. package/dist/middlewares/cookies-middleware.js +80 -0
  56. package/dist/utils/helper.d.ts +1 -0
  57. package/dist/utils/helper.js +1 -0
  58. package/oclif.manifest.json +545 -7
  59. package/package.json +32 -14
  60. package/dist/commands/refresh.d.ts +0 -4
  61. package/dist/commands/refresh.js +0 -7
  62. package/dist/config/constants.d.ts +0 -1
  63. package/dist/config/constants.js +0 -2
  64. package/dist/interfaces/abstract.command.d.ts +0 -16
  65. package/dist/interfaces/abstract.command.js +0 -59
  66. package/dist/managers/auth-manager.d.ts +0 -19
  67. package/dist/managers/auth-manager.js +0 -101
  68. package/dist/managers/cookies-manager.d.ts +0 -15
  69. package/dist/managers/cookies-manager.js +0 -92
@@ -1,19 +1,25 @@
1
- import { Config } from '@oclif/core/interfaces';
2
1
  import pino from 'pino';
3
- import { CliConfig } from '../config/config.schema.js';
2
+ import { Auth, CliConfig } from '../config/config.schema.js';
4
3
  import { IConfigManager } from '../interfaces/config-manager.interface.js';
5
4
  export declare class ConfigManager implements IConfigManager {
6
- private readonly oclifConfig;
7
- private readonly logger;
8
5
  private readonly configFile;
6
+ private readonly logger;
9
7
  private config;
10
- constructor(oclifConfig: Config, logger: pino.BaseLogger, configFile?: string);
11
- get<K extends keyof CliConfig, V extends CliConfig[K]>(key: K): Promise<V>;
8
+ private readonly configDir;
9
+ constructor(configFile: string, logger: pino.BaseLogger);
10
+ get<K extends keyof CliConfig, V extends CliConfig[K]>(key: K): Promise<undefined | V>;
12
11
  getConfig(): CliConfig;
12
+ getCookies(): Promise<unknown>;
13
+ getCredentials(): Promise<{
14
+ accessKey: string;
15
+ } | undefined>;
13
16
  init(): Promise<void>;
14
17
  load(): Promise<CliConfig>;
18
+ makeBackup(): void;
15
19
  save(): Promise<void>;
16
20
  set<K extends keyof CliConfig, V extends CliConfig[K]>(key: K, value: V): Promise<void>;
21
+ setCookies(cookies: unknown): Promise<void>;
22
+ setCredentials(auth: Auth): Promise<void>;
17
23
  private getConfigPath;
18
24
  private hasConfig;
19
25
  }
@@ -1,16 +1,17 @@
1
1
  import { ux } from '@oclif/core';
2
2
  import * as fs from 'node:fs';
3
3
  import path from 'node:path';
4
+ import { DIR_ACCESS_PERMS, FILE_ACCESS_PERMS } from '../constants.js';
4
5
  import { ConfigurationNotFoundError } from '../errors.js';
5
6
  export class ConfigManager {
6
- oclifConfig;
7
- logger;
8
7
  configFile;
8
+ logger;
9
9
  config = {};
10
- constructor(oclifConfig, logger, configFile = 'config.json') {
11
- this.oclifConfig = oclifConfig;
12
- this.logger = logger;
10
+ configDir;
11
+ constructor(configFile, logger) {
13
12
  this.configFile = configFile;
13
+ this.logger = logger;
14
+ this.configDir = path.dirname(configFile);
14
15
  }
15
16
  async get(key) {
16
17
  return this.config[key];
@@ -23,22 +24,30 @@ export class ConfigManager {
23
24
  const raw = fs.readFileSync(configPath, 'utf8');
24
25
  return JSON.parse(raw);
25
26
  }
27
+ getCookies() {
28
+ return this.get('cookies');
29
+ }
30
+ getCredentials() {
31
+ return this.get('auth');
32
+ }
26
33
  async init() {
27
34
  try {
28
35
  await this.load();
29
36
  }
30
37
  catch (error) {
31
38
  if (error instanceof ConfigurationNotFoundError) {
32
- ux.stdout('Config file not found. Creating new one ');
39
+ this.logger.error({ err: error }, 'Config file not found. Creating new one');
33
40
  await this.save();
34
41
  }
35
42
  else if (error instanceof SyntaxError) {
36
- ux.stdout('Config file corrupted');
43
+ this.makeBackup();
44
+ this.logger.error({ err: error }, 'Config file corrupted');
37
45
  await this.save();
38
46
  }
39
47
  else {
40
- ux.stderr('Something went wrong');
41
- this.logger.error(error);
48
+ this.makeBackup();
49
+ this.logger.error({ err: error }, 'Something went wrong');
50
+ ux.error('Something went wrong', { exit: 1 });
42
51
  }
43
52
  }
44
53
  }
@@ -47,21 +56,36 @@ export class ConfigManager {
47
56
  this.config = config;
48
57
  return this.config;
49
58
  }
59
+ makeBackup() {
60
+ if (this.hasConfig()) {
61
+ fs.renameSync(this.getConfigPath(), `${this.getConfigPath()}.${Date.now()}.bak`);
62
+ }
63
+ }
50
64
  async save() {
51
- fs.mkdirSync(this.oclifConfig.configDir, { recursive: true });
52
- console.log(this.getConfigPath(), JSON.stringify(this.config));
65
+ fs.mkdirSync(this.configDir, { mode: DIR_ACCESS_PERMS, recursive: true });
66
+ this.logger.debug({ configFile: this.getConfigPath() }, 'Saving config file');
53
67
  fs.writeFileSync(this.getConfigPath(), JSON.stringify(this.config, undefined, ' '), {
54
68
  encoding: 'utf8',
55
- flush: true,
69
+ flag: 'w',
70
+ mode: FILE_ACCESS_PERMS,
56
71
  });
57
72
  }
58
73
  async set(key, value) {
59
74
  this.config[key] = value;
60
- this.logger.debug({ key, value }, 'Saving parametes to file');
75
+ this.logger.debug({ key }, 'Saving parameters to file');
61
76
  await this.save();
62
77
  }
78
+ async setCookies(cookies) {
79
+ await this.set('cookies', cookies);
80
+ }
81
+ async setCredentials(auth) {
82
+ await this.set('auth', auth);
83
+ }
63
84
  getConfigPath() {
64
- return path.join(this.oclifConfig.configDir, this.configFile);
85
+ if (path.isAbsolute(this.configFile)) {
86
+ return this.configFile;
87
+ }
88
+ return path.join(this.configDir, this.configFile);
65
89
  }
66
90
  hasConfig() {
67
91
  const configPath = this.getConfigPath();
@@ -1,4 +1,3 @@
1
1
  export * from './account-manager.js';
2
- export * from './auth-manager.js';
2
+ export * from './config-file-manager.js';
3
3
  export * from './config-manager.js';
4
- export * from './cookies-manager.js';
@@ -1,4 +1,3 @@
1
1
  export * from './account-manager.js';
2
- export * from './auth-manager.js';
2
+ export * from './config-file-manager.js';
3
3
  export * from './config-manager.js';
4
- export * from './cookies-manager.js';
@@ -0,0 +1,9 @@
1
+ import type { MiddlewareFn, ProviderClient } from '@super-protocol/provider-client';
2
+ import type pino from 'pino';
3
+ import { type Auth } from '../config/config.schema.js';
4
+ interface CredentialsStorage {
5
+ getCredentials(): Promise<Auth | undefined>;
6
+ setCredentials(auth: Auth): void;
7
+ }
8
+ export declare function createAuthMiddleware(configManager: CredentialsStorage, providerClient: ProviderClient, logger: pino.BaseLogger): Promise<ReturnType<MiddlewareFn>>;
9
+ export {};
@@ -0,0 +1,87 @@
1
+ import { decode } from 'jsonwebtoken';
2
+ import { AuthorizationRequiredError } from '../errors.js';
3
+ function isAccessKeyExpired(accessKey) {
4
+ try {
5
+ const payload = decode(accessKey);
6
+ if (!payload || typeof payload.exp !== 'number') {
7
+ return true;
8
+ }
9
+ const now = Math.floor(Date.now() / 1000);
10
+ return now >= payload.exp;
11
+ }
12
+ catch {
13
+ return true;
14
+ }
15
+ }
16
+ export async function createAuthMiddleware(configManager, providerClient, logger) {
17
+ let auth;
18
+ let refreshPromise;
19
+ const performRefresh = async () => {
20
+ if (!auth) {
21
+ return undefined;
22
+ }
23
+ logger.info('Performed refresh token');
24
+ let refreshResult;
25
+ try {
26
+ refreshResult = await providerClient.POST('/api/auth/refresh-access');
27
+ }
28
+ catch (error) {
29
+ logger.error({ err: error }, 'failure refresh token');
30
+ const message = error instanceof Error ? error.message : 'Unknown error';
31
+ throw new AuthorizationRequiredError(`Unable to reach provider API: ${message}`);
32
+ }
33
+ const { data, error: refreshError } = refreshResult;
34
+ if (refreshError) {
35
+ logger.error({ refreshError }, 'Failure refresh response');
36
+ throw new AuthorizationRequiredError(`Unable to refresh access key: ${JSON.stringify(refreshError)}`);
37
+ }
38
+ if (!data || !data.accessToken) {
39
+ logger.error({ response: data }, 'Server returned incorrect response');
40
+ throw new AuthorizationRequiredError('Provider returned an unexpected refresh response.');
41
+ }
42
+ const updatedAuth = {
43
+ accessKey: data.accessToken,
44
+ };
45
+ await setCredentials(updatedAuth);
46
+ return updatedAuth.accessKey;
47
+ };
48
+ const refreshAccessKey = async () => {
49
+ if (!auth) {
50
+ return undefined;
51
+ }
52
+ if (!refreshPromise) {
53
+ refreshPromise = performRefresh().finally(() => {
54
+ refreshPromise = undefined;
55
+ });
56
+ }
57
+ return refreshPromise;
58
+ };
59
+ const ensureAccessKey = async (forceRefresh = false) => {
60
+ if (!auth) {
61
+ return undefined;
62
+ }
63
+ const { accessKey } = auth;
64
+ if (!forceRefresh && accessKey && !isAccessKeyExpired(accessKey)) {
65
+ return accessKey;
66
+ }
67
+ return refreshAccessKey();
68
+ };
69
+ const setCredentials = async (newAuth) => {
70
+ auth = newAuth;
71
+ await configManager.setCredentials(newAuth);
72
+ };
73
+ auth = await configManager.getCredentials();
74
+ if (!auth) {
75
+ throw new AuthorizationRequiredError('Authorization not found please login first');
76
+ }
77
+ const middleware = {
78
+ async onRequest({ request }) {
79
+ const accessToken = await ensureAccessKey();
80
+ if (accessToken) {
81
+ request.headers.set('Authorization', `Bearer ${accessToken}`);
82
+ }
83
+ return request;
84
+ },
85
+ };
86
+ return middleware;
87
+ }
@@ -0,0 +1,8 @@
1
+ import type { MiddlewareFn } from '@super-protocol/provider-client';
2
+ import type pino from 'pino';
3
+ interface CookiesStorage {
4
+ getCookies(): Promise<unknown>;
5
+ setCookies(cookies: unknown): Promise<void>;
6
+ }
7
+ export declare const createCookieMiddleware: (configManager: CookiesStorage, logger: pino.BaseLogger, baseUrl: string) => Promise<ReturnType<MiddlewareFn>>;
8
+ export {};
@@ -0,0 +1,80 @@
1
+ import { CookieJar } from 'tough-cookie';
2
+ const extractSetCookieHeaders = (response) => {
3
+ const cookiesFromGetter = response.headers.getSetCookie?.();
4
+ if (cookiesFromGetter && cookiesFromGetter.length > 0) {
5
+ return cookiesFromGetter;
6
+ }
7
+ // For capability to nodejs < 20
8
+ const headersWithRaw = response.headers;
9
+ const raw = headersWithRaw.raw?.();
10
+ const headerList = raw?.['set-cookie'];
11
+ if (headerList && headerList.length > 0) {
12
+ return headerList;
13
+ }
14
+ const headerValue = response.headers.get('set-cookie');
15
+ return headerValue ? [headerValue] : [];
16
+ };
17
+ export const createCookieMiddleware = async (configManager, logger, baseUrl) => {
18
+ let cookieJar = new CookieJar();
19
+ const loadCookieJar = async () => {
20
+ try {
21
+ const stored = await configManager.getCookies();
22
+ if (stored && typeof stored === 'object') {
23
+ cookieJar = CookieJar.fromJSON(stored);
24
+ return;
25
+ }
26
+ }
27
+ catch (error) {
28
+ logger.warn({ err: error }, 'Failed to load cookies from config');
29
+ }
30
+ cookieJar = new CookieJar();
31
+ };
32
+ const persistCookieJar = async () => {
33
+ try {
34
+ const serialized = cookieJar.toJSON();
35
+ await configManager.setCookies(serialized);
36
+ }
37
+ catch (error) {
38
+ logger.warn({ err: error }, 'Failed to persist cookies');
39
+ }
40
+ };
41
+ await loadCookieJar();
42
+ return {
43
+ async onRequest({ request }) {
44
+ const requestUrl = request.url ?? baseUrl;
45
+ try {
46
+ const cookieHeader = await cookieJar.getCookieString(requestUrl);
47
+ if (cookieHeader) {
48
+ request.headers.set('Cookie', cookieHeader);
49
+ }
50
+ }
51
+ catch (error) {
52
+ logger.warn({ err: error, requestUrl }, 'Failed to attach cookies to request');
53
+ }
54
+ },
55
+ async onResponse({ response }) {
56
+ const rawCookies = extractSetCookieHeaders(response);
57
+ if (rawCookies.length === 0) {
58
+ return response;
59
+ }
60
+ const targetUrl = response.url || baseUrl;
61
+ const updates = await Promise.all(rawCookies.map(async (rawCookie) => {
62
+ try {
63
+ const storedCookie = await cookieJar.setCookie(rawCookie, targetUrl);
64
+ if (storedCookie) {
65
+ logger.debug({ cookie: storedCookie.key }, 'Updated cookie from response');
66
+ }
67
+ return true;
68
+ }
69
+ catch (error) {
70
+ logger.warn({ err: error, rawCookie }, 'Failed to store cookie from response');
71
+ return false;
72
+ }
73
+ }));
74
+ if (updates.some(Boolean)) {
75
+ await persistCookieJar();
76
+ }
77
+ return response;
78
+ },
79
+ };
80
+ };
@@ -0,0 +1 @@
1
+ export declare const getConfigName: (name: string) => string;
@@ -0,0 +1 @@
1
+ export const getConfigName = (name) => name.toLowerCase().replaceAll(/[^a-z0-9]+/g, '-') + '.config.json';