@fluid-app/fluid-cli-widget 0.1.48 → 0.1.49

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/index.mjs CHANGED
@@ -4,7 +4,7 @@ import path from "node:path";
4
4
  import { randomUUID } from "node:crypto";
5
5
  import { execa } from "execa";
6
6
  import { fileURLToPath } from "node:url";
7
- import { findProjectConfig, readConfig } from "@fluid-app/fluid-cli";
7
+ import { findProjectConfig, gitSyncActorFromMe, gitSyncCommitSubject, parseNameStatusZ, readConfig, resolveGitSyncActor, subjectFragment, summarizeChanges } from "@fluid-app/fluid-cli";
8
8
  import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
9
9
  import { cp, mkdir, mkdtemp, readFile, readdir, rm, rmdir, stat, writeFile } from "node:fs/promises";
10
10
  import chalk from "chalk";
@@ -574,9 +574,9 @@ function isRecord$3(value) {
574
574
  }
575
575
  //#endregion
576
576
  //#region src/utils/template.ts
577
- const EMBEDDED_PORTAL_SDK_VERSION = "0.1.500";
578
- const EMBEDDED_CLI_CORE_VERSION = "0.1.22";
579
- const EMBEDDED_CLI_WIDGET_VERSION = "0.1.48";
577
+ const EMBEDDED_PORTAL_SDK_VERSION = "0.1.506";
578
+ const EMBEDDED_CLI_CORE_VERSION = "0.1.23";
579
+ const EMBEDDED_CLI_WIDGET_VERSION = "0.1.49";
580
580
  function getDefaultTemplatePath() {
581
581
  return path.join(findPackageRoot(), "templates", "default");
582
582
  }
@@ -2138,6 +2138,31 @@ const DEFAULT_COMMIT_IDENTITY = {
2138
2138
  *
2139
2139
  * Steps 2 and 3 are best-effort and never fail the push.
2140
2140
  */
2141
+ /**
2142
+ * The staged file set, as {@link ChangeEntry}s — the input a
2143
+ * {@link CommitMessage} resolver summarizes.
2144
+ *
2145
+ * `-z` (NUL-delimited) rather than the default: paths with spaces or
2146
+ * quotes are legal in git and common in themes and asset folders, and
2147
+ * the newline form quotes and escapes them, which would corrupt both the
2148
+ * path and the count.
2149
+ *
2150
+ * Best-effort — a summary is a nicety, never a reason to fail a publish.
2151
+ * An empty result makes the resolver fall back to its own constant.
2152
+ */
2153
+ async function stagedChanges(cwd) {
2154
+ try {
2155
+ const { stdout } = await execa("git", [
2156
+ "diff",
2157
+ "--cached",
2158
+ "--name-status",
2159
+ "-z"
2160
+ ], { cwd });
2161
+ return parseNameStatusZ(stdout);
2162
+ } catch {
2163
+ return [];
2164
+ }
2165
+ }
2141
2166
  async function gitAutoCommit(cwd, message, identity = DEFAULT_COMMIT_IDENTITY) {
2142
2167
  const repoRoot = await resolveRepoRoot(cwd);
2143
2168
  excludeLocalStateFromGit(repoRoot);
@@ -2166,13 +2191,14 @@ async function gitAutoCommit(cwd, message, identity = DEFAULT_COMMIT_IDENTITY) {
2166
2191
  ".mist-desktop"
2167
2192
  ], { cwd: repoRoot });
2168
2193
  } catch {}
2194
+ const subject = typeof message === "string" ? message : await message(await stagedChanges(cwd));
2169
2195
  const identityArgs = await resolveIdentityArgs(cwd, identity);
