@hyperdrive.bot/sign-cli 0.1.4 → 0.1.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/commands/api.js +1 -1
- package/dist/commands/modules/list.js +1 -3
- package/dist/hooks/command-not-found.js +4 -4
- package/dist/lib/manifest.d.ts +9 -4
- package/dist/lib/manifest.js +69 -27
- package/dist/lib/sign-api.d.ts +1 -1
- package/dist/lib/sign-api.js +1 -1
- package/oclif.manifest.json +1 -1
- package/package.json +1 -1
package/dist/commands/api.js
CHANGED
|
@@ -46,7 +46,7 @@ export default class Api extends Command {
|
|
|
46
46
|
let fullUrl;
|
|
47
47
|
if (flags.module) {
|
|
48
48
|
const { getManifest } = await import('../lib/manifest.js');
|
|
49
|
-
const manifest = await getManifest(
|
|
49
|
+
const manifest = await getManifest();
|
|
50
50
|
const resource = Object.values(manifest.resources).find(r => r.module === flags.module);
|
|
51
51
|
if (resource) {
|
|
52
52
|
fullUrl = `${resource.apiGateway}${path}`;
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { Command, Flags } from '@oclif/core';
|
|
8
8
|
import { getManifest, listResources } from '../../lib/manifest.js';
|
|
9
|
-
import { createSignClient } from '../../lib/sign-api.js';
|
|
10
9
|
export default class ModulesList extends Command {
|
|
11
10
|
static description = 'List all available API resources and actions';
|
|
12
11
|
static examples = [
|
|
@@ -21,8 +20,7 @@ export default class ModulesList extends Command {
|
|
|
21
20
|
async run() {
|
|
22
21
|
const { flags } = await this.parse(ModulesList);
|
|
23
22
|
try {
|
|
24
|
-
const
|
|
25
|
-
const manifest = await getManifest(client, flags.refresh);
|
|
23
|
+
const manifest = await getManifest(undefined, flags.refresh);
|
|
26
24
|
const resources = listResources(manifest);
|
|
27
25
|
if (flags.json) {
|
|
28
26
|
this.log(JSON.stringify(resources, null, 2));
|
|
@@ -24,7 +24,7 @@ const hook = async function (opts) {
|
|
|
24
24
|
const resource = parts[0];
|
|
25
25
|
try {
|
|
26
26
|
const client = createSignClient();
|
|
27
|
-
const manifest = await getManifest(
|
|
27
|
+
const manifest = await getManifest();
|
|
28
28
|
const res = manifest.resources[resource];
|
|
29
29
|
if (res) {
|
|
30
30
|
console.log(`\nResource: ${resource} (module: ${res.module})\n`);
|
|
@@ -56,11 +56,11 @@ const hook = async function (opts) {
|
|
|
56
56
|
if (resource === 'manifest' && action === 'refresh') {
|
|
57
57
|
const { clearManifestCache } = await import('../lib/manifest.js');
|
|
58
58
|
await clearManifestCache();
|
|
59
|
-
const manifest = await getManifest(
|
|
59
|
+
const manifest = await getManifest(undefined, true);
|
|
60
60
|
console.log(`Manifest refreshed: ${Object.keys(manifest.resources).length} resources discovered.`);
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
-
const manifest = await getManifest(
|
|
63
|
+
const manifest = await getManifest();
|
|
64
64
|
const match = resolveAction(manifest, resource, action);
|
|
65
65
|
if (!match) {
|
|
66
66
|
const res = manifest.resources[resource];
|
|
@@ -90,7 +90,7 @@ const hook = async function (opts) {
|
|
|
90
90
|
catch (error) {
|
|
91
91
|
const err = error;
|
|
92
92
|
if (err.message.includes('Missing required argument')) {
|
|
93
|
-
const match = resolveAction(await getManifest(
|
|
93
|
+
const match = resolveAction(await getManifest(), resource, action);
|
|
94
94
|
const argsStr = match?.action.args.map(a => `<${a}>`).join(' ') || '';
|
|
95
95
|
console.error(`Error: ${err.message}`);
|
|
96
96
|
console.error(`Usage: sign ${resource} ${action} ${argsStr}`);
|
package/dist/lib/manifest.d.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Manifest Client
|
|
3
3
|
*
|
|
4
|
-
* Fetches and caches the API manifest from the
|
|
4
|
+
* Fetches and caches the API manifest from the tenant domain.
|
|
5
5
|
* The manifest contains all discoverable resources + actions.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. After login, CLI fetches https://{domain}/tenant/manifest
|
|
9
|
+
* 2. Cached locally at ~/.sign/manifest.{domain}.json
|
|
10
|
+
* 3. Refetched daily or on "sign manifest refresh"
|
|
6
11
|
*/
|
|
7
12
|
export interface ManifestAction {
|
|
8
13
|
name: string;
|
|
@@ -23,13 +28,13 @@ export interface Manifest {
|
|
|
23
28
|
resources: Record<string, ManifestResource>;
|
|
24
29
|
}
|
|
25
30
|
/**
|
|
26
|
-
* Get the manifest
|
|
31
|
+
* Get the manifest -- from cache if fresh, otherwise from the tenant domain
|
|
27
32
|
*/
|
|
28
|
-
export declare const getManifest: (
|
|
33
|
+
export declare const getManifest: (_apiClient?: {
|
|
29
34
|
get: (path: string) => Promise<unknown>;
|
|
30
35
|
}, forceRefresh?: boolean) => Promise<Manifest>;
|
|
31
36
|
/**
|
|
32
|
-
* Clear the cached manifest
|
|
37
|
+
* Clear the cached manifest for the current domain
|
|
33
38
|
*/
|
|
34
39
|
export declare const clearManifestCache: () => Promise<void>;
|
|
35
40
|
/**
|
package/dist/lib/manifest.js
CHANGED
|
@@ -1,38 +1,79 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Manifest Client
|
|
3
3
|
*
|
|
4
|
-
* Fetches and caches the API manifest from the
|
|
4
|
+
* Fetches and caches the API manifest from the tenant domain.
|
|
5
5
|
* The manifest contains all discoverable resources + actions.
|
|
6
|
+
*
|
|
7
|
+
* Flow:
|
|
8
|
+
* 1. After login, CLI fetches https://{domain}/tenant/manifest
|
|
9
|
+
* 2. Cached locally at ~/.sign/manifest.{domain}.json
|
|
10
|
+
* 3. Refetched daily or on "sign manifest refresh"
|
|
6
11
|
*/
|
|
7
12
|
import { readFile, writeFile, mkdir } from 'node:fs/promises';
|
|
8
|
-
import { existsSync } from 'node:fs';
|
|
13
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
9
14
|
import { join } from 'node:path';
|
|
10
15
|
import { homedir } from 'node:os';
|
|
11
16
|
// --- Config ---
|
|
12
17
|
const CACHE_DIR = join(homedir(), '.sign');
|
|
13
|
-
const
|
|
14
|
-
|
|
18
|
+
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
19
|
+
/**
|
|
20
|
+
* Get cache file path for a domain
|
|
21
|
+
*/
|
|
22
|
+
const getCacheFile = (domain) => {
|
|
23
|
+
const safeDomain = domain.replace(/[^a-zA-Z0-9.-]/g, '_');
|
|
24
|
+
return join(CACHE_DIR, `manifest.${safeDomain}.json`);
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Resolve the tenant domain from stored credentials
|
|
28
|
+
*/
|
|
29
|
+
const resolveDomain = () => {
|
|
30
|
+
// Check default-domain file
|
|
31
|
+
const defaultDomainFile = join(CACHE_DIR, 'default-domain');
|
|
32
|
+
try {
|
|
33
|
+
if (existsSync(defaultDomainFile)) {
|
|
34
|
+
const domain = readFileSync(defaultDomainFile, 'utf-8').trim();
|
|
35
|
+
if (domain)
|
|
36
|
+
return domain;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
catch { /* ignore */ }
|
|
40
|
+
// Check for any credential file
|
|
41
|
+
try {
|
|
42
|
+
const files = readdirSync(CACHE_DIR);
|
|
43
|
+
const credFile = files.find((f) => f.startsWith('credentials.'));
|
|
44
|
+
if (credFile) {
|
|
45
|
+
return credFile.replace('credentials.', '');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch { /* ignore */ }
|
|
49
|
+
return null;
|
|
50
|
+
};
|
|
15
51
|
/**
|
|
16
|
-
* Get the manifest
|
|
52
|
+
* Get the manifest -- from cache if fresh, otherwise from the tenant domain
|
|
17
53
|
*/
|
|
18
|
-
export const getManifest = async (
|
|
54
|
+
export const getManifest = async (_apiClient, forceRefresh = false) => {
|
|
55
|
+
const domain = resolveDomain();
|
|
56
|
+
if (!domain) {
|
|
57
|
+
throw new Error('Not authenticated. Run "sign auth login --domain <domain>" first.');
|
|
58
|
+
}
|
|
19
59
|
if (!forceRefresh) {
|
|
20
|
-
const cached = await loadCachedManifest();
|
|
60
|
+
const cached = await loadCachedManifest(domain);
|
|
21
61
|
if (cached)
|
|
22
62
|
return cached;
|
|
23
63
|
}
|
|
24
|
-
const manifest = await fetchManifest(
|
|
25
|
-
await cacheManifest(manifest);
|
|
64
|
+
const manifest = await fetchManifest(domain);
|
|
65
|
+
await cacheManifest(domain, manifest);
|
|
26
66
|
return manifest;
|
|
27
67
|
};
|
|
28
68
|
/**
|
|
29
69
|
* Load manifest from local cache if it exists and is fresh
|
|
30
70
|
*/
|
|
31
|
-
const loadCachedManifest = async () => {
|
|
71
|
+
const loadCachedManifest = async (domain) => {
|
|
72
|
+
const cacheFile = getCacheFile(domain);
|
|
32
73
|
try {
|
|
33
|
-
if (!existsSync(
|
|
74
|
+
if (!existsSync(cacheFile))
|
|
34
75
|
return null;
|
|
35
|
-
const raw = await readFile(
|
|
76
|
+
const raw = await readFile(cacheFile, 'utf-8');
|
|
36
77
|
const cached = JSON.parse(raw);
|
|
37
78
|
const age = Date.now() - new Date(cached.fetchedAt).getTime();
|
|
38
79
|
if (age > CACHE_TTL_MS)
|
|
@@ -44,47 +85,48 @@ const loadCachedManifest = async () => {
|
|
|
44
85
|
}
|
|
45
86
|
};
|
|
46
87
|
/**
|
|
47
|
-
* Fetch manifest from the
|
|
48
|
-
* Uses the
|
|
88
|
+
* Fetch manifest from the tenant domain.
|
|
89
|
+
* Uses plain HTTPS -- the manifest endpoint is on the tenant's CloudFront.
|
|
49
90
|
*/
|
|
50
|
-
const fetchManifest = async (
|
|
51
|
-
|
|
52
|
-
// It's unauthenticated, so we fetch it with plain HTTP.
|
|
53
|
-
const bootstrapUrl = process.env.SIGN_BOOTSTRAP_URL || 'https://oo2wp0ax27.execute-api.us-east-1.amazonaws.com/dev/tenant/bootstrap-dev';
|
|
54
|
-
const baseUrl = bootstrapUrl.replace(/\/tenant\/bootstrap.*$/, '');
|
|
55
|
-
const manifestUrl = `${baseUrl}/tenant/manifest`;
|
|
91
|
+
const fetchManifest = async (domain) => {
|
|
92
|
+
const manifestUrl = `https://${domain}/tenant/manifest`;
|
|
56
93
|
const response = await fetch(manifestUrl);
|
|
57
94
|
if (!response.ok) {
|
|
58
|
-
throw new Error(`Failed to fetch manifest: ${response.status} ${response.statusText}`);
|
|
95
|
+
throw new Error(`Failed to fetch manifest from ${domain}: ${response.status} ${response.statusText}`);
|
|
59
96
|
}
|
|
60
97
|
return await response.json();
|
|
61
98
|
};
|
|
62
99
|
/**
|
|
63
|
-
* Cache manifest to local filesystem
|
|
100
|
+
* Cache manifest to local filesystem (per-domain)
|
|
64
101
|
*/
|
|
65
|
-
const cacheManifest = async (manifest) => {
|
|
102
|
+
const cacheManifest = async (domain, manifest) => {
|
|
66
103
|
try {
|
|
67
104
|
if (!existsSync(CACHE_DIR)) {
|
|
68
105
|
await mkdir(CACHE_DIR, { recursive: true });
|
|
69
106
|
}
|
|
70
107
|
const cached = {
|
|
71
108
|
fetchedAt: new Date().toISOString(),
|
|
109
|
+
domain,
|
|
72
110
|
manifest
|
|
73
111
|
};
|
|
74
|
-
await writeFile(
|
|
112
|
+
await writeFile(getCacheFile(domain), JSON.stringify(cached, null, 2));
|
|
75
113
|
}
|
|
76
114
|
catch {
|
|
77
115
|
// Cache write failure is non-fatal
|
|
78
116
|
}
|
|
79
117
|
};
|
|
80
118
|
/**
|
|
81
|
-
* Clear the cached manifest
|
|
119
|
+
* Clear the cached manifest for the current domain
|
|
82
120
|
*/
|
|
83
121
|
export const clearManifestCache = async () => {
|
|
122
|
+
const domain = resolveDomain();
|
|
123
|
+
if (!domain)
|
|
124
|
+
return;
|
|
84
125
|
try {
|
|
85
|
-
|
|
126
|
+
const cacheFile = getCacheFile(domain);
|
|
127
|
+
if (existsSync(cacheFile)) {
|
|
86
128
|
const { unlink } = await import('node:fs/promises');
|
|
87
|
-
await unlink(
|
|
129
|
+
await unlink(cacheFile);
|
|
88
130
|
}
|
|
89
131
|
}
|
|
90
132
|
catch {
|
package/dist/lib/sign-api.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { SigV4ApiClient } from '@hyperdrive.bot/cli-auth';
|
|
|
11
11
|
export declare class SignApiClient extends SigV4ApiClient {
|
|
12
12
|
constructor(domain?: string);
|
|
13
13
|
/**
|
|
14
|
-
* GET request to the primary API (
|
|
14
|
+
* GET request to the primary API (sign gateway)
|
|
15
15
|
*/
|
|
16
16
|
get<T = unknown>(path: string): Promise<T>;
|
|
17
17
|
/**
|
package/dist/lib/sign-api.js
CHANGED
package/oclif.manifest.json
CHANGED