@hitch42/applet 0.1.1-beta.4 → 0.1.1-beta.5

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/applet.js CHANGED
@@ -35,6 +35,7 @@ Deploy options:
35
35
  --changeset <id> Deploy against a pending schema changeset. The build
36
36
  generates against its schema and the release promotes
37
37
  when the changeset applies. Manifest mismatches warn.
38
+ Run applet deploy in the workspace root to deploy every applet.
38
39
 
39
40
  Logs options:
40
41
  --since <duration> Show logs from this period. Default: 1h. Maximum: 7d.
@@ -51,7 +52,9 @@ Variables options:
51
52
  --secret Store the value as a secret.
52
53
  -y, --yes Skip the live applet confirmation.
53
54
 
54
- CI: set HITCH_API_KEY (and HITCH_API_URL for a non-default instance).`;
55
+ CI: set HITCH_API_KEY (and HITCH_API_URL for a non-default instance).
56
+ GitHub Actions: generate, build, and deploy use the job's OIDC token instead of
57
+ a key. Grant the job id-token: write.`;
55
58
  async function main() {
56
59
  const { positionals, values } = parseArgs({
57
60
  allowPositionals: true,
@@ -82,23 +85,23 @@ async function main() {
82
85
  const noLogFlags = values.run === void 0 && values.filter === void 0 && values.level === void 0 && values.limit === void 0 && values.live === void 0 && values.search === void 0 && values.since === void 0;
83
86
  const noModeFlags = values["client-only"] === void 0 && values["server-only"] === void 0;
84
87
  if (positionals.length === 1 && positionals[0] === "bootstrap" && values.changeset === void 0 && noModeFlags && noGitFlags && noVariableFlags && noLogFlags) {
85
- await (await import("./init-BN-p6XMP.js")).runBootstrap({ install: values["no-install"] !== true });
88
+ await (await import("./init-VzV28EFT.js")).runBootstrap({ install: values["no-install"] !== true });
86
89
  return;
87
90
  }
88
91
  if (positionals.length === 1 && positionals[0] === "generate" && values["no-install"] === void 0 && noModeFlags && noGitFlags && noVariableFlags && noLogFlags) {
89
- await (await import("./generate-DnD-wLog.js")).runGenerate({ changesetId: values.changeset });
92
+ await (await import("./generate-D5rSXXa-.js")).runGenerate({ changesetId: values.changeset });
90
93
  return;
91
94
  }
92
95
  if (positionals.length === 1 && positionals[0] === "build" && values["no-install"] === void 0 && noModeFlags && noGitFlags && noVariableFlags && noLogFlags) {
93
- await (await import("./build-DwUC2Gkd.js")).runBuild({ changesetId: values.changeset });
96
+ await (await import("./build-pvl3vj91.js")).runBuild({ changesetId: values.changeset });
94
97
  return;
95
98
  }
96
99
  if (positionals.length === 1 && positionals[0] === "deploy" && values["no-install"] === void 0 && noModeFlags && noGitFlags && noVariableFlags && noLogFlags) {
97
- await (await import("./deploy-DWegAaXf.js")).runDeploy({ changesetId: values.changeset });
100
+ await (await import("./deploy-BDonkPqF.js")).runDeploy({ changesetId: values.changeset });
98
101
  return;
99
102
  }
100
103
  if (positionals.length === 2 && positionals[0] === "init" && values.changeset === void 0 && noModeFlags && noVariableFlags && noLogFlags) {
101
- await (await import("./init-BN-p6XMP.js")).runInit(positionals[1], {
104
+ await (await import("./init-VzV28EFT.js")).runInit(positionals[1], {
102
105
  commit: values["no-commit"] !== true,
103
106
  git: values["no-git"] !== true,
104
107
  install: values["no-install"] !== true
@@ -106,7 +109,7 @@ async function main() {
106
109
  return;
107
110
  }
108
111
  if (positionals.length === 2 && positionals[0] === "new" && values.changeset === void 0 && noGitFlags && noVariableFlags && noLogFlags) {
109
- await (await import("./init-BN-p6XMP.js")).runNew({
112
+ await (await import("./init-VzV28EFT.js")).runNew({
110
113
  code: positionals[1],
111
114
  clientOnly: values["client-only"],
112
115
  install: values["no-install"] !== true,
@@ -28,8 +28,7 @@ async function runPnpm(args, cwd) {
28
28
  });
29
29
  });
30
30
  }
31
- async function buildApplet(changesetId, outDir = ".hitch/dist") {
32
- const appletRoot = process.cwd();
31
+ async function buildApplet(appletRoot, changesetId, outDir = ".hitch/dist") {
33
32
  const { workspaceRoot } = await prepareAppletProject(appletRoot);
34
33
  await rm(resolve(appletRoot, ".hitch"), {
35
34
  force: true,
@@ -68,7 +67,7 @@ async function buildApplet(changesetId, outDir = ".hitch/dist") {
68
67
  });
69
68
  }
70
69
  async function runBuild(options = {}) {
71
- await buildApplet(options.changesetId);
70
+ await buildApplet(process.cwd(), options.changesetId);
72
71
  }
73
72
  //#endregion
74
73
  export { runBuild as n, buildApplet as t };
@@ -0,0 +1,2 @@
1
+ import { n as runBuild } from "./build-D1S5XCRV.js";
2
+ export { runBuild };
@@ -0,0 +1,52 @@
1
+ import { i as resolveInstanceUrl } from "./auth-DaVK0k5R.js";
2
+ //#region src/ci.ts
3
+ function githubActionsContext(env = process.env) {
4
+ if (env.GITHUB_ACTIONS !== "true" || env.HITCH_API_KEY) return void 0;
5
+ const requestUrl = env.ACTIONS_ID_TOKEN_REQUEST_URL;
6
+ const requestToken = env.ACTIONS_ID_TOKEN_REQUEST_TOKEN;
7
+ if (!requestUrl || !requestToken) throw new Error("GitHub Actions did not provide an OIDC token. Grant the job the id-token: write permission, or set HITCH_API_KEY.");
8
+ return {
9
+ requestUrl,
10
+ requestToken
11
+ };
12
+ }
13
+ async function hitchResponseError(response, summary) {
14
+ let details = "";
15
+ try {
16
+ const body = await response.json();
17
+ if (body !== null && typeof body === "object") details = ["code" in body && typeof body.code === "string" ? body.code : void 0, "message" in body && typeof body.message === "string" ? body.message : void 0].filter((value) => value !== void 0).join(": ");
18
+ } catch {
19
+ details = "";
20
+ }
21
+ return /* @__PURE__ */ new Error(`${summary} (${response.status})${details ? `: ${details}` : ""}.`);
22
+ }
23
+ async function githubActionsToken(context, instanceUrl, fetcher) {
24
+ const url = new URL(context.requestUrl);
25
+ url.searchParams.set("audience", new URL(instanceUrl).origin);
26
+ const response = await fetcher(url, { headers: { Authorization: `bearer ${context.requestToken}` } });
27
+ if (!response.ok) throw new Error(`GitHub did not issue an OIDC token (${response.status}).`);
28
+ const body = await response.json();
29
+ if (body === null || typeof body !== "object" || !("value" in body)) throw new Error("GitHub returned an OIDC token response without a value.");
30
+ if (typeof body.value !== "string") throw new Error("GitHub returned an OIDC token response without a value.");
31
+ return body.value;
32
+ }
33
+ async function fetchWithGitHubActions(context, options, request) {
34
+ const instanceUrl = await resolveInstanceUrl(void 0, options.store);
35
+ return request(instanceUrl, await githubActionsToken(context, instanceUrl, options.fetch ?? globalThis.fetch));
36
+ }
37
+ async function loadGitHubActionsBuildInputs(context, options) {
38
+ const fetcher = options.fetch ?? globalThis.fetch;
39
+ const response = await fetchWithGitHubActions(context, options, (instanceUrl, accessToken) => fetcher(`${instanceUrl}/api/ci/v1/build-inputs`, {
40
+ method: "POST",
41
+ headers: { Authorization: `Bearer ${accessToken}` }
42
+ }));
43
+ if (!response.ok) throw await hitchResponseError(response, "Failed to load the CI build inputs");
44
+ const body = await response.json();
45
+ if (body === null || typeof body !== "object" || !("openapi" in body) || !("theme" in body) || typeof body.theme !== "string") throw new Error("The CI build inputs must contain an openapi document and a theme string.");
46
+ return {
47
+ openapi: body.openapi,
48
+ theme: body.theme
49
+ };
50
+ }
51
+ //#endregion
52
+ export { loadGitHubActionsBuildInputs as i, githubActionsContext as n, hitchResponseError as r, fetchWithGitHubActions as t };
@@ -1,6 +1,9 @@
1
1
  import { t as fetchWithOrgAuth } from "./auth-DaVK0k5R.js";
2
- import { t as buildApplet } from "./build-B5hbyJGZ.js";
2
+ import { n as githubActionsContext, r as hitchResponseError, t as fetchWithGitHubActions } from "./ci-D0nC_Ho7.js";
3
+ import { r as readWorkspaceConfig } from "./load-config-BnxocLAQ.js";
4
+ import { t as buildApplet } from "./build-D1S5XCRV.js";
3
5
  import { createRequire } from "node:module";
6
+ import { existsSync } from "node:fs";
4
7
  import { readFile, readdir } from "node:fs/promises";
5
8
  import { relative, resolve, sep } from "node:path";
6
9
  import { execFile } from "node:child_process";
@@ -2355,14 +2358,17 @@ async function createBundle(distDir) {
2355
2358
  body.set(bundle);
2356
2359
  return body;
2357
2360
  }
2358
- async function readGitInfo() {
2359
- const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_")));
2361
+ function gitEnvironment() {
2362
+ return Object.fromEntries(Object.entries(process.env).filter(([key]) => !key.startsWith("GIT_")));
2363
+ }
2364
+ async function readGitInfo(appletRoot) {
2365
+ const env = gitEnvironment();
2360
2366
  try {
2361
2367
  const [{ stdout: status }, { stdout: sha }] = await Promise.all([execFileAsync("git", ["status", "--porcelain"], {
2362
- cwd: process.cwd(),
2368
+ cwd: appletRoot,
2363
2369
  env
2364
2370
  }), execFileAsync("git", ["rev-parse", "HEAD"], {
2365
- cwd: process.cwd(),
2371
+ cwd: appletRoot,
2366
2372
  env
2367
2373
  })]);
2368
2374
  return {
@@ -2373,26 +2379,26 @@ async function readGitInfo() {
2373
2379
  return { gitDirty: false };
2374
2380
  }
2375
2381
  }
2376
- async function deployError(response) {
2377
- let details = "";
2378
- try {
2379
- const body = await response.json();
2380
- if (body !== null && typeof body === "object") details = ["code" in body && typeof body.code === "string" ? body.code : void 0, "message" in body && typeof body.message === "string" ? body.message : void 0].filter((value) => value !== void 0).join(": ");
2381
- } catch {
2382
- details = "";
2383
- }
2384
- return /* @__PURE__ */ new Error(`Deploy failed (${response.status})${details ? `: ${details}` : ""}.`);
2382
+ async function repositoryDirectory(appletRoot) {
2383
+ const { stdout } = await execFileAsync("git", ["rev-parse", "--show-toplevel"], {
2384
+ cwd: appletRoot,
2385
+ env: gitEnvironment()
2386
+ });
2387
+ const directory = relative(stdout.trim(), appletRoot).split(sep).join("/") || ".";
2388
+ if (directory === ".." || directory.startsWith("../")) throw new Error(`${appletRoot} is outside its git repository ${stdout.trim()}.`);
2389
+ return directory;
2385
2390
  }
2386
- async function runDeploy(options) {
2387
- if (options.dist === void 0) await buildApplet(options.changesetId);
2388
- const distDir = resolve(options.dist ?? ".hitch/dist");
2389
- const manifest = await readManifest(distDir);
2390
- const [bundle, git] = await Promise.all([createBundle(distDir), readGitInfo()]);
2391
+ function releaseForm(manifest, bundle) {
2392
+ const body = new FormData();
2393
+ body.set("manifest", manifest.raw);
2394
+ body.set("bundle", new File([bundle], "bundle.tar.gz", { type: "application/gzip" }));
2395
+ return body;
2396
+ }
2397
+ async function deployWithOrgAuth(appletRoot, distDir, manifest, options) {
2391
2398
  const fetcher = options.fetch ?? globalThis.fetch;
2392
- const response = await fetchWithOrgAuth(manifest.org, options, (instanceUrl, accessToken) => {
2393
- const body = new FormData();
2394
- body.set("manifest", manifest.raw);
2395
- body.set("bundle", new File([bundle], "bundle.tar.gz", { type: "application/gzip" }));
2399
+ const [bundle, git] = await Promise.all([createBundle(distDir), readGitInfo(appletRoot)]);
2400
+ return fetchWithOrgAuth(manifest.org, options, (instanceUrl, accessToken) => {
2401
+ const body = releaseForm(manifest, bundle);
2396
2402
  body.set("gitDirty", String(git.gitDirty));
2397
2403
  if (git.gitSha !== void 0) body.set("gitSha", git.gitSha);
2398
2404
  if (options.changesetId !== void 0) body.set("changeset_id", options.changesetId);
@@ -2402,7 +2408,28 @@ async function runDeploy(options) {
2402
2408
  body
2403
2409
  });
2404
2410
  });
2405
- if (!response.ok) throw await deployError(response);
2411
+ }
2412
+ async function deployWithGitHubActions(context, appletRoot, distDir, manifest, options) {
2413
+ const fetcher = options.fetch ?? globalThis.fetch;
2414
+ const [bundle, sourceDirectory] = await Promise.all([createBundle(distDir), repositoryDirectory(appletRoot)]);
2415
+ return fetchWithGitHubActions(context, options, (instanceUrl, accessToken) => {
2416
+ const body = releaseForm(manifest, bundle);
2417
+ body.set("source_directory", sourceDirectory);
2418
+ return fetcher(`${instanceUrl}/api/ci/v1/applets/${manifest.code}/releases`, {
2419
+ method: "POST",
2420
+ headers: { Authorization: `Bearer ${accessToken}` },
2421
+ body
2422
+ });
2423
+ });
2424
+ }
2425
+ async function deployApplet(appletRoot, options) {
2426
+ const github = githubActionsContext(options.store?.env ?? process.env);
2427
+ if (github !== void 0 && options.changesetId !== void 0) throw new Error("A changeset deploy is not available in GitHub Actions.");
2428
+ if (options.dist === void 0) await buildApplet(appletRoot, options.changesetId);
2429
+ const distDir = resolve(appletRoot, options.dist ?? ".hitch/dist");
2430
+ const manifest = await readManifest(distDir);
2431
+ const response = github === void 0 ? await deployWithOrgAuth(appletRoot, distDir, manifest, options) : await deployWithGitHubActions(github, appletRoot, distDir, manifest, options);
2432
+ if (!response.ok) throw await hitchResponseError(response, "Deploy failed");
2406
2433
  const result = await response.json();
2407
2434
  console.log(`Deployed release ${result.data.id} (${result.promoted ? "promoted" : "not promoted"}).`);
2408
2435
  if (result.warnings?.length) {
@@ -2412,5 +2439,26 @@ async function runDeploy(options) {
2412
2439
  if (result.unset_variables?.length) console.warn(`Warning: declared variables not set: ${result.unset_variables.join(", ")}. Set them with applet variables set.`);
2413
2440
  return result;
2414
2441
  }
2442
+ async function workspaceAppletRoots(workspaceRoot) {
2443
+ const appletsDir = resolve(workspaceRoot, "applets");
2444
+ const roots = (await readdir(appletsDir, { withFileTypes: true }).catch((error) => {
2445
+ if (isErrno(error, "ENOENT")) return [];
2446
+ throw error;
2447
+ })).filter((entry) => entry.isDirectory()).map((entry) => resolve(appletsDir, entry.name)).filter((root) => existsSync(resolve(root, "applet.manifest.ts"))).sort();
2448
+ if (roots.length === 0) throw new Error(`No applets were found under ${appletsDir}.`);
2449
+ return roots;
2450
+ }
2451
+ async function runDeploy(options) {
2452
+ const root = process.cwd();
2453
+ if (existsSync(resolve(root, "applet.manifest.ts"))) {
2454
+ await deployApplet(root, options);
2455
+ return;
2456
+ }
2457
+ if (readWorkspaceConfig(root) === void 0) throw new Error("Run applet deploy in an applet folder or in the workspace root.");
2458
+ for (const appletRoot of await workspaceAppletRoots(root)) {
2459
+ console.log(`Deploying ${relative(root, appletRoot)}...`);
2460
+ await deployApplet(appletRoot, options);
2461
+ }
2462
+ }
2415
2463
  //#endregion
2416
2464
  export { runDeploy };
@@ -1,4 +1,5 @@
1
1
  import { i as resolveInstanceUrl, n as organizationInstanceUrl, t as fetchWithOrgAuth } from "./auth-DaVK0k5R.js";
2
+ import { i as loadGitHubActionsBuildInputs, n as githubActionsContext } from "./ci-D0nC_Ho7.js";
2
3
  import { t as loadAppletConfig } from "./load-config-BnxocLAQ.js";
3
4
  import { r as prepareAppletProject } from "./project-mW0vsN8A.js";
4
5
  import { createRequire } from "node:module";
@@ -352,30 +353,34 @@ async function responseJson(response) {
352
353
  }
353
354
  }
354
355
  async function loadAuthenticatedSpec(orgCode, options) {
355
- const ciPath = (options.store?.env ?? process.env).HITCH_CI_OPENAPI_PATH;
356
- if (ciPath !== void 0) try {
357
- return JSON.parse(await readFile(ciPath, "utf8"));
358
- } catch (error) {
359
- throw new Error(`Failed to read the CI OpenAPI input from ${ciPath}.`, { cause: error });
360
- }
356
+ const github = githubActionsContext(options.store?.env ?? process.env);
357
+ if (github !== void 0) return (await loadGitHubActionsBuildInputs(github, options)).openapi;
361
358
  const fetcher = options.fetch ?? globalThis.fetch;
362
359
  const response = await fetchWithOrgAuth(orgCode, options, (instanceUrl, accessToken) => fetcher(`${instanceUrl}/api/v1/openapi.json${options.changesetId === void 0 ? "" : `?changeset_id=${encodeURIComponent(options.changesetId)}`}`, { headers: { Authorization: `Bearer ${accessToken}` } }));
363
360
  if (!response.ok) throw new Error(`Failed to load the organization's OpenAPI spec (${response.status}).`);
364
361
  return responseJson(response);
365
362
  }
366
363
  async function loadThemeStylesheet(orgCode, options) {
367
- const ciPath = (options.store?.env ?? process.env).HITCH_CI_THEME_PATH;
368
- if (ciPath !== void 0) try {
369
- return await readFile(ciPath, "utf8");
370
- } catch (error) {
371
- throw new Error(`Failed to read the CI theme input from ${ciPath}.`, { cause: error });
372
- }
373
364
  const fetcher = options.fetch ?? globalThis.fetch;
374
365
  const instanceUrl = await resolveInstanceUrl(void 0, options.store);
375
366
  const response = await fetcher(`${organizationInstanceUrl(instanceUrl, orgCode)}/theme.css`);
376
367
  if (!response.ok) throw new Error(`Failed to load the organization's theme (${response.status}).`);
377
368
  return response.text();
378
369
  }
370
+ async function loadBuildInputs(orgCode, options) {
371
+ const github = githubActionsContext(options.store?.env ?? process.env);
372
+ if (github !== void 0) {
373
+ const inputs = await loadGitHubActionsBuildInputs(github, options);
374
+ return {
375
+ spec: inputs.openapi,
376
+ theme: inputs.theme
377
+ };
378
+ }
379
+ return {
380
+ spec: await loadAuthenticatedSpec(orgCode, options),
381
+ theme: await loadThemeStylesheet(orgCode, options)
382
+ };
383
+ }
379
384
  async function generateContract(spec, outDir) {
380
385
  await mkdir(outDir, { recursive: true });
381
386
  const paths = spec !== null && typeof spec === "object" && "paths" in spec ? spec.paths : void 0;
@@ -444,8 +449,7 @@ async function runGenerate(options) {
444
449
  const root = process.cwd();
445
450
  const outDir = resolve("./.hitch");
446
451
  const manifest = await loadAppletConfig(process.cwd());
447
- const spec = await loadAuthenticatedSpec(manifest.org, options);
448
- const themeStylesheet = await loadThemeStylesheet(manifest.org, options);
452
+ const { spec, theme: themeStylesheet } = await loadBuildInputs(manifest.org, options);
449
453
  const stagedDir = await mkdtemp(resolve("./.hitch.tmp-"));
450
454
  const backupDir = `${stagedDir}.backup`;
451
455
  let staged = true;
@@ -0,0 +1,2 @@
1
+ import { n as runGenerate } from "./generate-C6eAL5eo.js";
2
+ export { runGenerate };
@@ -1,6 +1,6 @@
1
1
  import { a as ORG_CODE_PATTERN, i as APPLET_CODE_PATTERN } from "./manifest-DyoJ1MIL.js";
2
2
  import { i as resolveInstanceUrl, r as resolveAuthForOrg } from "./auth-DaVK0k5R.js";
3
- import { n as runGenerate } from "./generate-C6Sy7X7A.js";
3
+ import { n as runGenerate } from "./generate-C6eAL5eo.js";
4
4
  import { n as loadAppletSourceDefinition, r as readWorkspaceConfig } from "./load-config-BnxocLAQ.js";
5
5
  import { a as writeWorkspaceSupport, i as writeAppletSupport, n as loadCliManifest, t as assertWorkspaceCli } from "./project-mW0vsN8A.js";
6
6
  import { access, cp, mkdir, readdir, writeFile } from "node:fs/promises";
package/dist/vite.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { i as APPLET_CODE_PATTERN, n as appletManifestSchema, r as appletSourceDefinitionSchema } from "./manifest-DyoJ1MIL.js";
2
2
  import { i as resolveInstanceUrl, n as organizationInstanceUrl } from "./auth-DaVK0k5R.js";
3
- import { c as writeClientEntry, i as writeRegisterDeclaration, l as writeManifestGen, r as syncContract, s as writeAppletRoot, u as writeServerEntry } from "./generate-C6Sy7X7A.js";
3
+ import { c as writeClientEntry, i as writeRegisterDeclaration, l as writeManifestGen, r as syncContract, s as writeAppletRoot, u as writeServerEntry } from "./generate-C6eAL5eo.js";
4
4
  import { i as resolveWorkspaceOrg } from "./load-config-BnxocLAQ.js";
5
5
  import { existsSync } from "node:fs";
6
6
  import { appendFile, readFile, readdir, rm, writeFile } from "node:fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hitch42/applet",
3
- "version": "0.1.1-beta.4",
3
+ "version": "0.1.1-beta.5",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "applet": "./dist/applet.js"
@@ -18,4 +18,4 @@ jobs:
18
18
  contents: read
19
19
  id-token: write
20
20
  # Hitch verifies the called workflow's commit SHA from the OIDC token.
21
- uses: hitch-42/actions/.github/workflows/deploy.yml@ea655986e6ad6d41b9859dacd3183b7feca8e23e
21
+ uses: hitch-42/actions/.github/workflows/deploy.yml@b03cda4a4369decfc376d87425c21bcb61bddc57
@@ -1,2 +0,0 @@
1
- import { n as runBuild } from "./build-B5hbyJGZ.js";
2
- export { runBuild };
@@ -1,2 +0,0 @@
1
- import { n as runGenerate } from "./generate-C6Sy7X7A.js";
2
- export { runGenerate };