@idapt/cli 1.9.0 → 1.11.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/dist/bin.cjs +41882 -1809
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +744 -19
- package/dist/bin.js.map +1 -1
- package/dist/chunk-3CWHRMLZ.js +45770 -0
- package/dist/chunk-3CWHRMLZ.js.map +1 -0
- package/dist/index.cjs +39932 -593
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +99 -12
- package/dist/index.d.ts +99 -12
- package/dist/index.js +1 -1
- package/package.json +2 -1
- package/dist/chunk-TYSHO65D.js +0 -6431
- package/dist/chunk-TYSHO65D.js.map +0 -1
package/dist/bin.js
CHANGED
|
@@ -1,15 +1,687 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { createFetchTransport, execute, autoMode, commandsForResource, findCommand, renderHelp, listResources, renderInstructions, quoteToken } from './chunk-
|
|
3
|
-
import { readFileSync, mkdirSync, writeFileSync, chmodSync, rmSync } from 'fs';
|
|
2
|
+
import { createFetchTransport, execute, autoMode, commandsForResource, findCommand, renderHelp, listResources, renderInstructions, quoteToken, commandCatalog, executeCommand } from './chunk-3CWHRMLZ.js';
|
|
3
|
+
import { readFileSync, mkdirSync, writeFileSync, chmodSync, rmSync, readdirSync, existsSync } from 'fs';
|
|
4
|
+
import { dirname, resolve, join, relative, sep } from 'path';
|
|
4
5
|
import { spawn } from 'child_process';
|
|
5
|
-
import { dirname, join } from 'path';
|
|
6
6
|
import { homedir } from 'os';
|
|
7
7
|
import { createServer } from 'http';
|
|
8
8
|
import { randomBytes, createHash } from 'crypto';
|
|
9
9
|
|
|
10
10
|
// src/version.ts
|
|
11
|
-
var VERSION = "1.
|
|
11
|
+
var VERSION = "1.11.0";
|
|
12
12
|
var USER_AGENT = `idapt-cli/${VERSION}`;
|
|
13
|
+
var MAX_BUNDLE_FILES = 500;
|
|
14
|
+
var MAX_BUNDLE_FILE_BYTES = 25 * 1024 * 1024;
|
|
15
|
+
var MAX_BUNDLE_TOTAL_BYTES = 100 * 1024 * 1024;
|
|
16
|
+
function parseDeployArgs(args) {
|
|
17
|
+
let dir;
|
|
18
|
+
let app;
|
|
19
|
+
let workspace;
|
|
20
|
+
let name;
|
|
21
|
+
for (let i = 0; i < args.length; i++) {
|
|
22
|
+
const a = args[i];
|
|
23
|
+
if (a === "--app") app = args[++i];
|
|
24
|
+
else if (a.startsWith("--app=")) app = a.slice("--app=".length);
|
|
25
|
+
else if (a === "--workspace" || a === "--workspace-id")
|
|
26
|
+
workspace = args[++i];
|
|
27
|
+
else if (a.startsWith("--workspace="))
|
|
28
|
+
workspace = a.slice("--workspace=".length);
|
|
29
|
+
else if (a.startsWith("--workspace-id="))
|
|
30
|
+
workspace = a.slice("--workspace-id=".length);
|
|
31
|
+
else if (a === "--name") name = args[++i];
|
|
32
|
+
else if (a.startsWith("--name=")) name = a.slice("--name=".length);
|
|
33
|
+
else if (!a.startsWith("-") && dir === void 0) dir = a;
|
|
34
|
+
}
|
|
35
|
+
return { dir: dir ?? "", app, workspace, name };
|
|
36
|
+
}
|
|
37
|
+
function buildDeployPayload(files) {
|
|
38
|
+
if (files.length === 0) {
|
|
39
|
+
return { ok: false, error: "no files found to deploy" };
|
|
40
|
+
}
|
|
41
|
+
if (files.length > MAX_BUNDLE_FILES) {
|
|
42
|
+
return {
|
|
43
|
+
ok: false,
|
|
44
|
+
error: `too many files: ${files.length} (max ${MAX_BUNDLE_FILES})`
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
const out = [];
|
|
48
|
+
let totalBytes = 0;
|
|
49
|
+
for (const f of files) {
|
|
50
|
+
const size = f.bytes.byteLength;
|
|
51
|
+
if (size > MAX_BUNDLE_FILE_BYTES) {
|
|
52
|
+
return {
|
|
53
|
+
ok: false,
|
|
54
|
+
error: `file too large: ${f.path} (${size} bytes, max ${MAX_BUNDLE_FILE_BYTES})`
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
totalBytes += size;
|
|
58
|
+
if (totalBytes > MAX_BUNDLE_TOTAL_BYTES) {
|
|
59
|
+
return {
|
|
60
|
+
ok: false,
|
|
61
|
+
error: `bundle too large: exceeds ${MAX_BUNDLE_TOTAL_BYTES} bytes total`
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
out.push({
|
|
65
|
+
path: f.path,
|
|
66
|
+
content_b64: Buffer.from(f.bytes).toString("base64")
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
return { ok: true, files: out };
|
|
70
|
+
}
|
|
71
|
+
function rowId(data) {
|
|
72
|
+
if (typeof data !== "object" || data === null) return void 0;
|
|
73
|
+
const r = data;
|
|
74
|
+
const id = r.id ?? r.resource_id ?? r.resourceId;
|
|
75
|
+
return typeof id === "string" ? id : void 0;
|
|
76
|
+
}
|
|
77
|
+
function rowUrl(data) {
|
|
78
|
+
if (typeof data !== "object" || data === null) return void 0;
|
|
79
|
+
const r = data;
|
|
80
|
+
const url = r.url ?? r.origin;
|
|
81
|
+
return typeof url === "string" ? url : void 0;
|
|
82
|
+
}
|
|
83
|
+
async function resolveExistingApp(exec, app, workspace) {
|
|
84
|
+
const got = await exec("browser-app get", { app });
|
|
85
|
+
if (got.ok) {
|
|
86
|
+
const id = rowId(got.data);
|
|
87
|
+
if (id) return { appId: id, url: rowUrl(got.data) };
|
|
88
|
+
}
|
|
89
|
+
const listed = await exec(
|
|
90
|
+
"browser-app list",
|
|
91
|
+
workspace ? { workspace_id: workspace } : {}
|
|
92
|
+
);
|
|
93
|
+
if (listed.ok && Array.isArray(listed.data)) {
|
|
94
|
+
const match = listed.data.find((row) => {
|
|
95
|
+
const r = row;
|
|
96
|
+
return r?.name === app || rowId(r) === app;
|
|
97
|
+
});
|
|
98
|
+
const id = match ? rowId(match) : void 0;
|
|
99
|
+
if (id) return { appId: id, url: rowUrl(match) };
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
async function deployBundle(opts, files, exec) {
|
|
104
|
+
const built = buildDeployPayload(files);
|
|
105
|
+
if (!built.ok) return { ok: false, error: built.error };
|
|
106
|
+
let appId;
|
|
107
|
+
let url;
|
|
108
|
+
let created = false;
|
|
109
|
+
if (opts.app) {
|
|
110
|
+
const existing = await resolveExistingApp(exec, opts.app, opts.workspace);
|
|
111
|
+
if (existing) {
|
|
112
|
+
appId = existing.appId;
|
|
113
|
+
url = existing.url;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
if (!appId) {
|
|
117
|
+
const createArgs = {
|
|
118
|
+
name: opts.name ?? opts.app ?? defaultAppName(opts.dir)
|
|
119
|
+
};
|
|
120
|
+
if (opts.workspace) createArgs.workspace_id = opts.workspace;
|
|
121
|
+
const createdRes = await exec("browser-app create", createArgs);
|
|
122
|
+
if (!createdRes.ok) {
|
|
123
|
+
return {
|
|
124
|
+
ok: false,
|
|
125
|
+
error: createdRes.error ?? "failed to create app"
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
appId = rowId(createdRes.data);
|
|
129
|
+
url = rowUrl(createdRes.data) ?? url;
|
|
130
|
+
created = true;
|
|
131
|
+
if (!appId) {
|
|
132
|
+
return { ok: false, error: "create succeeded but returned no app id" };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
const deployed = await exec("browser-app deploy", {
|
|
136
|
+
app: appId,
|
|
137
|
+
files: built.files
|
|
138
|
+
});
|
|
139
|
+
if (!deployed.ok) {
|
|
140
|
+
return {
|
|
141
|
+
ok: false,
|
|
142
|
+
appId,
|
|
143
|
+
url,
|
|
144
|
+
created,
|
|
145
|
+
error: deployed.error ?? "deploy failed"
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
const data = deployed.data;
|
|
149
|
+
const written = typeof data?.written === "number" ? data.written : built.files.length;
|
|
150
|
+
const deploymentId = typeof data?.id === "string" ? data.id : void 0;
|
|
151
|
+
return { ok: true, appId, deploymentId, url, created, written };
|
|
152
|
+
}
|
|
153
|
+
function defaultAppName(dir) {
|
|
154
|
+
const norm = resolve(dir).split(sep).filter(Boolean);
|
|
155
|
+
const base = norm.at(-1);
|
|
156
|
+
if ((base === "dist" || base === "build") && norm.length >= 2) {
|
|
157
|
+
return norm.at(-2) ?? "My App";
|
|
158
|
+
}
|
|
159
|
+
return base ?? "My App";
|
|
160
|
+
}
|
|
161
|
+
function collectDir(dir) {
|
|
162
|
+
const root = resolve(dir);
|
|
163
|
+
const out = [];
|
|
164
|
+
const walk = (abs) => {
|
|
165
|
+
for (const entry of readdirSync(abs, { withFileTypes: true })) {
|
|
166
|
+
const child = join(abs, entry.name);
|
|
167
|
+
if (entry.isDirectory()) {
|
|
168
|
+
walk(child);
|
|
169
|
+
} else if (entry.isFile()) {
|
|
170
|
+
const rel = relative(root, child).split(sep).join("/");
|
|
171
|
+
out.push({ path: rel, bytes: readFileSync(child) });
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
walk(root);
|
|
176
|
+
return out;
|
|
177
|
+
}
|
|
178
|
+
async function runAppDeploy(ctx, args, exec, io = { collect: collectDir }) {
|
|
179
|
+
const opts = parseDeployArgs(args);
|
|
180
|
+
if (!opts.dir) {
|
|
181
|
+
ctx.out(
|
|
182
|
+
"idapt app deploy: missing <dir> \u2014 pass the built bundle, e.g. `idapt app deploy ./dist`."
|
|
183
|
+
);
|
|
184
|
+
return 1;
|
|
185
|
+
}
|
|
186
|
+
let files;
|
|
187
|
+
try {
|
|
188
|
+
files = io.collect(opts.dir);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
ctx.out(
|
|
191
|
+
`idapt app deploy: cannot read "${opts.dir}": ${err.message}`
|
|
192
|
+
);
|
|
193
|
+
return 1;
|
|
194
|
+
}
|
|
195
|
+
const result = await deployBundle(opts, files, exec);
|
|
196
|
+
if (!result.ok) {
|
|
197
|
+
ctx.out(`idapt app deploy: ${result.error}`);
|
|
198
|
+
return 1;
|
|
199
|
+
}
|
|
200
|
+
ctx.print(
|
|
201
|
+
[
|
|
202
|
+
`Deployed ${result.written} file${result.written === 1 ? "" : "s"} to ${result.appId}${result.created ? " (new app)" : ""} \u2014 now live${result.deploymentId ? ` (deployment ${result.deploymentId})` : ""}.`,
|
|
203
|
+
result.url ? `Open: ${result.url}` : ""
|
|
204
|
+
].filter(Boolean).join("\n")
|
|
205
|
+
);
|
|
206
|
+
return 0;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ../../shared/browser-app-sdk-version.ts
|
|
210
|
+
var BROWSER_APP_SDK_DEP_RANGE = "^0.1.0";
|
|
211
|
+
|
|
212
|
+
// src/app/templates.ts
|
|
213
|
+
function normalize(opts) {
|
|
214
|
+
return {
|
|
215
|
+
name: (opts.name || "My App").trim() || "My App",
|
|
216
|
+
icon: (opts.icon || "\u{1F4E6}").trim() || "\u{1F4E6}",
|
|
217
|
+
description: opts.description?.trim() || void 0
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
function idaptManifest(n) {
|
|
221
|
+
const manifest = {
|
|
222
|
+
entrypoint: "dist/index.html",
|
|
223
|
+
name: n.name,
|
|
224
|
+
icon: n.icon,
|
|
225
|
+
version: "0.1.0",
|
|
226
|
+
permissions: []
|
|
227
|
+
};
|
|
228
|
+
if (n.description) manifest.description = n.description;
|
|
229
|
+
return manifest;
|
|
230
|
+
}
|
|
231
|
+
var VITEST_CONFIG = `import { defineConfig } from "vitest/config";
|
|
232
|
+
|
|
233
|
+
// Unit tests run in Node \u2014 the mock Idapt SDK needs no DOM. Add
|
|
234
|
+
// \`environment: "jsdom"\` + @testing-library if you render components in tests.
|
|
235
|
+
export default defineConfig({
|
|
236
|
+
test: {
|
|
237
|
+
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
|
238
|
+
},
|
|
239
|
+
});
|
|
240
|
+
`;
|
|
241
|
+
var EXAMPLE_UNIT_TEST = `/**
|
|
242
|
+
* Example unit test \u2014 runs WITHOUT a live Idapt origin via the mock harness.
|
|
243
|
+
*
|
|
244
|
+
* \`createMockIdapt()\` returns a fake Idapt client whose \`data\` is an in-memory
|
|
245
|
+
* KV and whose \`app\` reads from a fixture map, so app logic built on
|
|
246
|
+
* \`client.data\` / \`client.app\` is unit-testable offline (the real \`connect()\`
|
|
247
|
+
* throws when run off an Idapt app subdomain).
|
|
248
|
+
*/
|
|
249
|
+
import { createMockIdapt } from "@idapt/browser-app-sdk/testing";
|
|
250
|
+
import { describe, expect, it } from "vitest";
|
|
251
|
+
|
|
252
|
+
describe("idapt mock harness", () => {
|
|
253
|
+
it("round-trips per-user state through the in-memory data KV", async () => {
|
|
254
|
+
const idapt = createMockIdapt();
|
|
255
|
+
await idapt.data.setJSON("prefs.json", { theme: "dark" });
|
|
256
|
+
expect(await idapt.data.getJSON("prefs.json")).toEqual({ theme: "dark" });
|
|
257
|
+
expect(await idapt.data.get("missing.json")).toBeNull();
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
it("reads the app's own bundle from the fixture map", async () => {
|
|
261
|
+
const idapt = createMockIdapt({
|
|
262
|
+
files: { "idapt.json": JSON.stringify({ name: "Demo" }) },
|
|
263
|
+
});
|
|
264
|
+
const names = (await idapt.app.list()).map((f) => f.name);
|
|
265
|
+
expect(names).toContain("idapt.json");
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
`;
|
|
269
|
+
var PLAYWRIGHT_CONFIG = `import { defineConfig } from "@playwright/test";
|
|
270
|
+
|
|
271
|
+
// Point PREVIEW_URL at a deployment's preview (or a local \`vite preview\`) and run
|
|
272
|
+
// \`npm run test:e2e\`. The specs assert against the accessibility tree, so they
|
|
273
|
+
// read the app exactly as assistive tech and \`browser-app snapshot\` do.
|
|
274
|
+
export default defineConfig({
|
|
275
|
+
testDir: "./e2e",
|
|
276
|
+
use: { baseURL: process.env.PREVIEW_URL ?? "http://localhost:4173" },
|
|
277
|
+
});
|
|
278
|
+
`;
|
|
279
|
+
var E2E_SPEC = `import { expect, test } from "@playwright/test";
|
|
280
|
+
|
|
281
|
+
// Accessibility-first assertions: querying by ROLE (not a brittle CSS selector)
|
|
282
|
+
// is robust AND proves the app exposes a semantic tree \u2014 the same tree the agent
|
|
283
|
+
// sees via \`browser-app snapshot\`.
|
|
284
|
+
test("renders an accessible top-level heading", async ({ page }) => {
|
|
285
|
+
await page.goto("/");
|
|
286
|
+
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
|
287
|
+
});
|
|
288
|
+
`;
|
|
289
|
+
var IDAPT_CI_YML = `version: 1
|
|
290
|
+
jobs:
|
|
291
|
+
test:
|
|
292
|
+
executor: cloud
|
|
293
|
+
steps:
|
|
294
|
+
- npm install
|
|
295
|
+
- npm test
|
|
296
|
+
build:
|
|
297
|
+
executor: cloud
|
|
298
|
+
needs: [test]
|
|
299
|
+
steps:
|
|
300
|
+
- npm install
|
|
301
|
+
- npm run build
|
|
302
|
+
output:
|
|
303
|
+
paths:
|
|
304
|
+
- dist/**
|
|
305
|
+
deploy:
|
|
306
|
+
target: browser-app
|
|
307
|
+
app: __APP_NAME__
|
|
308
|
+
artifacts: build
|
|
309
|
+
`;
|
|
310
|
+
var ESLINT_A11Y_CONFIG = `import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
311
|
+
|
|
312
|
+
// Accessibility-first: the SAME semantic tree powers assistive tech, the e2e
|
|
313
|
+
// specs, and the agent's \`browser-app snapshot\`. jsx-a11y flags missing labels,
|
|
314
|
+
// alt text, and invalid roles so inaccessible markup fails lint, not review.
|
|
315
|
+
export default [
|
|
316
|
+
{
|
|
317
|
+
files: ["src/**/*.{ts,tsx}"],
|
|
318
|
+
plugins: { "jsx-a11y": jsxA11y },
|
|
319
|
+
rules: jsxA11y.flatConfigs.recommended.rules,
|
|
320
|
+
},
|
|
321
|
+
];
|
|
322
|
+
`;
|
|
323
|
+
var REACT_INDEX_HTML = `<!doctype html>
|
|
324
|
+
<html lang="en">
|
|
325
|
+
<head>
|
|
326
|
+
<meta charset="utf-8" />
|
|
327
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
328
|
+
<title>__APP_NAME__</title>
|
|
329
|
+
</head>
|
|
330
|
+
<body>
|
|
331
|
+
<div id="root"></div>
|
|
332
|
+
<!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency).
|
|
333
|
+
The SDK auto-connects via the app-key cookie; no script tag needed. -->
|
|
334
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
335
|
+
</body>
|
|
336
|
+
</html>
|
|
337
|
+
`;
|
|
338
|
+
var REACT_PKG = "react";
|
|
339
|
+
var REACT_DOM_CLIENT_PKG = "react-dom/client";
|
|
340
|
+
var REACT_MAIN_TSX = `import { StrictMode } from "${REACT_PKG}";
|
|
341
|
+
import { createRoot } from "${REACT_DOM_CLIENT_PKG}";
|
|
342
|
+
import { App } from "./App";
|
|
343
|
+
|
|
344
|
+
createRoot(document.getElementById("root")!).render(
|
|
345
|
+
<StrictMode>
|
|
346
|
+
<App />
|
|
347
|
+
</StrictMode>,
|
|
348
|
+
);
|
|
349
|
+
`;
|
|
350
|
+
var REACT_APP_TSX = `import { connect } from "@idapt/browser-app-sdk";
|
|
351
|
+
import { useEffect, useState } from "${REACT_PKG}";
|
|
352
|
+
|
|
353
|
+
export function App() {
|
|
354
|
+
const [status, setStatus] = useState("Connecting\u2026");
|
|
355
|
+
|
|
356
|
+
useEffect(() => {
|
|
357
|
+
// On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
|
|
358
|
+
connect()
|
|
359
|
+
.then(() => setStatus("Connected."))
|
|
360
|
+
.catch(() => setStatus("Could not connect to idapt."));
|
|
361
|
+
}, []);
|
|
362
|
+
|
|
363
|
+
return (
|
|
364
|
+
<main style={{ maxWidth: "36rem", margin: "0 auto", padding: "2rem 1.25rem" }}>
|
|
365
|
+
<h1>__APP_NAME__</h1>
|
|
366
|
+
<p>{status}</p>
|
|
367
|
+
</main>
|
|
368
|
+
);
|
|
369
|
+
}
|
|
370
|
+
`;
|
|
371
|
+
var REACT_VITE_CONFIG = `import react from "@vitejs/plugin-react";
|
|
372
|
+
import { defineConfig } from "vite";
|
|
373
|
+
|
|
374
|
+
// Build to dist/ (the served bundle). Relative base so assets resolve on the
|
|
375
|
+
// app subdomain regardless of mount path.
|
|
376
|
+
export default defineConfig({
|
|
377
|
+
base: "./",
|
|
378
|
+
plugins: [react()],
|
|
379
|
+
build: { outDir: "dist" },
|
|
380
|
+
});
|
|
381
|
+
`;
|
|
382
|
+
var REACT_TSCONFIG = `{
|
|
383
|
+
"compilerOptions": {
|
|
384
|
+
"target": "ES2020",
|
|
385
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
386
|
+
"module": "ESNext",
|
|
387
|
+
"moduleResolution": "Bundler",
|
|
388
|
+
"jsx": "react-jsx",
|
|
389
|
+
"strict": true,
|
|
390
|
+
"skipLibCheck": true,
|
|
391
|
+
"noEmit": true
|
|
392
|
+
},
|
|
393
|
+
"include": ["src"]
|
|
394
|
+
}
|
|
395
|
+
`;
|
|
396
|
+
function getReactScaffold(opts) {
|
|
397
|
+
const n = normalize(opts);
|
|
398
|
+
const pkg = {
|
|
399
|
+
name: "idapt-react-app",
|
|
400
|
+
private: true,
|
|
401
|
+
version: "0.1.0",
|
|
402
|
+
type: "module",
|
|
403
|
+
scripts: {
|
|
404
|
+
build: "vite build",
|
|
405
|
+
dev: "vite",
|
|
406
|
+
test: "vitest run",
|
|
407
|
+
"test:e2e": "playwright test",
|
|
408
|
+
lint: "eslint ."
|
|
409
|
+
},
|
|
410
|
+
dependencies: {
|
|
411
|
+
"@idapt/browser-app-sdk": BROWSER_APP_SDK_DEP_RANGE,
|
|
412
|
+
react: "^18.3.1",
|
|
413
|
+
"react-dom": "^18.3.1"
|
|
414
|
+
},
|
|
415
|
+
devDependencies: {
|
|
416
|
+
"@playwright/test": "^1.48.0",
|
|
417
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
418
|
+
eslint: "^9.13.0",
|
|
419
|
+
"eslint-plugin-jsx-a11y": "^6.10.0",
|
|
420
|
+
typescript: "^5.5.4",
|
|
421
|
+
vite: "^5.4.0",
|
|
422
|
+
vitest: "^2.1.0"
|
|
423
|
+
},
|
|
424
|
+
idapt: idaptManifest(n)
|
|
425
|
+
};
|
|
426
|
+
return [
|
|
427
|
+
{ path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
|
|
428
|
+
` },
|
|
429
|
+
{
|
|
430
|
+
path: "index.html",
|
|
431
|
+
content: REACT_INDEX_HTML.replaceAll("__APP_NAME__", n.name)
|
|
432
|
+
},
|
|
433
|
+
{ path: "vite.config.ts", content: REACT_VITE_CONFIG },
|
|
434
|
+
{ path: "vitest.config.ts", content: VITEST_CONFIG },
|
|
435
|
+
{ path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
|
|
436
|
+
{ path: "eslint.config.js", content: ESLINT_A11Y_CONFIG },
|
|
437
|
+
{
|
|
438
|
+
path: "idapt-ci.yml",
|
|
439
|
+
content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
|
|
440
|
+
},
|
|
441
|
+
{ path: "tsconfig.json", content: REACT_TSCONFIG },
|
|
442
|
+
{ path: "src/main.tsx", content: REACT_MAIN_TSX },
|
|
443
|
+
{
|
|
444
|
+
path: "src/App.tsx",
|
|
445
|
+
content: REACT_APP_TSX.replaceAll("__APP_NAME__", n.name)
|
|
446
|
+
},
|
|
447
|
+
{ path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
|
|
448
|
+
{ path: "e2e/app.spec.ts", content: E2E_SPEC }
|
|
449
|
+
];
|
|
450
|
+
}
|
|
451
|
+
var VANILLA_INDEX_HTML = `<!doctype html>
|
|
452
|
+
<html lang="en">
|
|
453
|
+
<head>
|
|
454
|
+
<meta charset="utf-8" />
|
|
455
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
456
|
+
<title>__APP_NAME__</title>
|
|
457
|
+
<link rel="stylesheet" href="main.css" />
|
|
458
|
+
</head>
|
|
459
|
+
<body>
|
|
460
|
+
<main class="app">
|
|
461
|
+
<h1>__APP_ICON__ __APP_NAME__</h1>
|
|
462
|
+
<p id="status">Connecting\u2026</p>
|
|
463
|
+
</main>
|
|
464
|
+
<!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency). -->
|
|
465
|
+
<script type="module" src="main.js"></script>
|
|
466
|
+
</body>
|
|
467
|
+
</html>
|
|
468
|
+
`;
|
|
469
|
+
var VANILLA_MAIN_TS = `// __APP_NAME__ \u2014 a buildable idapt browser-app (esbuild).
|
|
470
|
+
import { connect } from "@idapt/browser-app-sdk";
|
|
471
|
+
|
|
472
|
+
const statusEl = document.getElementById("status");
|
|
473
|
+
function setStatus(text: string) {
|
|
474
|
+
if (statusEl) statusEl.textContent = text;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
|
|
478
|
+
connect()
|
|
479
|
+
.then(() => setStatus("Connected."))
|
|
480
|
+
.catch(() => setStatus("Could not connect to idapt."));
|
|
481
|
+
|
|
482
|
+
export {};
|
|
483
|
+
`;
|
|
484
|
+
var VANILLA_MAIN_CSS = `body {
|
|
485
|
+
margin: 0;
|
|
486
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
487
|
+
background: #0b0b0f;
|
|
488
|
+
color: #f4f4f5;
|
|
489
|
+
}
|
|
490
|
+
.app {
|
|
491
|
+
max-width: 36rem;
|
|
492
|
+
margin: 0 auto;
|
|
493
|
+
padding: 2rem 1.25rem;
|
|
494
|
+
}
|
|
495
|
+
`;
|
|
496
|
+
var VANILLA_BUILD_SH = `set -e
|
|
497
|
+
mkdir -p dist
|
|
498
|
+
node_modules/.bin/esbuild src/main.ts --bundle --format=esm --outfile=dist/main.js
|
|
499
|
+
cp index.html dist/index.html
|
|
500
|
+
cp src/main.css dist/main.css
|
|
501
|
+
`;
|
|
502
|
+
function getVanillaScaffold(opts) {
|
|
503
|
+
const n = normalize(opts);
|
|
504
|
+
const pkg = {
|
|
505
|
+
name: "idapt-vanilla-app",
|
|
506
|
+
private: true,
|
|
507
|
+
version: "0.1.0",
|
|
508
|
+
type: "module",
|
|
509
|
+
scripts: {
|
|
510
|
+
// The build step runs build.sh so it can bundle + copy static assets.
|
|
511
|
+
build: "sh build.sh",
|
|
512
|
+
test: "vitest run",
|
|
513
|
+
"test:e2e": "playwright test"
|
|
514
|
+
},
|
|
515
|
+
dependencies: {
|
|
516
|
+
"@idapt/browser-app-sdk": BROWSER_APP_SDK_DEP_RANGE
|
|
517
|
+
},
|
|
518
|
+
devDependencies: {
|
|
519
|
+
"@playwright/test": "^1.48.0",
|
|
520
|
+
esbuild: "^0.23.0",
|
|
521
|
+
typescript: "^5.5.4",
|
|
522
|
+
vitest: "^2.1.0"
|
|
523
|
+
},
|
|
524
|
+
idapt: idaptManifest(n)
|
|
525
|
+
};
|
|
526
|
+
return [
|
|
527
|
+
{ path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
|
|
528
|
+
` },
|
|
529
|
+
{ path: "build.sh", content: VANILLA_BUILD_SH },
|
|
530
|
+
{ path: "vitest.config.ts", content: VITEST_CONFIG },
|
|
531
|
+
{ path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
|
|
532
|
+
{
|
|
533
|
+
path: "idapt-ci.yml",
|
|
534
|
+
content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
|
|
535
|
+
},
|
|
536
|
+
{
|
|
537
|
+
path: "index.html",
|
|
538
|
+
content: VANILLA_INDEX_HTML.replaceAll("__APP_NAME__", n.name).replaceAll(
|
|
539
|
+
"__APP_ICON__",
|
|
540
|
+
n.icon
|
|
541
|
+
)
|
|
542
|
+
},
|
|
543
|
+
{
|
|
544
|
+
path: "src/main.ts",
|
|
545
|
+
content: VANILLA_MAIN_TS.replaceAll("__APP_NAME__", n.name)
|
|
546
|
+
},
|
|
547
|
+
{ path: "src/main.css", content: VANILLA_MAIN_CSS },
|
|
548
|
+
{ path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
|
|
549
|
+
{ path: "e2e/app.spec.ts", content: E2E_SPEC }
|
|
550
|
+
];
|
|
551
|
+
}
|
|
552
|
+
function getScaffoldFiles(template, opts) {
|
|
553
|
+
return template === "react" ? getReactScaffold(opts) : getVanillaScaffold(opts);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/app/init.ts
|
|
557
|
+
function parseInitArgs(args) {
|
|
558
|
+
let name;
|
|
559
|
+
let template = "react";
|
|
560
|
+
let dir = ".";
|
|
561
|
+
let force = false;
|
|
562
|
+
for (let i = 0; i < args.length; i++) {
|
|
563
|
+
const a = args[i];
|
|
564
|
+
if (a === "--template" || a === "-t") {
|
|
565
|
+
template = normTemplate(args[++i]);
|
|
566
|
+
} else if (a.startsWith("--template=")) {
|
|
567
|
+
template = normTemplate(a.slice("--template=".length));
|
|
568
|
+
} else if (a === "--dir" || a === "-d") {
|
|
569
|
+
dir = args[++i] ?? ".";
|
|
570
|
+
} else if (a.startsWith("--dir=")) {
|
|
571
|
+
dir = a.slice("--dir=".length) || ".";
|
|
572
|
+
} else if (a === "--force" || a === "-f") {
|
|
573
|
+
force = true;
|
|
574
|
+
} else if (!a.startsWith("-") && name === void 0) {
|
|
575
|
+
name = a;
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
return { name, template, dir, force };
|
|
579
|
+
}
|
|
580
|
+
function normTemplate(v) {
|
|
581
|
+
return v === "vanilla" ? "vanilla" : "react";
|
|
582
|
+
}
|
|
583
|
+
function planInit(opts, io) {
|
|
584
|
+
if (opts.template !== "react" && opts.template !== "vanilla") {
|
|
585
|
+
return {
|
|
586
|
+
ok: false,
|
|
587
|
+
written: [],
|
|
588
|
+
error: `unknown template "${opts.template}" (use react or vanilla)`
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
if (!opts.force && io.dirIsNonEmpty(opts.dir)) {
|
|
592
|
+
return {
|
|
593
|
+
ok: false,
|
|
594
|
+
written: [],
|
|
595
|
+
error: `target "${opts.dir}" is not empty \u2014 pass --force to scaffold into it anyway`
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
const files = getScaffoldFiles(opts.template, {
|
|
599
|
+
name: opts.name ?? "My App"
|
|
600
|
+
});
|
|
601
|
+
const written = [];
|
|
602
|
+
for (const f of files) {
|
|
603
|
+
io.writeFile(join(opts.dir, f.path), f.content);
|
|
604
|
+
written.push(f.path);
|
|
605
|
+
}
|
|
606
|
+
return { ok: true, written };
|
|
607
|
+
}
|
|
608
|
+
function runAppInit(ctx, args) {
|
|
609
|
+
const opts = parseInitArgs(args);
|
|
610
|
+
const io = {
|
|
611
|
+
dirIsNonEmpty: (p) => {
|
|
612
|
+
const abs = resolve(p);
|
|
613
|
+
if (!existsSync(abs)) return false;
|
|
614
|
+
try {
|
|
615
|
+
return readdirSync(abs).length > 0;
|
|
616
|
+
} catch {
|
|
617
|
+
return false;
|
|
618
|
+
}
|
|
619
|
+
},
|
|
620
|
+
writeFile: (p, content) => {
|
|
621
|
+
const abs = resolve(p);
|
|
622
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
623
|
+
writeFileSync(abs, content, "utf-8");
|
|
624
|
+
}
|
|
625
|
+
};
|
|
626
|
+
const result = planInit(opts, io);
|
|
627
|
+
if (!result.ok) {
|
|
628
|
+
ctx.out(`idapt app init: ${result.error}`);
|
|
629
|
+
return 1;
|
|
630
|
+
}
|
|
631
|
+
ctx.print(
|
|
632
|
+
`Scaffolded a ${opts.template} browser-app in ${opts.dir} (${result.written.length} files).`
|
|
633
|
+
);
|
|
634
|
+
ctx.out(
|
|
635
|
+
[
|
|
636
|
+
"",
|
|
637
|
+
"Next steps:",
|
|
638
|
+
` cd ${opts.dir}`,
|
|
639
|
+
" npm install && npm run build",
|
|
640
|
+
" idapt app deploy ./dist"
|
|
641
|
+
].join("\n")
|
|
642
|
+
);
|
|
643
|
+
return 0;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// src/app/index.ts
|
|
647
|
+
var APP_USAGE = `idapt app \u2014 local browser-app developer loop
|
|
648
|
+
|
|
649
|
+
Usage:
|
|
650
|
+
idapt app init [name] [--template react|vanilla] [--dir .] [--force]
|
|
651
|
+
idapt app deploy <dir> [--app <id|name>] [--workspace <id>] [--name <name>]
|
|
652
|
+
|
|
653
|
+
init Scaffold an npm browser-app project locally (then npm install && npm run build)
|
|
654
|
+
deploy Upload a locally-built dist/ to a browser-app (creates one if --app is absent/unknown)`;
|
|
655
|
+
async function runApp(ctx, args) {
|
|
656
|
+
const sub = args[0];
|
|
657
|
+
const rest = args.slice(1);
|
|
658
|
+
if (sub === "init") {
|
|
659
|
+
return runAppInit(ctx, rest);
|
|
660
|
+
}
|
|
661
|
+
if (sub === "deploy") {
|
|
662
|
+
if (!ctx.token) {
|
|
663
|
+
ctx.out(
|
|
664
|
+
"idapt app deploy: not signed in. Run `idapt login` (or set IDAPT_API_KEY)."
|
|
665
|
+
);
|
|
666
|
+
return 1;
|
|
667
|
+
}
|
|
668
|
+
const transport = createFetchTransport({
|
|
669
|
+
baseUrl: ctx.baseUrl,
|
|
670
|
+
token: ctx.token,
|
|
671
|
+
userAgent: USER_AGENT
|
|
672
|
+
});
|
|
673
|
+
const exec = (commandName, cmdArgs) => executeCommand(commandName, cmdArgs, { transport, mode: "json" });
|
|
674
|
+
return runAppDeploy(ctx, rest, exec);
|
|
675
|
+
}
|
|
676
|
+
if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
|
|
677
|
+
ctx.print(APP_USAGE);
|
|
678
|
+
return sub ? 0 : 1;
|
|
679
|
+
}
|
|
680
|
+
ctx.out(`idapt app: unknown subcommand "${sub}".
|
|
681
|
+
|
|
682
|
+
${APP_USAGE}`);
|
|
683
|
+
return 1;
|
|
684
|
+
}
|
|
13
685
|
function canOpenBrowser(env, isTty) {
|
|
14
686
|
if (!isTty) return false;
|
|
15
687
|
if (env.SSH_CONNECTION || env.SSH_TTY) return false;
|
|
@@ -157,7 +829,7 @@ function loginAuthCode(io) {
|
|
|
157
829
|
const base = trimRight(io.baseUrl);
|
|
158
830
|
const { verifier, challenge } = generatePkce();
|
|
159
831
|
const state = randomState();
|
|
160
|
-
return new Promise((
|
|
832
|
+
return new Promise((resolve3, reject) => {
|
|
161
833
|
let settled = false;
|
|
162
834
|
const finish = (fn) => {
|
|
163
835
|
if (settled) return;
|
|
@@ -216,7 +888,7 @@ function loginAuthCode(io) {
|
|
|
216
888
|
},
|
|
217
889
|
io.userAgent
|
|
218
890
|
).then(
|
|
219
|
-
(tok) => finish(() =>
|
|
891
|
+
(tok) => finish(() => resolve3(tok)),
|
|
220
892
|
(e) => finish(() => reject(e))
|
|
221
893
|
);
|
|
222
894
|
});
|
|
@@ -256,8 +928,8 @@ If it doesn't open, visit:
|
|
|
256
928
|
});
|
|
257
929
|
});
|
|
258
930
|
}
|
|
259
|
-
var sleep = (ms, signal) => new Promise((
|
|
260
|
-
const t = setTimeout(
|
|
931
|
+
var sleep = (ms, signal) => new Promise((resolve3, reject) => {
|
|
932
|
+
const t = setTimeout(resolve3, ms);
|
|
261
933
|
signal?.addEventListener("abort", () => {
|
|
262
934
|
clearTimeout(t);
|
|
263
935
|
reject(new AuthError("sign-in cancelled"));
|
|
@@ -516,6 +1188,10 @@ async function runStatus(ctx) {
|
|
|
516
1188
|
}
|
|
517
1189
|
|
|
518
1190
|
// src/doc.ts
|
|
1191
|
+
function dumpJson(resource) {
|
|
1192
|
+
const entries = resource ? commandCatalog.filter((c) => c.resource === resource) : commandCatalog;
|
|
1193
|
+
return JSON.stringify(entries, null, 2);
|
|
1194
|
+
}
|
|
519
1195
|
function helpIndex() {
|
|
520
1196
|
const resources = listResources().sort().join(", ");
|
|
521
1197
|
return [
|
|
@@ -525,6 +1201,7 @@ function helpIndex() {
|
|
|
525
1201
|
" idapt <resource> <verb> [args] Run a command (e.g. idapt drive list)",
|
|
526
1202
|
" idapt help [resource [verb]] Show a command's contract",
|
|
527
1203
|
" idapt instructions [resource] Show a resource's playbook",
|
|
1204
|
+
" idapt app init | deploy Scaffold + ship a local browser-app",
|
|
528
1205
|
" idapt login | logout | whoami Authenticate",
|
|
529
1206
|
" idapt upgrade Update to the latest version",
|
|
530
1207
|
"",
|
|
@@ -552,9 +1229,11 @@ function resourceHelp(resource, cmds) {
|
|
|
552
1229
|
].join("\n");
|
|
553
1230
|
}
|
|
554
1231
|
function renderHelpDoc(tokens) {
|
|
555
|
-
|
|
556
|
-
if (tokens.
|
|
557
|
-
|
|
1232
|
+
const positional = tokens.filter((t) => !t.startsWith("-"));
|
|
1233
|
+
if (tokens.includes("--dump-json")) return dumpJson(positional[0]);
|
|
1234
|
+
if (positional.length === 0) return helpIndex();
|
|
1235
|
+
if (positional.length === 1) {
|
|
1236
|
+
const resource = positional[0];
|
|
558
1237
|
const cmds2 = commandsForResource(resource);
|
|
559
1238
|
if (cmds2.length === 0) {
|
|
560
1239
|
return `Unknown resource "${resource}".
|
|
@@ -563,14 +1242,14 @@ ${helpIndex()}`;
|
|
|
563
1242
|
}
|
|
564
1243
|
return resourceHelp(resource, cmds2);
|
|
565
1244
|
}
|
|
566
|
-
const command = `${
|
|
1245
|
+
const command = `${positional[0]} ${positional[1]}`;
|
|
567
1246
|
const spec = findCommand(command);
|
|
568
1247
|
if (spec) return renderHelp(spec);
|
|
569
|
-
const cmds = commandsForResource(
|
|
1248
|
+
const cmds = commandsForResource(positional[0]);
|
|
570
1249
|
if (cmds.length > 0) {
|
|
571
1250
|
return `Unknown command "${command}".
|
|
572
1251
|
|
|
573
|
-
${resourceHelp(
|
|
1252
|
+
${resourceHelp(positional[0], cmds)}`;
|
|
574
1253
|
}
|
|
575
1254
|
return `Unknown command "${command}".
|
|
576
1255
|
|
|
@@ -703,12 +1382,12 @@ function detectManager(scriptPath, env, channel) {
|
|
|
703
1382
|
};
|
|
704
1383
|
}
|
|
705
1384
|
function spawnInherit(cmd, args) {
|
|
706
|
-
return new Promise((
|
|
1385
|
+
return new Promise((resolve3) => {
|
|
707
1386
|
const child = process.platform === "win32" ? spawn(process.env.ComSpec || "cmd.exe", ["/c", cmd, ...args], {
|
|
708
1387
|
stdio: "inherit"
|
|
709
1388
|
}) : spawn(cmd, args, { stdio: "inherit" });
|
|
710
|
-
child.on("error", () =>
|
|
711
|
-
child.on("close", (code) =>
|
|
1389
|
+
child.on("error", () => resolve3(127));
|
|
1390
|
+
child.on("close", (code) => resolve3(code ?? 0));
|
|
712
1391
|
});
|
|
713
1392
|
}
|
|
714
1393
|
async function runUpgrade(io, opts) {
|
|
@@ -798,6 +1477,31 @@ function extractValueFlag(argv, name) {
|
|
|
798
1477
|
function buildCommandFromArgv(argv) {
|
|
799
1478
|
return ["idapt", ...argv.map(quoteToken)].join(" ");
|
|
800
1479
|
}
|
|
1480
|
+
function resolveJsonArgSource(argv, io) {
|
|
1481
|
+
const resolveVal = (val) => {
|
|
1482
|
+
if (val === "-") return io.readStdin();
|
|
1483
|
+
if (val.startsWith("@")) return io.readFile(val.slice(1));
|
|
1484
|
+
return null;
|
|
1485
|
+
};
|
|
1486
|
+
const out = [];
|
|
1487
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1488
|
+
const tok = argv[i];
|
|
1489
|
+
if (tok.startsWith("--json=")) {
|
|
1490
|
+
const resolved = resolveVal(tok.slice("--json=".length));
|
|
1491
|
+
out.push(resolved === null ? tok : `--json=${resolved}`);
|
|
1492
|
+
continue;
|
|
1493
|
+
}
|
|
1494
|
+
if (tok === "--json" && i + 1 < argv.length) {
|
|
1495
|
+
out.push(tok);
|
|
1496
|
+
const resolved = resolveVal(argv[i + 1]);
|
|
1497
|
+
out.push(resolved === null ? argv[i + 1] : resolved);
|
|
1498
|
+
i++;
|
|
1499
|
+
continue;
|
|
1500
|
+
}
|
|
1501
|
+
out.push(tok);
|
|
1502
|
+
}
|
|
1503
|
+
return out;
|
|
1504
|
+
}
|
|
801
1505
|
function isDocCommand(rest) {
|
|
802
1506
|
if (rest[0] === "help" || rest[0] === "instructions") return true;
|
|
803
1507
|
return rest.some(
|
|
@@ -865,6 +1569,23 @@ async function run(io) {
|
|
|
865
1569
|
);
|
|
866
1570
|
return 1;
|
|
867
1571
|
}
|
|
1572
|
+
if (cmd === "app") {
|
|
1573
|
+
const appCred = await resolveCredential({
|
|
1574
|
+
apiKeyFlag,
|
|
1575
|
+
env: io.env,
|
|
1576
|
+
baseUrl,
|
|
1577
|
+
userAgent: USER_AGENT
|
|
1578
|
+
}).catch(() => null);
|
|
1579
|
+
return runApp(
|
|
1580
|
+
{
|
|
1581
|
+
out: io.stderr,
|
|
1582
|
+
print: io.stdout,
|
|
1583
|
+
baseUrl,
|
|
1584
|
+
token: appCred?.token
|
|
1585
|
+
},
|
|
1586
|
+
rest.slice(1)
|
|
1587
|
+
);
|
|
1588
|
+
}
|
|
868
1589
|
const doc = isDocCommand(rest);
|
|
869
1590
|
let token;
|
|
870
1591
|
const resolved = await resolveCredential({
|
|
@@ -887,7 +1608,11 @@ async function run(io) {
|
|
|
887
1608
|
token,
|
|
888
1609
|
userAgent: USER_AGENT
|
|
889
1610
|
});
|
|
890
|
-
const
|
|
1611
|
+
const restWithJson = resolveJsonArgSource(rest, {
|
|
1612
|
+
readFile: (p) => readFileSync(p, "utf-8"),
|
|
1613
|
+
readStdin: () => readFileSync(0, "utf-8")
|
|
1614
|
+
});
|
|
1615
|
+
const result = await execute(buildCommandFromArgv(restWithJson), {
|
|
891
1616
|
transport,
|
|
892
1617
|
mode: autoMode(io.isTty, requested)
|
|
893
1618
|
});
|
|
@@ -923,6 +1648,6 @@ if (typeof process !== "undefined" && isCliEntrypoint(process.argv[1])) {
|
|
|
923
1648
|
void main();
|
|
924
1649
|
}
|
|
925
1650
|
|
|
926
|
-
export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, run };
|
|
1651
|
+
export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, resolveJsonArgSource, run };
|
|
927
1652
|
//# sourceMappingURL=bin.js.map
|
|
928
1653
|
//# sourceMappingURL=bin.js.map
|