2170
2196
  try {
2171
2197
  await execa("git", [
2172
2198
  ...identityArgs,
2173
2199
  "commit",
2174
2200
  "-m",
2175
- message
2201
+ subject
2176
2202
  ], {
2177
2203
  cwd,
2178
2204
  stdio: [
@@ -2291,6 +2317,24 @@ function writeWidgetMarker(dir, marker) {
2291
2317
  } : { package_key: marker.package_key };
2292
2318
  writeFileSync(resolveWidgetMarkerPath(dir), JSON.stringify(payload, null, 2) + "\n", "utf8");
2293
2319
  }
2320
+ function readWidgetMarker(dir) {
2321
+ const filePath = resolveWidgetMarkerPath(dir);
2322
+ if (!existsSync(filePath)) return null;
2323
+ try {
2324
+ const parsed = JSON.parse(readFileSync(filePath, "utf8"));
2325
+ if (!parsed || typeof parsed !== "object") return null;
2326
+ const obj = parsed;
2327
+ const packageKey = obj["package_key"];
2328
+ if (typeof packageKey !== "string" || packageKey.length === 0) return null;
2329
+ const droplet = obj["droplet"];
2330
+ return {
2331
+ package_key: packageKey,
2332
+ ...typeof droplet === "string" && droplet.length > 0 ? { droplet } : {}
2333
+ };
2334
+ } catch {
2335
+ return null;
2336
+ }
2337
+ }
2294
2338
  //#endregion
2295
2339
  //#region src/commands/clone.ts
2296
2340
  const cloneCommand = new Command("clone").description("Clone a widget package's git repository (mints a fresh token per call)").argument("<package>", "Widget package key to clone").option("--droplet <uuid>", "Droplet UUID that owns the package (defaults to the company-owned package)").option("--dir <dir>", "Target directory (defaults to ./<package>/)").option("--skip-install", "Skip dependency installation").action((packageKey, options) => runCliAction(async () => {
@@ -3085,25 +3129,24 @@ function printWidgetPublishSummary(options) {
3085
3129
  }
3086
3130
  //#endregion
3087
3131
  //#region src/utils/me.ts
3088
- function str(value) {
3089
- if (typeof value === "string" && value.trim().length > 0) return value.trim();
3090
- return null;
3091
- }
3132
+ /**
3133
+ * `GET /api/me` — the authenticated user's identity, used to stamp
3134
+ * auto-commit messages in the canonical GitSync format. The name fallback
3135
+ * chain and the shape it feeds both live in `@fluid-app/fluid-cli`'s
3136
+ * git-sync/commit-subject.ts, shared with the mist / portal / theme CLIs.
3137
+ *
3138
+ * Best-effort by contract: callers treat a rejection (or a null actor) as
3139
+ * a system-attributed commit rather than failing the push.
3140
+ */
3092
3141
  async function fetchCurrentUser(client) {
3093
- const raw = await client.get({
3142
+ return gitSyncActorFromMe(await client.get({
3094
3143
  responseStyle: "data",
3095
3144
  security: [{
3096
3145
  scheme: "bearer",
3097
3146
  type: "http"
3098
3147
  }],
3099
3148
  url: "/api/me"
3100
- });
3101
- const composed = [str(raw.first_name), str(raw.last_name)].filter((part) => part !== null).join(" ").trim();
3102
- const idValue = str(raw.public_id) ?? (typeof raw.id === "number" && Number.isFinite(raw.id) ? String(raw.id) : null);
3103
- return {
3104
- name: str(raw.full_name) ?? (composed.length > 0 ? composed : null) ?? str(raw.email),
3105
- publicId: idValue
3106
- };
3149
+ }));
3107
3150
  }
3108
3151
  //#endregion
3109
3152
  //#region src/lib/git-sync.ts
@@ -3120,6 +3163,7 @@ async function fetchCurrentUser(client) {
3120
3163
  async function syncWidgetGitRepo(options) {
3121
3164
  const { cwd, client } = options;
3122
3165
  const branch = options.branch ?? "main";
3166
+ const actor = memoizeActor(client);
3123
3167
  if (!await isGitRepository(cwd)) await gitInit(cwd, branch);
3124
3168
  await ensureLocalStateExcluded(cwd);
3125
3169
  const cred = await mintWidgetGitCredential({
@@ -3148,10 +3192,11 @@ async function syncWidgetGitRepo(options) {
3148
3192
  }
3149
3193
  } else pulled = await gitAdoptFetchHead(cwd);
3150
3194
  }
3151
- await purgeLocalStateFromIndex(cwd, "Remove private Mist Desktop state from the widget repository", await resolveCommitIdentity(client));
3195
+ await purgeLocalStateFromIndex(cwd, gitSyncCommitSubject("Remove private Mist Desktop state from the widget repository", null), commitIdentity(await actor()));
3152
3196
  let committed = false;
3153
3197
  if (options.commit !== false && await hasUncommittedChanges(cwd)) {
3154
- await gitAutoCommit(cwd, await buildCommitMessage(client, options.message), await resolveCommitIdentity(client));
3198
+ const resolved = await actor();
3199
+ await gitAutoCommit(cwd, (changes) => buildCommitMessage(cwd, resolved, changes, options.message), commitIdentity(resolved));
3155
3200
  committed = true;
3156
3201
  }
3157
3202
  await gitPush(cwd, cred.remoteUrl, branch);
@@ -3161,33 +3206,33 @@ async function syncWidgetGitRepo(options) {
3161
3206
  committed
3162
3207
  };
3163
3208
  }
3164
- const DEFAULT_MESSAGE_PREFIX = "Publish from Fluid widget CLI";
3165
- function timestamp() {
3166
- return (/* @__PURE__ */ new Date()).toISOString().slice(0, 19).replace("T", " ");
3167
- }
3168
3209
  /**
3169
- * Build the auto-commit message, stamped as `<name>: <msg> (<public_id>)` so
3170
- * collaboration history traces back to a real person. Best-effort: a failed or
3171
- * slow /api/me still yields a usable bare message.
3210
+ * Build the auto-commit message in the canonical GitSync shape —
3211
+ * `<name>: <action> · <timestamp> (<public_id>)` — so widget history reads
3212
+ * the same as mist / theme / portal history and always says who published.
3213
+ * A `-m` message replaces the action but keeps the stamp. An unknown actor
3214
+ * yields a `Fluid:` system commit rather than an unattributed one.
3172
3215
  */
3173
- async function buildCommitMessage(client, base) {
3174
- const baseMessage = base ?? `${DEFAULT_MESSAGE_PREFIX} · ${timestamp()}`;
3175
- try {
3176
- const me = await withTimeout(fetchCurrentUser(client), 5e3);
3177
- return `${me.name ? `${me.name}: ` : ""}${baseMessage}${me.publicId ? ` (${me.publicId})` : ""}`;
3178
- } catch {
3179
- return baseMessage;
3180
- }
3216
+ function buildCommitMessage(cwd, actor, changes, base) {
3217
+ const packageKey = readWidgetMarker(cwd)?.package_key;
3218
+ const fallback = packageKey ? `Publish widget ${subjectFragment(packageKey)}` : "Publish widget";
3219
+ return gitSyncCommitSubject(base?.trim() || summarizeChanges(changes) || fallback, actor);
3181
3220
  }
3182
3221
  /** Prefer the Fluid user for the git author when the repo has no identity. */
3183
- async function resolveCommitIdentity(client) {
3184
- try {
3185
- const me = await withTimeout(fetchCurrentUser(client), 5e3);
3186
- if (me.name) return {
3187
- name: me.name,
3188
- email: "cli@fluid.app"
3189
- };
3190
- } catch {}
3222
+ function commitIdentity(actor) {
3223
+ return actor ? {
3224
+ name: actor.name,
3225
+ email: "cli@fluid.app"
3226
+ } : void 0;
3227
+ }
3228
+ /**
3229
+ * Resolve the signed-in user once per sync, cached across callers.
3230
+ * Best-effort by contract: a failed or slow `/api/me` resolves to null (a
3231
+ * system-attributed commit) instead of failing the push.
3232
+ */
3233
+ function memoizeActor(client) {
3234
+ let pending = null;
3235
+ return () => pending ??= resolveGitSyncActor(() => withTimeout(fetchCurrentUser(client), 5e3));
3191
3236
  }
3192
3237
  function withTimeout(promise, ms) {
3193
3238
  return Promise.race([promise, new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`timed out after ${ms}ms`)), ms))]);