@idapt/cli 1.9.0 → 1.10.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 +36599 -1693
- package/dist/bin.cjs.map +1 -1
- package/dist/bin.js +741 -19
- package/dist/bin.js.map +1 -1
- package/dist/chunk-CX7FTE47.js +40606 -0
- package/dist/chunk-CX7FTE47.js.map +1 -0
- package/dist/index.cjs +34545 -370
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -11
- package/dist/index.d.ts +46 -11
- 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,684 @@
|
|
|
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-CX7FTE47.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.10.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
|
+
// src/app/templates.ts
|
|
210
|
+
function normalize(opts) {
|
|
211
|
+
return {
|
|
212
|
+
name: (opts.name || "My App").trim() || "My App",
|
|
213
|
+
icon: (opts.icon || "\u{1F4E6}").trim() || "\u{1F4E6}",
|
|
214
|
+
description: opts.description?.trim() || void 0
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function idaptManifest(n) {
|
|
218
|
+
const manifest = {
|
|
219
|
+
entrypoint: "dist/index.html",
|
|
220
|
+
name: n.name,
|
|
221
|
+
icon: n.icon,
|
|
222
|
+
version: "0.1.0",
|
|
223
|
+
permissions: []
|
|
224
|
+
};
|
|
225
|
+
if (n.description) manifest.description = n.description;
|
|
226
|
+
return manifest;
|
|
227
|
+
}
|
|
228
|
+
var VITEST_CONFIG = `import { defineConfig } from "vitest/config";
|
|
229
|
+
|
|
230
|
+
// Unit tests run in Node \u2014 the mock Idapt SDK needs no DOM. Add
|
|
231
|
+
// \`environment: "jsdom"\` + @testing-library if you render components in tests.
|
|
232
|
+
export default defineConfig({
|
|
233
|
+
test: {
|
|
234
|
+
include: ["src/**/*.test.ts", "src/**/*.test.tsx"],
|
|
235
|
+
},
|
|
236
|
+
});
|
|
237
|
+
`;
|
|
238
|
+
var EXAMPLE_UNIT_TEST = `/**
|
|
239
|
+
* Example unit test \u2014 runs WITHOUT a live Idapt origin via the mock harness.
|
|
240
|
+
*
|
|
241
|
+
* \`createMockIdapt()\` returns a fake Idapt client whose \`data\` is an in-memory
|
|
242
|
+
* KV and whose \`app\` reads from a fixture map, so app logic built on
|
|
243
|
+
* \`client.data\` / \`client.app\` is unit-testable offline (the real \`connect()\`
|
|
244
|
+
* throws when run off an Idapt app subdomain).
|
|
245
|
+
*/
|
|
246
|
+
import { createMockIdapt } from "@idapt/browser-app-sdk/testing";
|
|
247
|
+
import { describe, expect, it } from "vitest";
|
|
248
|
+
|
|
249
|
+
describe("idapt mock harness", () => {
|
|
250
|
+
it("round-trips per-user state through the in-memory data KV", async () => {
|
|
251
|
+
const idapt = createMockIdapt();
|
|
252
|
+
await idapt.data.setJSON("prefs.json", { theme: "dark" });
|
|
253
|
+
expect(await idapt.data.getJSON("prefs.json")).toEqual({ theme: "dark" });
|
|
254
|
+
expect(await idapt.data.get("missing.json")).toBeNull();
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("reads the app's own bundle from the fixture map", async () => {
|
|
258
|
+
const idapt = createMockIdapt({
|
|
259
|
+
files: { "idapt.json": JSON.stringify({ name: "Demo" }) },
|
|
260
|
+
});
|
|
261
|
+
const names = (await idapt.app.list()).map((f) => f.name);
|
|
262
|
+
expect(names).toContain("idapt.json");
|
|
263
|
+
});
|
|
264
|
+
});
|
|
265
|
+
`;
|
|
266
|
+
var PLAYWRIGHT_CONFIG = `import { defineConfig } from "@playwright/test";
|
|
267
|
+
|
|
268
|
+
// Point PREVIEW_URL at a deployment's preview (or a local \`vite preview\`) and run
|
|
269
|
+
// \`npm run test:e2e\`. The specs assert against the accessibility tree, so they
|
|
270
|
+
// read the app exactly as assistive tech and \`browser-app snapshot\` do.
|
|
271
|
+
export default defineConfig({
|
|
272
|
+
testDir: "./e2e",
|
|
273
|
+
use: { baseURL: process.env.PREVIEW_URL ?? "http://localhost:4173" },
|
|
274
|
+
});
|
|
275
|
+
`;
|
|
276
|
+
var E2E_SPEC = `import { expect, test } from "@playwright/test";
|
|
277
|
+
|
|
278
|
+
// Accessibility-first assertions: querying by ROLE (not a brittle CSS selector)
|
|
279
|
+
// is robust AND proves the app exposes a semantic tree \u2014 the same tree the agent
|
|
280
|
+
// sees via \`browser-app snapshot\`.
|
|
281
|
+
test("renders an accessible top-level heading", async ({ page }) => {
|
|
282
|
+
await page.goto("/");
|
|
283
|
+
await expect(page.getByRole("heading", { level: 1 })).toBeVisible();
|
|
284
|
+
});
|
|
285
|
+
`;
|
|
286
|
+
var IDAPT_CI_YML = `version: 1
|
|
287
|
+
jobs:
|
|
288
|
+
test:
|
|
289
|
+
executor: cloud
|
|
290
|
+
steps:
|
|
291
|
+
- npm install
|
|
292
|
+
- npm test
|
|
293
|
+
build:
|
|
294
|
+
executor: cloud
|
|
295
|
+
needs: [test]
|
|
296
|
+
steps:
|
|
297
|
+
- npm install
|
|
298
|
+
- npm run build
|
|
299
|
+
output:
|
|
300
|
+
paths:
|
|
301
|
+
- dist/**
|
|
302
|
+
deploy:
|
|
303
|
+
target: browser-app
|
|
304
|
+
app: __APP_NAME__
|
|
305
|
+
artifacts: build
|
|
306
|
+
`;
|
|
307
|
+
var ESLINT_A11Y_CONFIG = `import jsxA11y from "eslint-plugin-jsx-a11y";
|
|
308
|
+
|
|
309
|
+
// Accessibility-first: the SAME semantic tree powers assistive tech, the e2e
|
|
310
|
+
// specs, and the agent's \`browser-app snapshot\`. jsx-a11y flags missing labels,
|
|
311
|
+
// alt text, and invalid roles so inaccessible markup fails lint, not review.
|
|
312
|
+
export default [
|
|
313
|
+
{
|
|
314
|
+
files: ["src/**/*.{ts,tsx}"],
|
|
315
|
+
plugins: { "jsx-a11y": jsxA11y },
|
|
316
|
+
rules: jsxA11y.flatConfigs.recommended.rules,
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
`;
|
|
320
|
+
var REACT_INDEX_HTML = `<!doctype html>
|
|
321
|
+
<html lang="en">
|
|
322
|
+
<head>
|
|
323
|
+
<meta charset="utf-8" />
|
|
324
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
325
|
+
<title>__APP_NAME__</title>
|
|
326
|
+
</head>
|
|
327
|
+
<body>
|
|
328
|
+
<div id="root"></div>
|
|
329
|
+
<!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency).
|
|
330
|
+
The SDK auto-connects via the app-key cookie; no script tag needed. -->
|
|
331
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
332
|
+
</body>
|
|
333
|
+
</html>
|
|
334
|
+
`;
|
|
335
|
+
var REACT_PKG = "react";
|
|
336
|
+
var REACT_DOM_CLIENT_PKG = "react-dom/client";
|
|
337
|
+
var REACT_MAIN_TSX = `import { StrictMode } from "${REACT_PKG}";
|
|
338
|
+
import { createRoot } from "${REACT_DOM_CLIENT_PKG}";
|
|
339
|
+
import { App } from "./App";
|
|
340
|
+
|
|
341
|
+
createRoot(document.getElementById("root")!).render(
|
|
342
|
+
<StrictMode>
|
|
343
|
+
<App />
|
|
344
|
+
</StrictMode>,
|
|
345
|
+
);
|
|
346
|
+
`;
|
|
347
|
+
var REACT_APP_TSX = `import { connect } from "@idapt/browser-app-sdk";
|
|
348
|
+
import { useEffect, useState } from "${REACT_PKG}";
|
|
349
|
+
|
|
350
|
+
export function App() {
|
|
351
|
+
const [status, setStatus] = useState("Connecting\u2026");
|
|
352
|
+
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
// On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
|
|
355
|
+
connect()
|
|
356
|
+
.then(() => setStatus("Connected."))
|
|
357
|
+
.catch(() => setStatus("Could not connect to idapt."));
|
|
358
|
+
}, []);
|
|
359
|
+
|
|
360
|
+
return (
|
|
361
|
+
<main style={{ maxWidth: "36rem", margin: "0 auto", padding: "2rem 1.25rem" }}>
|
|
362
|
+
<h1>__APP_NAME__</h1>
|
|
363
|
+
<p>{status}</p>
|
|
364
|
+
</main>
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
`;
|
|
368
|
+
var REACT_VITE_CONFIG = `import react from "@vitejs/plugin-react";
|
|
369
|
+
import { defineConfig } from "vite";
|
|
370
|
+
|
|
371
|
+
// Build to dist/ (the served bundle). Relative base so assets resolve on the
|
|
372
|
+
// app subdomain regardless of mount path.
|
|
373
|
+
export default defineConfig({
|
|
374
|
+
base: "./",
|
|
375
|
+
plugins: [react()],
|
|
376
|
+
build: { outDir: "dist" },
|
|
377
|
+
});
|
|
378
|
+
`;
|
|
379
|
+
var REACT_TSCONFIG = `{
|
|
380
|
+
"compilerOptions": {
|
|
381
|
+
"target": "ES2020",
|
|
382
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
383
|
+
"module": "ESNext",
|
|
384
|
+
"moduleResolution": "Bundler",
|
|
385
|
+
"jsx": "react-jsx",
|
|
386
|
+
"strict": true,
|
|
387
|
+
"skipLibCheck": true,
|
|
388
|
+
"noEmit": true
|
|
389
|
+
},
|
|
390
|
+
"include": ["src"]
|
|
391
|
+
}
|
|
392
|
+
`;
|
|
393
|
+
function getReactScaffold(opts) {
|
|
394
|
+
const n = normalize(opts);
|
|
395
|
+
const pkg = {
|
|
396
|
+
name: "idapt-react-app",
|
|
397
|
+
private: true,
|
|
398
|
+
version: "0.1.0",
|
|
399
|
+
type: "module",
|
|
400
|
+
scripts: {
|
|
401
|
+
build: "vite build",
|
|
402
|
+
dev: "vite",
|
|
403
|
+
test: "vitest run",
|
|
404
|
+
"test:e2e": "playwright test",
|
|
405
|
+
lint: "eslint ."
|
|
406
|
+
},
|
|
407
|
+
dependencies: {
|
|
408
|
+
"@idapt/browser-app-sdk": "^0.1.0",
|
|
409
|
+
react: "^18.3.1",
|
|
410
|
+
"react-dom": "^18.3.1"
|
|
411
|
+
},
|
|
412
|
+
devDependencies: {
|
|
413
|
+
"@playwright/test": "^1.48.0",
|
|
414
|
+
"@vitejs/plugin-react": "^4.3.1",
|
|
415
|
+
eslint: "^9.13.0",
|
|
416
|
+
"eslint-plugin-jsx-a11y": "^6.10.0",
|
|
417
|
+
typescript: "^5.5.4",
|
|
418
|
+
vite: "^5.4.0",
|
|
419
|
+
vitest: "^2.1.0"
|
|
420
|
+
},
|
|
421
|
+
idapt: idaptManifest(n)
|
|
422
|
+
};
|
|
423
|
+
return [
|
|
424
|
+
{ path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
|
|
425
|
+
` },
|
|
426
|
+
{
|
|
427
|
+
path: "index.html",
|
|
428
|
+
content: REACT_INDEX_HTML.replaceAll("__APP_NAME__", n.name)
|
|
429
|
+
},
|
|
430
|
+
{ path: "vite.config.ts", content: REACT_VITE_CONFIG },
|
|
431
|
+
{ path: "vitest.config.ts", content: VITEST_CONFIG },
|
|
432
|
+
{ path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
|
|
433
|
+
{ path: "eslint.config.js", content: ESLINT_A11Y_CONFIG },
|
|
434
|
+
{
|
|
435
|
+
path: "idapt-ci.yml",
|
|
436
|
+
content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
|
|
437
|
+
},
|
|
438
|
+
{ path: "tsconfig.json", content: REACT_TSCONFIG },
|
|
439
|
+
{ path: "src/main.tsx", content: REACT_MAIN_TSX },
|
|
440
|
+
{
|
|
441
|
+
path: "src/App.tsx",
|
|
442
|
+
content: REACT_APP_TSX.replaceAll("__APP_NAME__", n.name)
|
|
443
|
+
},
|
|
444
|
+
{ path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
|
|
445
|
+
{ path: "e2e/app.spec.ts", content: E2E_SPEC }
|
|
446
|
+
];
|
|
447
|
+
}
|
|
448
|
+
var VANILLA_INDEX_HTML = `<!doctype html>
|
|
449
|
+
<html lang="en">
|
|
450
|
+
<head>
|
|
451
|
+
<meta charset="utf-8" />
|
|
452
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
453
|
+
<title>__APP_NAME__</title>
|
|
454
|
+
<link rel="stylesheet" href="main.css" />
|
|
455
|
+
</head>
|
|
456
|
+
<body>
|
|
457
|
+
<main class="app">
|
|
458
|
+
<h1>__APP_ICON__ __APP_NAME__</h1>
|
|
459
|
+
<p id="status">Connecting\u2026</p>
|
|
460
|
+
</main>
|
|
461
|
+
<!-- npm-only: the app bundles @idapt/browser-app-sdk itself (a dependency). -->
|
|
462
|
+
<script type="module" src="main.js"></script>
|
|
463
|
+
</body>
|
|
464
|
+
</html>
|
|
465
|
+
`;
|
|
466
|
+
var VANILLA_MAIN_TS = `// __APP_NAME__ \u2014 a buildable idapt browser-app (esbuild).
|
|
467
|
+
import { connect } from "@idapt/browser-app-sdk";
|
|
468
|
+
|
|
469
|
+
const statusEl = document.getElementById("status");
|
|
470
|
+
function setStatus(text: string) {
|
|
471
|
+
if (statusEl) statusEl.textContent = text;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// On an idapt app subdomain the app-key cookie auto-connects \u2014 no args.
|
|
475
|
+
connect()
|
|
476
|
+
.then(() => setStatus("Connected."))
|
|
477
|
+
.catch(() => setStatus("Could not connect to idapt."));
|
|
478
|
+
|
|
479
|
+
export {};
|
|
480
|
+
`;
|
|
481
|
+
var VANILLA_MAIN_CSS = `body {
|
|
482
|
+
margin: 0;
|
|
483
|
+
font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
|
484
|
+
background: #0b0b0f;
|
|
485
|
+
color: #f4f4f5;
|
|
486
|
+
}
|
|
487
|
+
.app {
|
|
488
|
+
max-width: 36rem;
|
|
489
|
+
margin: 0 auto;
|
|
490
|
+
padding: 2rem 1.25rem;
|
|
491
|
+
}
|
|
492
|
+
`;
|
|
493
|
+
var VANILLA_BUILD_SH = `set -e
|
|
494
|
+
mkdir -p dist
|
|
495
|
+
node_modules/.bin/esbuild src/main.ts --bundle --format=esm --outfile=dist/main.js
|
|
496
|
+
cp index.html dist/index.html
|
|
497
|
+
cp src/main.css dist/main.css
|
|
498
|
+
`;
|
|
499
|
+
function getVanillaScaffold(opts) {
|
|
500
|
+
const n = normalize(opts);
|
|
501
|
+
const pkg = {
|
|
502
|
+
name: "idapt-vanilla-app",
|
|
503
|
+
private: true,
|
|
504
|
+
version: "0.1.0",
|
|
505
|
+
type: "module",
|
|
506
|
+
scripts: {
|
|
507
|
+
// The build step runs build.sh so it can bundle + copy static assets.
|
|
508
|
+
build: "sh build.sh",
|
|
509
|
+
test: "vitest run",
|
|
510
|
+
"test:e2e": "playwright test"
|
|
511
|
+
},
|
|
512
|
+
dependencies: {
|
|
513
|
+
"@idapt/browser-app-sdk": "^0.1.0"
|
|
514
|
+
},
|
|
515
|
+
devDependencies: {
|
|
516
|
+
"@playwright/test": "^1.48.0",
|
|
517
|
+
esbuild: "^0.23.0",
|
|
518
|
+
typescript: "^5.5.4",
|
|
519
|
+
vitest: "^2.1.0"
|
|
520
|
+
},
|
|
521
|
+
idapt: idaptManifest(n)
|
|
522
|
+
};
|
|
523
|
+
return [
|
|
524
|
+
{ path: "package.json", content: `${JSON.stringify(pkg, null, 2)}
|
|
525
|
+
` },
|
|
526
|
+
{ path: "build.sh", content: VANILLA_BUILD_SH },
|
|
527
|
+
{ path: "vitest.config.ts", content: VITEST_CONFIG },
|
|
528
|
+
{ path: "playwright.config.ts", content: PLAYWRIGHT_CONFIG },
|
|
529
|
+
{
|
|
530
|
+
path: "idapt-ci.yml",
|
|
531
|
+
content: IDAPT_CI_YML.replaceAll("__APP_NAME__", n.name)
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
path: "index.html",
|
|
535
|
+
content: VANILLA_INDEX_HTML.replaceAll("__APP_NAME__", n.name).replaceAll(
|
|
536
|
+
"__APP_ICON__",
|
|
537
|
+
n.icon
|
|
538
|
+
)
|
|
539
|
+
},
|
|
540
|
+
{
|
|
541
|
+
path: "src/main.ts",
|
|
542
|
+
content: VANILLA_MAIN_TS.replaceAll("__APP_NAME__", n.name)
|
|
543
|
+
},
|
|
544
|
+
{ path: "src/main.css", content: VANILLA_MAIN_CSS },
|
|
545
|
+
{ path: "src/example.test.ts", content: EXAMPLE_UNIT_TEST },
|
|
546
|
+
{ path: "e2e/app.spec.ts", content: E2E_SPEC }
|
|
547
|
+
];
|
|
548
|
+
}
|
|
549
|
+
function getScaffoldFiles(template, opts) {
|
|
550
|
+
return template === "react" ? getReactScaffold(opts) : getVanillaScaffold(opts);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// src/app/init.ts
|
|
554
|
+
function parseInitArgs(args) {
|
|
555
|
+
let name;
|
|
556
|
+
let template = "react";
|
|
557
|
+
let dir = ".";
|
|
558
|
+
let force = false;
|
|
559
|
+
for (let i = 0; i < args.length; i++) {
|
|
560
|
+
const a = args[i];
|
|
561
|
+
if (a === "--template" || a === "-t") {
|
|
562
|
+
template = normTemplate(args[++i]);
|
|
563
|
+
} else if (a.startsWith("--template=")) {
|
|
564
|
+
template = normTemplate(a.slice("--template=".length));
|
|
565
|
+
} else if (a === "--dir" || a === "-d") {
|
|
566
|
+
dir = args[++i] ?? ".";
|
|
567
|
+
} else if (a.startsWith("--dir=")) {
|
|
568
|
+
dir = a.slice("--dir=".length) || ".";
|
|
569
|
+
} else if (a === "--force" || a === "-f") {
|
|
570
|
+
force = true;
|
|
571
|
+
} else if (!a.startsWith("-") && name === void 0) {
|
|
572
|
+
name = a;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return { name, template, dir, force };
|
|
576
|
+
}
|
|
577
|
+
function normTemplate(v) {
|
|
578
|
+
return v === "vanilla" ? "vanilla" : "react";
|
|
579
|
+
}
|
|
580
|
+
function planInit(opts, io) {
|
|
581
|
+
if (opts.template !== "react" && opts.template !== "vanilla") {
|
|
582
|
+
return {
|
|
583
|
+
ok: false,
|
|
584
|
+
written: [],
|
|
585
|
+
error: `unknown template "${opts.template}" (use react or vanilla)`
|
|
586
|
+
};
|
|
587
|
+
}
|
|
588
|
+
if (!opts.force && io.dirIsNonEmpty(opts.dir)) {
|
|
589
|
+
return {
|
|
590
|
+
ok: false,
|
|
591
|
+
written: [],
|
|
592
|
+
error: `target "${opts.dir}" is not empty \u2014 pass --force to scaffold into it anyway`
|
|
593
|
+
};
|
|
594
|
+
}
|
|
595
|
+
const files = getScaffoldFiles(opts.template, {
|
|
596
|
+
name: opts.name ?? "My App"
|
|
597
|
+
});
|
|
598
|
+
const written = [];
|
|
599
|
+
for (const f of files) {
|
|
600
|
+
io.writeFile(join(opts.dir, f.path), f.content);
|
|
601
|
+
written.push(f.path);
|
|
602
|
+
}
|
|
603
|
+
return { ok: true, written };
|
|
604
|
+
}
|
|
605
|
+
function runAppInit(ctx, args) {
|
|
606
|
+
const opts = parseInitArgs(args);
|
|
607
|
+
const io = {
|
|
608
|
+
dirIsNonEmpty: (p) => {
|
|
609
|
+
const abs = resolve(p);
|
|
610
|
+
if (!existsSync(abs)) return false;
|
|
611
|
+
try {
|
|
612
|
+
return readdirSync(abs).length > 0;
|
|
613
|
+
} catch {
|
|
614
|
+
return false;
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
writeFile: (p, content) => {
|
|
618
|
+
const abs = resolve(p);
|
|
619
|
+
mkdirSync(dirname(abs), { recursive: true });
|
|
620
|
+
writeFileSync(abs, content, "utf-8");
|
|
621
|
+
}
|
|
622
|
+
};
|
|
623
|
+
const result = planInit(opts, io);
|
|
624
|
+
if (!result.ok) {
|
|
625
|
+
ctx.out(`idapt app init: ${result.error}`);
|
|
626
|
+
return 1;
|
|
627
|
+
}
|
|
628
|
+
ctx.print(
|
|
629
|
+
`Scaffolded a ${opts.template} browser-app in ${opts.dir} (${result.written.length} files).`
|
|
630
|
+
);
|
|
631
|
+
ctx.out(
|
|
632
|
+
[
|
|
633
|
+
"",
|
|
634
|
+
"Next steps:",
|
|
635
|
+
` cd ${opts.dir}`,
|
|
636
|
+
" npm install && npm run build",
|
|
637
|
+
" idapt app deploy ./dist"
|
|
638
|
+
].join("\n")
|
|
639
|
+
);
|
|
640
|
+
return 0;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/app/index.ts
|
|
644
|
+
var APP_USAGE = `idapt app \u2014 local browser-app developer loop
|
|
645
|
+
|
|
646
|
+
Usage:
|
|
647
|
+
idapt app init [name] [--template react|vanilla] [--dir .] [--force]
|
|
648
|
+
idapt app deploy <dir> [--app <id|name>] [--workspace <id>] [--name <name>]
|
|
649
|
+
|
|
650
|
+
init Scaffold an npm browser-app project locally (then npm install && npm run build)
|
|
651
|
+
deploy Upload a locally-built dist/ to a browser-app (creates one if --app is absent/unknown)`;
|
|
652
|
+
async function runApp(ctx, args) {
|
|
653
|
+
const sub = args[0];
|
|
654
|
+
const rest = args.slice(1);
|
|
655
|
+
if (sub === "init") {
|
|
656
|
+
return runAppInit(ctx, rest);
|
|
657
|
+
}
|
|
658
|
+
if (sub === "deploy") {
|
|
659
|
+
if (!ctx.token) {
|
|
660
|
+
ctx.out(
|
|
661
|
+
"idapt app deploy: not signed in. Run `idapt login` (or set IDAPT_API_KEY)."
|
|
662
|
+
);
|
|
663
|
+
return 1;
|
|
664
|
+
}
|
|
665
|
+
const transport = createFetchTransport({
|
|
666
|
+
baseUrl: ctx.baseUrl,
|
|
667
|
+
token: ctx.token,
|
|
668
|
+
userAgent: USER_AGENT
|
|
669
|
+
});
|
|
670
|
+
const exec = (commandName, cmdArgs) => executeCommand(commandName, cmdArgs, { transport, mode: "json" });
|
|
671
|
+
return runAppDeploy(ctx, rest, exec);
|
|
672
|
+
}
|
|
673
|
+
if (!sub || sub === "--help" || sub === "-h" || sub === "help") {
|
|
674
|
+
ctx.print(APP_USAGE);
|
|
675
|
+
return sub ? 0 : 1;
|
|
676
|
+
}
|
|
677
|
+
ctx.out(`idapt app: unknown subcommand "${sub}".
|
|
678
|
+
|
|
679
|
+
${APP_USAGE}`);
|
|
680
|
+
return 1;
|
|
681
|
+
}
|
|
13
682
|
function canOpenBrowser(env, isTty) {
|
|
14
683
|
if (!isTty) return false;
|
|
15
684
|
if (env.SSH_CONNECTION || env.SSH_TTY) return false;
|
|
@@ -157,7 +826,7 @@ function loginAuthCode(io) {
|
|
|
157
826
|
const base = trimRight(io.baseUrl);
|
|
158
827
|
const { verifier, challenge } = generatePkce();
|
|
159
828
|
const state = randomState();
|
|
160
|
-
return new Promise((
|
|
829
|
+
return new Promise((resolve3, reject) => {
|
|
161
830
|
let settled = false;
|
|
162
831
|
const finish = (fn) => {
|
|
163
832
|
if (settled) return;
|
|
@@ -216,7 +885,7 @@ function loginAuthCode(io) {
|
|
|
216
885
|
},
|
|
217
886
|
io.userAgent
|
|
218
887
|
).then(
|
|
219
|
-
(tok) => finish(() =>
|
|
888
|
+
(tok) => finish(() => resolve3(tok)),
|
|
220
889
|
(e) => finish(() => reject(e))
|
|
221
890
|
);
|
|
222
891
|
});
|
|
@@ -256,8 +925,8 @@ If it doesn't open, visit:
|
|
|
256
925
|
});
|
|
257
926
|
});
|
|
258
927
|
}
|
|
259
|
-
var sleep = (ms, signal) => new Promise((
|
|
260
|
-
const t = setTimeout(
|
|
928
|
+
var sleep = (ms, signal) => new Promise((resolve3, reject) => {
|
|
929
|
+
const t = setTimeout(resolve3, ms);
|
|
261
930
|
signal?.addEventListener("abort", () => {
|
|
262
931
|
clearTimeout(t);
|
|
263
932
|
reject(new AuthError("sign-in cancelled"));
|
|
@@ -516,6 +1185,10 @@ async function runStatus(ctx) {
|
|
|
516
1185
|
}
|
|
517
1186
|
|
|
518
1187
|
// src/doc.ts
|
|
1188
|
+
function dumpJson(resource) {
|
|
1189
|
+
const entries = resource ? commandCatalog.filter((c) => c.resource === resource) : commandCatalog;
|
|
1190
|
+
return JSON.stringify(entries, null, 2);
|
|
1191
|
+
}
|
|
519
1192
|
function helpIndex() {
|
|
520
1193
|
const resources = listResources().sort().join(", ");
|
|
521
1194
|
return [
|
|
@@ -525,6 +1198,7 @@ function helpIndex() {
|
|
|
525
1198
|
" idapt <resource> <verb> [args] Run a command (e.g. idapt drive list)",
|
|
526
1199
|
" idapt help [resource [verb]] Show a command's contract",
|
|
527
1200
|
" idapt instructions [resource] Show a resource's playbook",
|
|
1201
|
+
" idapt app init | deploy Scaffold + ship a local browser-app",
|
|
528
1202
|
" idapt login | logout | whoami Authenticate",
|
|
529
1203
|
" idapt upgrade Update to the latest version",
|
|
530
1204
|
"",
|
|
@@ -552,9 +1226,11 @@ function resourceHelp(resource, cmds) {
|
|
|
552
1226
|
].join("\n");
|
|
553
1227
|
}
|
|
554
1228
|
function renderHelpDoc(tokens) {
|
|
555
|
-
|
|
556
|
-
if (tokens.
|
|
557
|
-
|
|
1229
|
+
const positional = tokens.filter((t) => !t.startsWith("-"));
|
|
1230
|
+
if (tokens.includes("--dump-json")) return dumpJson(positional[0]);
|
|
1231
|
+
if (positional.length === 0) return helpIndex();
|
|
1232
|
+
if (positional.length === 1) {
|
|
1233
|
+
const resource = positional[0];
|
|
558
1234
|
const cmds2 = commandsForResource(resource);
|
|
559
1235
|
if (cmds2.length === 0) {
|
|
560
1236
|
return `Unknown resource "${resource}".
|
|
@@ -563,14 +1239,14 @@ ${helpIndex()}`;
|
|
|
563
1239
|
}
|
|
564
1240
|
return resourceHelp(resource, cmds2);
|
|
565
1241
|
}
|
|
566
|
-
const command = `${
|
|
1242
|
+
const command = `${positional[0]} ${positional[1]}`;
|
|
567
1243
|
const spec = findCommand(command);
|
|
568
1244
|
if (spec) return renderHelp(spec);
|
|
569
|
-
const cmds = commandsForResource(
|
|
1245
|
+
const cmds = commandsForResource(positional[0]);
|
|
570
1246
|
if (cmds.length > 0) {
|
|
571
1247
|
return `Unknown command "${command}".
|
|
572
1248
|
|
|
573
|
-
${resourceHelp(
|
|
1249
|
+
${resourceHelp(positional[0], cmds)}`;
|
|
574
1250
|
}
|
|
575
1251
|
return `Unknown command "${command}".
|
|
576
1252
|
|
|
@@ -703,12 +1379,12 @@ function detectManager(scriptPath, env, channel) {
|
|
|
703
1379
|
};
|
|
704
1380
|
}
|
|
705
1381
|
function spawnInherit(cmd, args) {
|
|
706
|
-
return new Promise((
|
|
1382
|
+
return new Promise((resolve3) => {
|
|
707
1383
|
const child = process.platform === "win32" ? spawn(process.env.ComSpec || "cmd.exe", ["/c", cmd, ...args], {
|
|
708
1384
|
stdio: "inherit"
|
|
709
1385
|
}) : spawn(cmd, args, { stdio: "inherit" });
|
|
710
|
-
child.on("error", () =>
|
|
711
|
-
child.on("close", (code) =>
|
|
1386
|
+
child.on("error", () => resolve3(127));
|
|
1387
|
+
child.on("close", (code) => resolve3(code ?? 0));
|
|
712
1388
|
});
|
|
713
1389
|
}
|
|
714
1390
|
async function runUpgrade(io, opts) {
|
|
@@ -798,6 +1474,31 @@ function extractValueFlag(argv, name) {
|
|
|
798
1474
|
function buildCommandFromArgv(argv) {
|
|
799
1475
|
return ["idapt", ...argv.map(quoteToken)].join(" ");
|
|
800
1476
|
}
|
|
1477
|
+
function resolveJsonArgSource(argv, io) {
|
|
1478
|
+
const resolveVal = (val) => {
|
|
1479
|
+
if (val === "-") return io.readStdin();
|
|
1480
|
+
if (val.startsWith("@")) return io.readFile(val.slice(1));
|
|
1481
|
+
return null;
|
|
1482
|
+
};
|
|
1483
|
+
const out = [];
|
|
1484
|
+
for (let i = 0; i < argv.length; i++) {
|
|
1485
|
+
const tok = argv[i];
|
|
1486
|
+
if (tok.startsWith("--json=")) {
|
|
1487
|
+
const resolved = resolveVal(tok.slice("--json=".length));
|
|
1488
|
+
out.push(resolved === null ? tok : `--json=${resolved}`);
|
|
1489
|
+
continue;
|
|
1490
|
+
}
|
|
1491
|
+
if (tok === "--json" && i + 1 < argv.length) {
|
|
1492
|
+
out.push(tok);
|
|
1493
|
+
const resolved = resolveVal(argv[i + 1]);
|
|
1494
|
+
out.push(resolved === null ? argv[i + 1] : resolved);
|
|
1495
|
+
i++;
|
|
1496
|
+
continue;
|
|
1497
|
+
}
|
|
1498
|
+
out.push(tok);
|
|
1499
|
+
}
|
|
1500
|
+
return out;
|
|
1501
|
+
}
|
|
801
1502
|
function isDocCommand(rest) {
|
|
802
1503
|
if (rest[0] === "help" || rest[0] === "instructions") return true;
|
|
803
1504
|
return rest.some(
|
|
@@ -865,6 +1566,23 @@ async function run(io) {
|
|
|
865
1566
|
);
|
|
866
1567
|
return 1;
|
|
867
1568
|
}
|
|
1569
|
+
if (cmd === "app") {
|
|
1570
|
+
const appCred = await resolveCredential({
|
|
1571
|
+
apiKeyFlag,
|
|
1572
|
+
env: io.env,
|
|
1573
|
+
baseUrl,
|
|
1574
|
+
userAgent: USER_AGENT
|
|
1575
|
+
}).catch(() => null);
|
|
1576
|
+
return runApp(
|
|
1577
|
+
{
|
|
1578
|
+
out: io.stderr,
|
|
1579
|
+
print: io.stdout,
|
|
1580
|
+
baseUrl,
|
|
1581
|
+
token: appCred?.token
|
|
1582
|
+
},
|
|
1583
|
+
rest.slice(1)
|
|
1584
|
+
);
|
|
1585
|
+
}
|
|
868
1586
|
const doc = isDocCommand(rest);
|
|
869
1587
|
let token;
|
|
870
1588
|
const resolved = await resolveCredential({
|
|
@@ -887,7 +1605,11 @@ async function run(io) {
|
|
|
887
1605
|
token,
|
|
888
1606
|
userAgent: USER_AGENT
|
|
889
1607
|
});
|
|
890
|
-
const
|
|
1608
|
+
const restWithJson = resolveJsonArgSource(rest, {
|
|
1609
|
+
readFile: (p) => readFileSync(p, "utf-8"),
|
|
1610
|
+
readStdin: () => readFileSync(0, "utf-8")
|
|
1611
|
+
});
|
|
1612
|
+
const result = await execute(buildCommandFromArgv(restWithJson), {
|
|
891
1613
|
transport,
|
|
892
1614
|
mode: autoMode(io.isTty, requested)
|
|
893
1615
|
});
|
|
@@ -923,6 +1645,6 @@ if (typeof process !== "undefined" && isCliEntrypoint(process.argv[1])) {
|
|
|
923
1645
|
void main();
|
|
924
1646
|
}
|
|
925
1647
|
|
|
926
|
-
export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, run };
|
|
1648
|
+
export { buildCommandFromArgv, extractOutputMode, extractValueFlag, isCliEntrypoint, isDocCommand, resolveJsonArgSource, run };
|
|
927
1649
|
//# sourceMappingURL=bin.js.map
|
|
928
1650
|
//# sourceMappingURL=bin.js.map
|