@ductape/cli 0.2.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/API_PARITY.md +58 -0
- package/CHANGELOG.md +36 -0
- package/README.md +89 -0
- package/dist/commands/apps-import.d.ts +9 -0
- package/dist/commands/apps-import.js +29 -0
- package/dist/commands/apps.d.ts +10 -0
- package/dist/commands/apps.js +68 -0
- package/dist/commands/cloud.d.ts +7 -0
- package/dist/commands/cloud.js +42 -0
- package/dist/commands/completion.d.ts +1 -0
- package/dist/commands/completion.js +113 -0
- package/dist/commands/db.d.ts +5 -0
- package/dist/commands/db.js +55 -0
- package/dist/commands/generate.d.ts +14 -0
- package/dist/commands/generate.js +52 -0
- package/dist/commands/graph.d.ts +4 -0
- package/dist/commands/graph.js +44 -0
- package/dist/commands/init.d.ts +7 -0
- package/dist/commands/init.js +28 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +42 -0
- package/dist/commands/link.d.ts +7 -0
- package/dist/commands/link.js +61 -0
- package/dist/commands/login.d.ts +9 -0
- package/dist/commands/login.js +103 -0
- package/dist/commands/logout.d.ts +1 -0
- package/dist/commands/logout.js +6 -0
- package/dist/commands/platform.d.ts +7 -0
- package/dist/commands/platform.js +99 -0
- package/dist/commands/products.d.ts +14 -0
- package/dist/commands/products.js +74 -0
- package/dist/commands/profiles.d.ts +2 -0
- package/dist/commands/profiles.js +18 -0
- package/dist/commands/resources.d.ts +7 -0
- package/dist/commands/resources.js +61 -0
- package/dist/commands/secrets.d.ts +6 -0
- package/dist/commands/secrets.js +38 -0
- package/dist/commands/unlink.d.ts +3 -0
- package/dist/commands/unlink.js +14 -0
- package/dist/commands/whoami.d.ts +1 -0
- package/dist/commands/whoami.js +22 -0
- package/dist/commands/workspaces.d.ts +16 -0
- package/dist/commands/workspaces.js +72 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +283 -0
- package/dist/lib/api-client.d.ts +18 -0
- package/dist/lib/api-client.js +65 -0
- package/dist/lib/app-import.d.ts +15 -0
- package/dist/lib/app-import.js +63 -0
- package/dist/lib/config.d.ts +63 -0
- package/dist/lib/config.js +155 -0
- package/dist/lib/context-store.d.ts +17 -0
- package/dist/lib/context-store.js +43 -0
- package/dist/lib/db-methods.d.ts +3 -0
- package/dist/lib/db-methods.js +55 -0
- package/dist/lib/encryption.d.ts +1 -0
- package/dist/lib/encryption.js +11 -0
- package/dist/lib/forms/enums.d.ts +11 -0
- package/dist/lib/forms/enums.js +42 -0
- package/dist/lib/forms/index.d.ts +2 -0
- package/dist/lib/forms/index.js +2 -0
- package/dist/lib/forms/prompt.d.ts +11 -0
- package/dist/lib/forms/prompt.js +203 -0
- package/dist/lib/forms/schemas.d.ts +9 -0
- package/dist/lib/forms/schemas.js +253 -0
- package/dist/lib/forms/types.d.ts +18 -0
- package/dist/lib/forms/types.js +1 -0
- package/dist/lib/http.d.ts +10 -0
- package/dist/lib/http.js +51 -0
- package/dist/lib/integrations.d.ts +20 -0
- package/dist/lib/integrations.js +20 -0
- package/dist/lib/interactive-opts.d.ts +6 -0
- package/dist/lib/interactive-opts.js +6 -0
- package/dist/lib/output.d.ts +3 -0
- package/dist/lib/output.js +22 -0
- package/dist/lib/platform-api.d.ts +19 -0
- package/dist/lib/platform-api.js +123 -0
- package/dist/lib/proxy/context.d.ts +16 -0
- package/dist/lib/proxy/context.js +73 -0
- package/dist/lib/proxy/db-proxy.d.ts +13 -0
- package/dist/lib/proxy/db-proxy.js +28 -0
- package/dist/lib/proxy/graph-proxy.d.ts +19 -0
- package/dist/lib/proxy/graph-proxy.js +43 -0
- package/dist/lib/proxy/sdk-proxy.d.ts +14 -0
- package/dist/lib/proxy/sdk-proxy.js +22 -0
- package/dist/lib/read-body.d.ts +11 -0
- package/dist/lib/read-body.js +38 -0
- package/dist/lib/resources.d.ts +12 -0
- package/dist/lib/resources.js +147 -0
- package/dist/lib/snippets.d.ts +6 -0
- package/dist/lib/snippets.js +146 -0
- package/dist/lib/templates.d.ts +3 -0
- package/dist/lib/templates.js +74 -0
- package/dist/lib/workspace-context.d.ts +12 -0
- package/dist/lib/workspace-context.js +25 -0
- package/dist/lib/workspaces.d.ts +45 -0
- package/dist/lib/workspaces.js +219 -0
- package/package.json +37 -0
- package/src/commands/apps-import.ts +43 -0
- package/src/commands/apps.ts +92 -0
- package/src/commands/cloud.ts +50 -0
- package/src/commands/completion.ts +119 -0
- package/src/commands/db.ts +63 -0
- package/src/commands/generate.ts +74 -0
- package/src/commands/graph.ts +51 -0
- package/src/commands/init.ts +39 -0
- package/src/commands/install.ts +48 -0
- package/src/commands/link.ts +84 -0
- package/src/commands/login.ts +145 -0
- package/src/commands/logout.ts +7 -0
- package/src/commands/platform.ts +123 -0
- package/src/commands/products.ts +104 -0
- package/src/commands/profiles.ts +23 -0
- package/src/commands/resources.ts +85 -0
- package/src/commands/secrets.ts +46 -0
- package/src/commands/unlink.ts +15 -0
- package/src/commands/whoami.ts +27 -0
- package/src/commands/workspaces.ts +98 -0
- package/src/index.ts +381 -0
- package/src/lib/api-client.ts +88 -0
- package/src/lib/app-import.ts +82 -0
- package/src/lib/config.ts +225 -0
- package/src/lib/context-store.ts +77 -0
- package/src/lib/db-methods.ts +56 -0
- package/src/lib/encryption.ts +12 -0
- package/src/lib/forms/enums.ts +52 -0
- package/src/lib/forms/index.ts +8 -0
- package/src/lib/forms/prompt.ts +209 -0
- package/src/lib/forms/schemas.ts +288 -0
- package/src/lib/forms/types.ts +27 -0
- package/src/lib/http.ts +70 -0
- package/src/lib/integrations.ts +47 -0
- package/src/lib/interactive-opts.ts +13 -0
- package/src/lib/output.ts +24 -0
- package/src/lib/platform-api.ts +178 -0
- package/src/lib/proxy/context.ts +94 -0
- package/src/lib/proxy/db-proxy.ts +40 -0
- package/src/lib/proxy/graph-proxy.ts +64 -0
- package/src/lib/proxy/sdk-proxy.ts +58 -0
- package/src/lib/read-body.ts +57 -0
- package/src/lib/resources.ts +156 -0
- package/src/lib/snippets.ts +165 -0
- package/src/lib/templates.ts +78 -0
- package/src/lib/workspace-context.ts +43 -0
- package/src/lib/workspaces.ts +283 -0
- package/templates/dotnet/Program.cs +2 -0
- package/templates/go/main.go +8 -0
- package/templates/python/ductape_example.py +16 -0
- package/templates/typescript/ductape.example.ts +18 -0
- package/test/encryption.test.ts +20 -0
- package/test/forms.test.ts +25 -0
- package/test/platform-api.test.ts +19 -0
- package/test/resources.test.ts +23 -0
- package/test/workspace-resolve.test.ts +29 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export declare function runWorkspacesList(opts: {
|
|
2
|
+
profile?: string;
|
|
3
|
+
json?: boolean;
|
|
4
|
+
refresh?: boolean;
|
|
5
|
+
}): Promise<void>;
|
|
6
|
+
export declare function runWorkspacesCurrent(json: boolean): void;
|
|
7
|
+
export declare function runWorkspacesUse(selector: string | undefined, opts: {
|
|
8
|
+
profile?: string;
|
|
9
|
+
dir?: string;
|
|
10
|
+
json?: boolean;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
/** Refresh workspace list from API and update login cache */
|
|
13
|
+
export declare function runWorkspacesRefresh(opts: {
|
|
14
|
+
profile?: string;
|
|
15
|
+
json?: boolean;
|
|
16
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { applyActiveWorkspace, fetchWorkspaces, getActiveWorkspace, printWorkspaceTable, promptWorkspaceSelection, selectAndApplyWorkspace, workspaceId, workspaceLabel, } from '../lib/workspaces.js';
|
|
2
|
+
import { printJson, success } from '../lib/output.js';
|
|
3
|
+
export async function runWorkspacesList(opts) {
|
|
4
|
+
const workspaces = await fetchWorkspaces(opts.profile);
|
|
5
|
+
const active = getActiveWorkspace();
|
|
6
|
+
if (opts.json) {
|
|
7
|
+
printJson({
|
|
8
|
+
active_workspace_id: active?.id ?? null,
|
|
9
|
+
active_workspace_name: active?.name ?? null,
|
|
10
|
+
workspaces,
|
|
11
|
+
}, true);
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (active) {
|
|
15
|
+
console.log(`Active workspace: ${active.name ?? active.id}`);
|
|
16
|
+
console.log(` ${active.id}\n`);
|
|
17
|
+
}
|
|
18
|
+
printWorkspaceTable(workspaces, active?.id);
|
|
19
|
+
}
|
|
20
|
+
export function runWorkspacesCurrent(json) {
|
|
21
|
+
const active = getActiveWorkspace();
|
|
22
|
+
if (!active) {
|
|
23
|
+
if (json) {
|
|
24
|
+
printJson({ active: null }, true);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
console.log('No active workspace. Run: ductape login or ductape workspaces use');
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (json) {
|
|
31
|
+
printJson({ active_workspace_id: active.id, active_workspace_name: active.name }, true);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
console.log(`Active workspace: ${active.name ?? active.id}`);
|
|
35
|
+
console.log(` ${active.id}`);
|
|
36
|
+
}
|
|
37
|
+
export async function runWorkspacesUse(selector, opts) {
|
|
38
|
+
const workspaces = await fetchWorkspaces(opts.profile);
|
|
39
|
+
let chosen;
|
|
40
|
+
if (!selector) {
|
|
41
|
+
chosen = await promptWorkspaceSelection(workspaces, {
|
|
42
|
+
message: 'Switch workspace:',
|
|
43
|
+
});
|
|
44
|
+
await applyActiveWorkspace(chosen, { syncProject: true, dir: opts.dir });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
chosen = await selectAndApplyWorkspace({
|
|
48
|
+
profile: opts.profile,
|
|
49
|
+
selector,
|
|
50
|
+
skipPrompt: true,
|
|
51
|
+
syncProject: true,
|
|
52
|
+
dir: opts.dir,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
const id = workspaceId(chosen);
|
|
56
|
+
const name = workspaceLabel(chosen);
|
|
57
|
+
if (opts.json) {
|
|
58
|
+
printJson({ active_workspace_id: id, active_workspace_name: name }, true);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
success(`Switched to workspace: ${name} (${id})`);
|
|
62
|
+
}
|
|
63
|
+
/** Refresh workspace list from API and update login cache */
|
|
64
|
+
export async function runWorkspacesRefresh(opts) {
|
|
65
|
+
const workspaces = await fetchWorkspaces(opts.profile);
|
|
66
|
+
if (opts.json) {
|
|
67
|
+
printJson({ workspaces, count: workspaces.length }, true);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
success(`Refreshed ${workspaces.length} workspace(s) from API`);
|
|
71
|
+
await runWorkspacesList({ profile: opts.profile, json: false });
|
|
72
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from 'commander';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { runLogin } from './commands/login.js';
|
|
7
|
+
import { runLogout } from './commands/logout.js';
|
|
8
|
+
import { runWhoami } from './commands/whoami.js';
|
|
9
|
+
import { runProfilesList, runProfilesUse } from './commands/profiles.js';
|
|
10
|
+
import { runLink } from './commands/link.js';
|
|
11
|
+
import { runUnlink } from './commands/unlink.js';
|
|
12
|
+
import { runInit } from './commands/init.js';
|
|
13
|
+
import { runInstall } from './commands/install.js';
|
|
14
|
+
import { runStart, runStop, runStatus } from './commands/platform.js';
|
|
15
|
+
import { runResourceCrud, runResourcesList } from './commands/resources.js';
|
|
16
|
+
import { runCloud } from './commands/cloud.js';
|
|
17
|
+
import { runDb, runDbContext } from './commands/db.js';
|
|
18
|
+
import { runGraph } from './commands/graph.js';
|
|
19
|
+
import { runSecrets } from './commands/secrets.js';
|
|
20
|
+
import { runWorkspacesCurrent, runWorkspacesList, runWorkspacesRefresh, runWorkspacesUse, } from './commands/workspaces.js';
|
|
21
|
+
import { runGeneratePayload, runGenerateSnippet } from './commands/generate.js';
|
|
22
|
+
import { runCompletion } from './commands/completion.js';
|
|
23
|
+
import { runProducts, runProductApps } from './commands/products.js';
|
|
24
|
+
import { runApps } from './commands/apps.js';
|
|
25
|
+
import { runAppsImport } from './commands/apps-import.js';
|
|
26
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const pkg = JSON.parse(readFileSync(path.join(__dirname, '../package.json'), 'utf8'));
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
29
|
+
function wrap(fn) {
|
|
30
|
+
return (...args) => {
|
|
31
|
+
Promise.resolve(fn(...args)).catch((err) => {
|
|
32
|
+
console.error(err instanceof Error ? err.message : err);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
const program = new Command();
|
|
38
|
+
program
|
|
39
|
+
.name('ductape')
|
|
40
|
+
.description('Ductape CLI — local platform, auth, project linking, and resource CRUD via Workbench-compatible encrypted proxy')
|
|
41
|
+
.version(pkg.version);
|
|
42
|
+
program
|
|
43
|
+
.command('login')
|
|
44
|
+
.description('Log in (email/password, OAuth token, or browser OAuth)')
|
|
45
|
+
.option('-e, --email <email>')
|
|
46
|
+
.option('-p, --password <password>')
|
|
47
|
+
.option('--token <token>', 'OAuth callback token from browser redirect URL')
|
|
48
|
+
.option('--browser [provider]', 'Open browser for Google OAuth (google|github)')
|
|
49
|
+
.option('--profile <name>', 'API profile', 'cloud')
|
|
50
|
+
.option('-w, --workspace <id>', 'Workspace index, name, or id (skips interactive picker)')
|
|
51
|
+
.option('--skip-workspace-select', 'Keep prior active workspace or default without prompting')
|
|
52
|
+
.action(wrap((opts) => runLogin({
|
|
53
|
+
email: opts.email,
|
|
54
|
+
password: opts.password,
|
|
55
|
+
profile: opts.profile,
|
|
56
|
+
token: opts.token,
|
|
57
|
+
browser: opts.browser === true ? 'google' : opts.browser,
|
|
58
|
+
workspace: opts.workspace,
|
|
59
|
+
skipWorkspaceSelect: Boolean(opts.skipWorkspaceSelect),
|
|
60
|
+
})));
|
|
61
|
+
program.command('logout').description('Clear local credentials').action(wrap(runLogout));
|
|
62
|
+
program
|
|
63
|
+
.command('whoami')
|
|
64
|
+
.description('Show login and linked project')
|
|
65
|
+
.option('--json', 'JSON output')
|
|
66
|
+
.action(wrap((opts) => runWhoami(Boolean(opts.json))));
|
|
67
|
+
program
|
|
68
|
+
.command('install')
|
|
69
|
+
.description('Check Docker/Node prerequisites and prepare platform .env')
|
|
70
|
+
.action(wrap(runInstall));
|
|
71
|
+
const profiles = program.command('profiles').description('Manage API profiles (cloud / local)');
|
|
72
|
+
profiles
|
|
73
|
+
.command('list')
|
|
74
|
+
.option('--json', 'JSON output')
|
|
75
|
+
.action(wrap((opts) => runProfilesList(Boolean(opts.json))));
|
|
76
|
+
profiles.command('use <name>').action(wrap((name) => runProfilesUse(name)));
|
|
77
|
+
const workspaces = program
|
|
78
|
+
.command('workspaces')
|
|
79
|
+
.description('List and switch active workspace (workspace_id is stored globally at login)');
|
|
80
|
+
workspaces
|
|
81
|
+
.command('list')
|
|
82
|
+
.description('List workspaces (marks active)')
|
|
83
|
+
.option('--profile <name>')
|
|
84
|
+
.option('--json', 'JSON output')
|
|
85
|
+
.action(wrap((opts) => runWorkspacesList({ profile: opts.profile, json: opts.json })));
|
|
86
|
+
workspaces
|
|
87
|
+
.command('current')
|
|
88
|
+
.description('Show active workspace')
|
|
89
|
+
.option('--json', 'JSON output')
|
|
90
|
+
.action(wrap((opts) => runWorkspacesCurrent(Boolean(opts.json))));
|
|
91
|
+
workspaces
|
|
92
|
+
.command('use [selector]')
|
|
93
|
+
.description('Switch active workspace (interactive picker). Updates linked .ductape/config.json if present')
|
|
94
|
+
.option('--profile <name>')
|
|
95
|
+
.option('-C, --dir <path>', 'Project directory to sync', process.cwd())
|
|
96
|
+
.option('--json', 'JSON output')
|
|
97
|
+
.action(wrap((selector, opts) => runWorkspacesUse(selector, opts)));
|
|
98
|
+
workspaces
|
|
99
|
+
.command('switch [selector]')
|
|
100
|
+
.description('Alias for workspaces use')
|
|
101
|
+
.option('--profile <name>')
|
|
102
|
+
.option('-C, --dir <path>', 'Project directory to sync', process.cwd())
|
|
103
|
+
.option('--json', 'JSON output')
|
|
104
|
+
.action(wrap((selector, opts) => runWorkspacesUse(selector, opts)));
|
|
105
|
+
workspaces
|
|
106
|
+
.command('refresh')
|
|
107
|
+
.description('Refresh workspace list from API')
|
|
108
|
+
.option('--profile <name>')
|
|
109
|
+
.option('--json', 'JSON output')
|
|
110
|
+
.action(wrap((opts) => runWorkspacesRefresh(opts)));
|
|
111
|
+
program
|
|
112
|
+
.command('link')
|
|
113
|
+
.option('-w, --workspace <id>')
|
|
114
|
+
.option('--product <tag>')
|
|
115
|
+
.option('--env <slug>')
|
|
116
|
+
.option('--profile <name>')
|
|
117
|
+
.option('-C, --dir <path>', 'Project directory', process.cwd())
|
|
118
|
+
.action(wrap((opts) => runLink({
|
|
119
|
+
workspace: opts.workspace,
|
|
120
|
+
product: opts.product,
|
|
121
|
+
env: opts.env,
|
|
122
|
+
profile: opts.profile,
|
|
123
|
+
dir: opts.dir,
|
|
124
|
+
})));
|
|
125
|
+
program
|
|
126
|
+
.command('unlink')
|
|
127
|
+
.option('-C, --dir <path>', 'Project directory', process.cwd())
|
|
128
|
+
.action(wrap((opts) => runUnlink({ dir: opts.dir })));
|
|
129
|
+
program
|
|
130
|
+
.command('init')
|
|
131
|
+
.option('--link', 'Run link after init')
|
|
132
|
+
.option('-w, --workspace <id>')
|
|
133
|
+
.option('--product <tag>')
|
|
134
|
+
.option('--env <slug>')
|
|
135
|
+
.option('--language <lang>', 'typescript|python|go|dotnet')
|
|
136
|
+
.action(wrap((opts) => runInit({
|
|
137
|
+
link: Boolean(opts.link),
|
|
138
|
+
workspace: opts.workspace,
|
|
139
|
+
product: opts.product,
|
|
140
|
+
env: opts.env,
|
|
141
|
+
language: opts.language,
|
|
142
|
+
})));
|
|
143
|
+
program
|
|
144
|
+
.command('start')
|
|
145
|
+
.description('Start platform via docker compose')
|
|
146
|
+
.option('--backend-only', 'Backend without workbench UI')
|
|
147
|
+
.option('--remote', 'Workbench UI only, built against cloud API (no local Nest/Mongo/Kafka)')
|
|
148
|
+
.option('--build', 'Build images before start')
|
|
149
|
+
.action(wrap((opts) => runStart({
|
|
150
|
+
backendOnly: Boolean(opts.backendOnly),
|
|
151
|
+
remote: Boolean(opts.remote),
|
|
152
|
+
build: Boolean(opts.build),
|
|
153
|
+
})));
|
|
154
|
+
program.command('stop').action(wrap(runStop));
|
|
155
|
+
program.command('status').option('--json', 'JSON output').action(wrap((opts) => runStatus(Boolean(opts.json))));
|
|
156
|
+
const products = program
|
|
157
|
+
.command('products')
|
|
158
|
+
.description('Workspace products CRUD (REST + sdk-proxy update)');
|
|
159
|
+
products
|
|
160
|
+
.command('<verb>')
|
|
161
|
+
.description('list | get | create | update | delete')
|
|
162
|
+
.option('--profile <name>')
|
|
163
|
+
.option('--status <status>', 'Filter for list: all, active, draft, …', 'all')
|
|
164
|
+
.option('--id <id>', 'Product _id (get, delete)')
|
|
165
|
+
.option('-t, --tag <tag>', 'Product tag (get, update)')
|
|
166
|
+
.option('-f, --file <path>', 'JSON body (create, update)')
|
|
167
|
+
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
168
|
+
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
169
|
+
.option('--json', 'JSON output')
|
|
170
|
+
.action(wrap((verb, opts) => runProducts(verb, opts, [])));
|
|
171
|
+
const productApps = products.command('apps').description('Apps connected to a product');
|
|
172
|
+
productApps
|
|
173
|
+
.command('list')
|
|
174
|
+
.option('--profile <name>')
|
|
175
|
+
.option('--product <id>', 'Product _id')
|
|
176
|
+
.option('--json', 'JSON output')
|
|
177
|
+
.action(wrap((opts) => runProductApps('list', opts, [])));
|
|
178
|
+
const apps = program.command('apps').description('Workspace integration apps CRUD (REST)');
|
|
179
|
+
apps
|
|
180
|
+
.command('<verb>')
|
|
181
|
+
.description('list | get | create | update | delete')
|
|
182
|
+
.option('--profile <name>')
|
|
183
|
+
.option('--status <status>', 'Filter for list', 'all')
|
|
184
|
+
.option('--id <id>', 'App _id')
|
|
185
|
+
.option('-t, --tag <tag>', 'App tag (get; update with --proxy)')
|
|
186
|
+
.option('--proxy', 'Update via sdk-proxy (use with --tag)')
|
|
187
|
+
.option('-f, --file <path>', 'JSON body')
|
|
188
|
+
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
189
|
+
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
190
|
+
.option('--json', 'JSON output')
|
|
191
|
+
.action(wrap((verb, opts) => runApps(verb, opts, [])));
|
|
192
|
+
apps
|
|
193
|
+
.command('import <file>')
|
|
194
|
+
.description('Import Postman v2.1 or OpenAPI 3.0 into a new or existing app')
|
|
195
|
+
.requiredOption('-t, --type <format>', 'postman | openapi (Postman Collection v2.1 or OpenAPI 3.0)')
|
|
196
|
+
.option('--profile <name>')
|
|
197
|
+
.option('--app-id <id>', 'Update existing app by _id')
|
|
198
|
+
.option('--app-tag <tag>', 'Update existing app by tag')
|
|
199
|
+
.option('--update', 'Replace/update if app already exists')
|
|
200
|
+
.option('--version <tag>', 'App version tag', '0.0.1')
|
|
201
|
+
.option('--json', 'JSON output')
|
|
202
|
+
.action(wrap((file, opts) => runAppsImport(file, {
|
|
203
|
+
profile: opts.profile,
|
|
204
|
+
type: opts.type,
|
|
205
|
+
appId: opts.appId,
|
|
206
|
+
appTag: opts.appTag,
|
|
207
|
+
update: Boolean(opts.update),
|
|
208
|
+
version: opts.version,
|
|
209
|
+
json: opts.json,
|
|
210
|
+
})));
|
|
211
|
+
const resources = program.command('resources').description('Component CRUD (sdk-proxy)');
|
|
212
|
+
resources.command('types').option('--json', 'JSON output').action(wrap((opts) => runResourcesList(Boolean(opts.json))));
|
|
213
|
+
resources
|
|
214
|
+
.command('<type> <verb>')
|
|
215
|
+
.option('-t, --tag <tag>')
|
|
216
|
+
.option('-f, --file <path>', 'JSON body (or use interactive prompts)')
|
|
217
|
+
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
218
|
+
.option('--no-interactive', 'Require -f JSON for create/update/connect')
|
|
219
|
+
.option('--json', 'JSON output')
|
|
220
|
+
.action(wrap((type, verb, opts) => runResourceCrud(type, verb, opts, [])));
|
|
221
|
+
const cloud = program.command('cloud').description('Cloud connections & resources');
|
|
222
|
+
cloud
|
|
223
|
+
.command('connections <verb> [id]')
|
|
224
|
+
.option('-f, --file <path>')
|
|
225
|
+
.option('--json', 'JSON output')
|
|
226
|
+
.action(wrap((verb, id, opts) => runCloud('connections', verb, { id, file: opts.file, json: opts.json }, [])));
|
|
227
|
+
cloud
|
|
228
|
+
.command('resources <verb>')
|
|
229
|
+
.option('-f, --file <path>')
|
|
230
|
+
.option('--json', 'JSON output')
|
|
231
|
+
.action(wrap((verb, opts) => runCloud('resources', verb, { file: opts.file, json: opts.json }, [])));
|
|
232
|
+
const db = program.command('db').description('Database runtime (db-proxy)');
|
|
233
|
+
db.command('context').option('--json', 'JSON output').action(wrap((opts) => runDbContext(Boolean(opts.json))));
|
|
234
|
+
db
|
|
235
|
+
.command('<verb>')
|
|
236
|
+
.option('-f, --file <path>')
|
|
237
|
+
.option('--json', 'JSON output')
|
|
238
|
+
.action(wrap((verb, opts) => runDb(verb, { file: opts.file, json: opts.json })));
|
|
239
|
+
const graph = program.command('graph').description('Graph runtime (graph-proxy)');
|
|
240
|
+
graph
|
|
241
|
+
.command('<verb>')
|
|
242
|
+
.option('-f, --file <path>')
|
|
243
|
+
.option('--json', 'JSON output')
|
|
244
|
+
.action(wrap((verb, opts) => runGraph(verb, { file: opts.file, json: opts.json })));
|
|
245
|
+
program
|
|
246
|
+
.command('secrets <verb>')
|
|
247
|
+
.description('Workspace secrets CRUD')
|
|
248
|
+
.option('-k, --key <key>')
|
|
249
|
+
.option('-f, --file <path>')
|
|
250
|
+
.option('-i, --interactive', 'Prompt for fields (default in TTY when -f omitted)')
|
|
251
|
+
.option('--no-interactive', 'Require -f JSON for create/update')
|
|
252
|
+
.option('--json', 'JSON output')
|
|
253
|
+
.action(wrap((verb, opts) => runSecrets(verb, opts)));
|
|
254
|
+
const generate = program.command('generate').description('Payload templates & code snippets');
|
|
255
|
+
generate
|
|
256
|
+
.command('payload <family> <method>')
|
|
257
|
+
.option('-f, --file <path>', 'targets JSON')
|
|
258
|
+
.option('--json', 'JSON output')
|
|
259
|
+
.action(wrap((family, method, opts) => runGeneratePayload({ family, method, file: opts.file, json: opts.json })));
|
|
260
|
+
generate
|
|
261
|
+
.command('snippet <family> <method>')
|
|
262
|
+
.requiredOption('-l, --language <lang>', 'typescript|python|go|dotnet')
|
|
263
|
+
.option('-f, --file <path>')
|
|
264
|
+
.option('-o, --out <path>', 'Write snippet to file')
|
|
265
|
+
.option('--json', 'JSON output')
|
|
266
|
+
.action(wrap((family, method, opts) => runGenerateSnippet({
|
|
267
|
+
family,
|
|
268
|
+
method,
|
|
269
|
+
language: opts.language,
|
|
270
|
+
file: opts.file,
|
|
271
|
+
json: opts.json,
|
|
272
|
+
out: opts.out,
|
|
273
|
+
})));
|
|
274
|
+
program
|
|
275
|
+
.command('completion <shell>')
|
|
276
|
+
.description('Print shell completions (bash|zsh)')
|
|
277
|
+
.action(wrap((shell) => {
|
|
278
|
+
if (shell !== 'bash' && shell !== 'zsh' && shell !== 'fish') {
|
|
279
|
+
throw new Error('Shell must be bash, zsh, or fish');
|
|
280
|
+
}
|
|
281
|
+
runCompletion(shell);
|
|
282
|
+
}));
|
|
283
|
+
program.parse();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Credentials } from './config.js';
|
|
2
|
+
export interface ApiEnvelope<T> {
|
|
3
|
+
status?: boolean;
|
|
4
|
+
data?: T;
|
|
5
|
+
message?: string;
|
|
6
|
+
}
|
|
7
|
+
export declare class ApiClient {
|
|
8
|
+
private readonly apiUrl;
|
|
9
|
+
private readonly token?;
|
|
10
|
+
constructor(apiUrl: string, token?: string | undefined);
|
|
11
|
+
static forProfile(profileName?: string, creds?: Credentials | null): ApiClient;
|
|
12
|
+
request<T>(method: string, path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
|
|
13
|
+
post<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
14
|
+
put<T>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
15
|
+
delete<T>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
16
|
+
get<T>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
17
|
+
getPath<T>(path: string, query?: Record<string, string>): Promise<T>;
|
|
18
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { getApiUrl } from './config.js';
|
|
2
|
+
export class ApiClient {
|
|
3
|
+
apiUrl;
|
|
4
|
+
token;
|
|
5
|
+
constructor(apiUrl, token) {
|
|
6
|
+
this.apiUrl = apiUrl;
|
|
7
|
+
this.token = token;
|
|
8
|
+
}
|
|
9
|
+
static forProfile(profileName, creds) {
|
|
10
|
+
return new ApiClient(getApiUrl(profileName), creds?.auth_token);
|
|
11
|
+
}
|
|
12
|
+
async request(method, path, body, extraHeaders) {
|
|
13
|
+
const url = `${this.apiUrl}${path.startsWith('/') ? path : `/${path}`}`;
|
|
14
|
+
const headers = {
|
|
15
|
+
'Content-Type': 'application/json',
|
|
16
|
+
...extraHeaders,
|
|
17
|
+
};
|
|
18
|
+
if (this.token) {
|
|
19
|
+
headers.Authorization = `Bearer ${this.token}`;
|
|
20
|
+
}
|
|
21
|
+
const res = await fetch(url, {
|
|
22
|
+
method,
|
|
23
|
+
headers,
|
|
24
|
+
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
25
|
+
});
|
|
26
|
+
const text = await res.text();
|
|
27
|
+
let parsed;
|
|
28
|
+
try {
|
|
29
|
+
parsed = text ? JSON.parse(text) : {};
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
throw new Error(`Invalid JSON from ${url}: ${text.slice(0, 200)}`);
|
|
33
|
+
}
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
const msg = parsed.message ??
|
|
36
|
+
parsed.error ??
|
|
37
|
+
`HTTP ${res.status}`;
|
|
38
|
+
throw new Error(msg);
|
|
39
|
+
}
|
|
40
|
+
if (typeof parsed.status === 'boolean' && !parsed.status) {
|
|
41
|
+
throw new Error(parsed.message ?? 'Request failed');
|
|
42
|
+
}
|
|
43
|
+
const data = parsed.data;
|
|
44
|
+
if (data && typeof data === 'object' && 'result' in data) {
|
|
45
|
+
return data.result;
|
|
46
|
+
}
|
|
47
|
+
return (data ?? parsed);
|
|
48
|
+
}
|
|
49
|
+
post(path, body, headers) {
|
|
50
|
+
return this.request('POST', path, body, headers);
|
|
51
|
+
}
|
|
52
|
+
put(path, body, headers) {
|
|
53
|
+
return this.request('PUT', path, body, headers);
|
|
54
|
+
}
|
|
55
|
+
delete(path, headers) {
|
|
56
|
+
return this.request('DELETE', path, undefined, headers);
|
|
57
|
+
}
|
|
58
|
+
get(path, headers) {
|
|
59
|
+
return this.request('GET', path, undefined, headers);
|
|
60
|
+
}
|
|
61
|
+
getPath(path, query) {
|
|
62
|
+
const qs = query ? `?${new URLSearchParams(query).toString()}` : '';
|
|
63
|
+
return this.get(`${path}${qs}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { WorkspaceContext } from './workspace-context.js';
|
|
2
|
+
/** Values accepted by POST /apps/v1/import `type` field */
|
|
3
|
+
export declare const IMPORT_DOC_TYPES: {
|
|
4
|
+
readonly postman: "PostmanV2.1";
|
|
5
|
+
readonly openapi: "OpenApiV3.0";
|
|
6
|
+
};
|
|
7
|
+
export type ImportFormat = keyof typeof IMPORT_DOC_TYPES;
|
|
8
|
+
export declare function parseImportFormat(value: string): ImportFormat;
|
|
9
|
+
export declare function importAppFromFile(ctx: WorkspaceContext, opts: {
|
|
10
|
+
filePath: string;
|
|
11
|
+
format: ImportFormat;
|
|
12
|
+
appId?: string;
|
|
13
|
+
update?: boolean;
|
|
14
|
+
version?: string;
|
|
15
|
+
}): Promise<unknown>;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/** Values accepted by POST /apps/v1/import `type` field */
|
|
4
|
+
export const IMPORT_DOC_TYPES = {
|
|
5
|
+
postman: 'PostmanV2.1',
|
|
6
|
+
openapi: 'OpenApiV3.0',
|
|
7
|
+
};
|
|
8
|
+
export function parseImportFormat(value) {
|
|
9
|
+
const v = value.toLowerCase().replace(/[_\s]/g, '');
|
|
10
|
+
if (v === 'postman' || v === 'postmanv21' || v === 'postman2.1' || v === 'postmanv2.1') {
|
|
11
|
+
return 'postman';
|
|
12
|
+
}
|
|
13
|
+
if (v === 'openapi' || v === 'openapiv30' || v === 'openapi3.0' || v === 'openapi3') {
|
|
14
|
+
return 'openapi';
|
|
15
|
+
}
|
|
16
|
+
throw new Error(`Unknown import type "${value}". Use: postman (Postman Collection v2.1) or openapi (OpenAPI 3.0)`);
|
|
17
|
+
}
|
|
18
|
+
function uploadFilename(filePath) {
|
|
19
|
+
const base = path.basename(filePath);
|
|
20
|
+
if (base.endsWith('.yaml') || base.endsWith('.yml'))
|
|
21
|
+
return base;
|
|
22
|
+
if (base.endsWith('.json'))
|
|
23
|
+
return base;
|
|
24
|
+
return `${base}.json`;
|
|
25
|
+
}
|
|
26
|
+
export async function importAppFromFile(ctx, opts) {
|
|
27
|
+
const abs = path.resolve(opts.filePath);
|
|
28
|
+
if (!fs.existsSync(abs))
|
|
29
|
+
throw new Error(`File not found: ${abs}`);
|
|
30
|
+
const buffer = fs.readFileSync(abs);
|
|
31
|
+
const filename = uploadFilename(abs);
|
|
32
|
+
const form = new FormData();
|
|
33
|
+
form.append('file', new Blob([buffer]), filename);
|
|
34
|
+
form.append('workspace_id', ctx.workspaceId);
|
|
35
|
+
form.append('public_key', ctx.credentials.public_key);
|
|
36
|
+
form.append('user_id', ctx.credentials.user_id);
|
|
37
|
+
form.append('type', IMPORT_DOC_TYPES[opts.format]);
|
|
38
|
+
form.append('version', opts.version ?? '0.0.1');
|
|
39
|
+
form.append('update', opts.update ? 'true' : 'false');
|
|
40
|
+
if (opts.appId)
|
|
41
|
+
form.append('app_id', opts.appId);
|
|
42
|
+
const url = `${ctx.apiUrl}/apps/v1/import`;
|
|
43
|
+
const res = await fetch(url, {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { Authorization: `Bearer ${ctx.credentials.auth_token}` },
|
|
46
|
+
body: form,
|
|
47
|
+
});
|
|
48
|
+
const text = await res.text();
|
|
49
|
+
let parsed;
|
|
50
|
+
try {
|
|
51
|
+
parsed = text ? JSON.parse(text) : {};
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
throw new Error(`Invalid JSON from import API: ${text.slice(0, 200)}`);
|
|
55
|
+
}
|
|
56
|
+
if (!res.ok) {
|
|
57
|
+
throw new Error(parsed.message ?? `Import failed (HTTP ${res.status})`);
|
|
58
|
+
}
|
|
59
|
+
if (parsed.status === false) {
|
|
60
|
+
throw new Error(parsed.message ?? 'Import failed');
|
|
61
|
+
}
|
|
62
|
+
return parsed.data ?? parsed;
|
|
63
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export declare const GLOBAL_DIR: string;
|
|
2
|
+
export declare const CREDENTIALS_PATH: string;
|
|
3
|
+
export declare const GLOBAL_CONFIG_PATH: string;
|
|
4
|
+
export declare const PROJECT_CONFIG_DIR = ".ductape";
|
|
5
|
+
export declare const PROJECT_CONFIG_PATH: string;
|
|
6
|
+
export interface Credentials {
|
|
7
|
+
user_id: string;
|
|
8
|
+
email: string;
|
|
9
|
+
auth_token: string;
|
|
10
|
+
public_key: string;
|
|
11
|
+
firstname?: string;
|
|
12
|
+
lastname?: string;
|
|
13
|
+
workspaces?: Array<{
|
|
14
|
+
workspace_id?: string;
|
|
15
|
+
workspace_name?: string;
|
|
16
|
+
_id?: string;
|
|
17
|
+
name?: string;
|
|
18
|
+
tag?: string;
|
|
19
|
+
default?: boolean;
|
|
20
|
+
}>;
|
|
21
|
+
}
|
|
22
|
+
export interface Profile {
|
|
23
|
+
api_url: string;
|
|
24
|
+
}
|
|
25
|
+
export interface GlobalConfig {
|
|
26
|
+
default_profile: string;
|
|
27
|
+
profiles: Record<string, Profile>;
|
|
28
|
+
/** Set at login or via `ductape workspaces use` */
|
|
29
|
+
active_workspace_id?: string;
|
|
30
|
+
active_workspace_name?: string;
|
|
31
|
+
active_workspace_tag?: string;
|
|
32
|
+
}
|
|
33
|
+
/** Linked project config — use workspace_tag (human-readable); workspace_id is resolved by the CLI. */
|
|
34
|
+
export interface ProjectConfig {
|
|
35
|
+
workspace_tag?: string;
|
|
36
|
+
/** Resolved automatically; do not edit unless debugging */
|
|
37
|
+
workspace_id?: string;
|
|
38
|
+
product_tag: string;
|
|
39
|
+
env_slug: string;
|
|
40
|
+
linked_profile?: string;
|
|
41
|
+
project_id?: string;
|
|
42
|
+
}
|
|
43
|
+
export interface ResolvedProjectConfig extends ProjectConfig {
|
|
44
|
+
workspace_id: string;
|
|
45
|
+
}
|
|
46
|
+
export declare function ensureGlobalDir(): void;
|
|
47
|
+
export declare function loadCredentials(): Credentials | null;
|
|
48
|
+
export declare function saveCredentials(creds: Credentials): void;
|
|
49
|
+
export declare function clearCredentials(): void;
|
|
50
|
+
export declare function getActiveWorkspaceId(): string | null;
|
|
51
|
+
export declare function loadGlobalConfig(): GlobalConfig;
|
|
52
|
+
export declare function saveGlobalConfig(config: GlobalConfig): void;
|
|
53
|
+
export declare function getActiveProfileName(project?: ProjectConfig | null): string;
|
|
54
|
+
export declare function getApiUrl(profileName?: string): string;
|
|
55
|
+
/** Resolve workspace_id from tag (cached workspaces), explicit id, or global active workspace. */
|
|
56
|
+
export declare function resolveWorkspaceId(cfg: ProjectConfig, creds?: Credentials | null): string | null;
|
|
57
|
+
export declare function findProjectConfig(startDir?: string): {
|
|
58
|
+
dir: string;
|
|
59
|
+
config: ProjectConfig;
|
|
60
|
+
} | null;
|
|
61
|
+
export declare function loadProjectConfig(): ResolvedProjectConfig;
|
|
62
|
+
export declare function saveProjectConfig(dir: string, config: ProjectConfig): void;
|
|
63
|
+
export declare function resolvePlatformDir(): string;
|