@base44-preview/cli 0.1.32-pr.663.ab2ede0 → 0.1.32-pr.663.b609424

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/cli/index.js CHANGED
@@ -39570,9 +39570,9 @@ function tm(e, t, r) {
39570
39570
  return l(["<", r("name"), r("typeArguments"), " ", ...e.map(r, "attributes"), n.selfClosing ? " />" : ">"]);
39571
39571
  let i = n.attributes?.some((u) => V3(u.value) && u.value.value.includes(`
39572
39572
  `)), o = t.singleAttributePerLine && n.attributes.length > 1 ? E : A;
39573
- return l(["<", r("name"), r("typeArguments"), m(e.map(() => [o, r()], "attributes")), ...rm2(n, t, s)], { shouldBreak: i });
39573
+ return l(["<", r("name"), r("typeArguments"), m(e.map(() => [o, r()], "attributes")), ...rm3(n, t, s)], { shouldBreak: i });
39574
39574
  }
39575
- function rm2(e, t, r) {
39575
+ function rm3(e, t, r) {
39576
39576
  return e.selfClosing ? [A, "/>"] : nm(e, t, r) ? [">"] : [f, ">"];
39577
39577
  }
39578
39578
  function nm(e, t, r) {
@@ -111521,7 +111521,7 @@ function N62(e) {
111521
111521
  function nm2(e, t, a) {
111522
111522
  return ie10(t, a, e.typeParameters) || ie10(t, a, e.parameters) || S7(t, e.type);
111523
111523
  }
111524
- function rm3(e, t, a) {
111524
+ function rm4(e, t, a) {
111525
111525
  return ie10(t, a, e.types);
111526
111526
  }
111527
111527
  function im2(e, t, a) {
@@ -114489,7 +114489,7 @@ ${Me.join(`
114489
114489
  return S7(a, t.elementType);
114490
114490
  }, 190: function(t, a, _) {
114491
114491
  return ie10(a, _, t.elements);
114492
- }, 193: rm3, 194: rm3, 195: function(t, a, _) {
114492
+ }, 193: rm4, 194: rm4, 195: function(t, a, _) {
114493
114493
  return S7(a, t.checkType) || S7(a, t.extendsType) || S7(a, t.trueType) || S7(a, t.falseType);
114494
114494
  }, 196: function(t, a, _) {
114495
114495
  return S7(a, t.typeParameter);
@@ -249251,23 +249251,26 @@ var OperationSchema = looseObject({
249251
249251
  responses: record(string2(), unknown()).optional(),
249252
249252
  security: array(SecurityRequirementSchema).optional()
249253
249253
  });
249254
- var PathItemSchema = looseObject({
249255
- ...Object.fromEntries(HTTP_METHODS.map((method) => [method, OperationSchema.optional()])),
249256
- parameters: array(unknown()).optional()
249257
- });
249258
249254
  var OpenApiSpecSchema = looseObject({
249259
249255
  openapi: string2(),
249260
- paths: record(string2(), PathItemSchema),
249256
+ paths: record(string2(), record(string2(), unknown())),
249261
249257
  components: looseObject({
249262
249258
  schemas: record(string2(), unknown()).optional()
249263
249259
  }).optional(),
249264
- security: array(SecurityRequirementSchema).optional()
249260
+ security: array(SecurityRequirementSchema).optional().catch(undefined)
249265
249261
  });
249262
+ var ParameterListSchema = array(unknown());
249263
+
249264
+ // src/core/api-spec/text.ts
249265
+ var DOCS_CALLOUT = /<(Info|Note|Tip|Check|Warning)>([\s\S]*?)<\/\1>/g;
249266
+ var RELATIVE_LINK = /\[([^\]]+)\]\(\/[^)]*\)/g;
249267
+ function cleanText(text) {
249268
+ return text.replace(DOCS_CALLOUT, (_, kind, body) => kind === "Warning" ? `Warning: ${body}` : body).replace(RELATIVE_LINK, "$1");
249269
+ }
249266
249270
 
249267
249271
  // src/core/api-spec/contract.ts
249268
249272
  var SCHEMA_REF_PREFIX = "#/components/schemas/";
249269
249273
  var MAX_REF_DEPTH = 4;
249270
- var FRAMEWORK_VALIDATION_ERROR = "Validation Error";
249271
249274
  function inlineRefs(node, schemas, seen = new Set, depth = 0) {
249272
249275
  if (depth > MAX_REF_DEPTH) {
249273
249276
  return node;
@@ -249307,8 +249310,10 @@ function leftoverRefs(node, schemas, found = new Map) {
249307
249310
  for (const [key, value] of Object.entries(node)) {
249308
249311
  if (key === "$ref" && typeof value === "string" && value.startsWith(SCHEMA_REF_PREFIX) && !found.has(value)) {
249309
249312
  const schema = schemas[value.slice(SCHEMA_REF_PREFIX.length)];
249310
- found.set(value, schema);
249311
- leftoverRefs(schema, schemas, found);
249313
+ if (schema !== undefined) {
249314
+ found.set(value, schema);
249315
+ leftoverRefs(schema, schemas, found);
249316
+ }
249312
249317
  } else {
249313
249318
  leftoverRefs(value, schemas, found);
249314
249319
  }
@@ -249323,11 +249328,6 @@ var SCHEMA_DATA_KEYS = new Set([
249323
249328
  "enum",
249324
249329
  "const"
249325
249330
  ]);
249326
- var DOCS_CALLOUT = /<(Info|Note|Tip|Check|Warning)>([\s\S]*?)<\/\1>/g;
249327
- var RELATIVE_LINK = /\[([^\]]+)\]\(\/[^)]*\)/g;
249328
- function cleanText(text) {
249329
- return text.replace(DOCS_CALLOUT, (_, kind, body) => kind === "Warning" ? `Warning: ${body}` : body).replace(RELATIVE_LINK, "$1");
249330
- }
249331
249331
  function slimSchema(node, isPropertyMap = false) {
249332
249332
  if (Array.isArray(node)) {
249333
249333
  return node.map((item) => slimSchema(item));
@@ -249365,14 +249365,13 @@ function getOperationContract(spec, op) {
249365
249365
  return parsed.success ? [[status, parsed.data]] : [];
249366
249366
  });
249367
249367
  const success = responses.filter(([status]) => status.startsWith("2")).sort(([a], [b]) => a.localeCompare(b)).map(([, response]) => bodyOf(response).schema).find(Boolean);
249368
- const errors = responses.filter(([status, response]) => !status.startsWith("2") && response.description !== FRAMEWORK_VALIDATION_ERROR);
249369
249368
  const requestBody = bodyOf(op.operation.requestBody);
249370
249369
  const contract = {
249371
249370
  endpoint: op.endpoint,
249372
249371
  method: op.method,
249373
249372
  path: op.path,
249374
249373
  summary: op.summary,
249375
- description: cleanText(op.description),
249374
+ description: op.description,
249376
249375
  parameters: (op.operation.parameters ?? []).flatMap((parameter) => {
249377
249376
  const parsed = ParameterSchema.safeParse(parameter);
249378
249377
  if (!parsed.success) {
@@ -249393,11 +249392,7 @@ function getOperationContract(spec, op) {
249393
249392
  }),
249394
249393
  ...requestBody.contentType && requestBody.contentType !== "application/json" ? { requestBodyContentType: requestBody.contentType } : {},
249395
249394
  requestBodySchema: inline(requestBody.schema),
249396
- responseSchema: inline(success),
249397
- errors: Object.fromEntries(errors.map(([status, response]) => [
249398
- status,
249399
- cleanText(response.description ?? "")
249400
- ]))
249395
+ responseSchema: inline(success)
249401
249396
  };
249402
249397
  const refs = leftoverRefs(contract, schemas);
249403
249398
  return {
@@ -249422,7 +249417,7 @@ function isReadOnly(operation, spec) {
249422
249417
  function parameterText(operation) {
249423
249418
  return (operation.parameters ?? []).flatMap((parameter) => {
249424
249419
  const parsed = ParameterSchema.safeParse(parameter);
249425
- return parsed.success ? [parsed.data.name, parsed.data.description ?? ""] : [];
249420
+ return parsed.success ? [parsed.data.name, cleanText(parsed.data.description ?? "")] : [];
249426
249421
  }).join(" ");
249427
249422
  }
249428
249423
  function mergeParameters(shared, own) {
@@ -249441,13 +249436,14 @@ function mergeParameters(shared, own) {
249441
249436
  }
249442
249437
  function listOperations(spec) {
249443
249438
  return Object.entries(spec.paths).flatMap(([path, item]) => HTTP_METHODS.flatMap((method) => {
249444
- const declared = item[method];
249445
- if (!declared) {
249439
+ const parsed = OperationSchema.safeParse(item[method]);
249440
+ if (!parsed.success) {
249446
249441
  return [];
249447
249442
  }
249443
+ const shared = ParameterListSchema.safeParse(item.parameters);
249448
249444
  const operation = {
249449
- ...declared,
249450
- parameters: mergeParameters(item.parameters, declared.parameters)
249445
+ ...parsed.data,
249446
+ parameters: mergeParameters(shared.success ? shared.data : undefined, parsed.data.parameters)
249451
249447
  };
249452
249448
  return [
249453
249449
  {
@@ -249455,7 +249451,7 @@ function listOperations(spec) {
249455
249451
  method: method.toUpperCase(),
249456
249452
  path,
249457
249453
  summary: operation.summary ?? "",
249458
- description: operation.description ?? "",
249454
+ description: cleanText(operation.description ?? ""),
249459
249455
  parameterText: parameterText(operation),
249460
249456
  readOnly: isReadOnly(operation, spec),
249461
249457
  operation
@@ -249488,6 +249484,7 @@ function matchOperation(operations, method, concretePath) {
249488
249484
  }
249489
249485
  // src/core/api-spec/request.ts
249490
249486
  var MAX_ERROR_BODY_CHARS = 4000;
249487
+ var QUERY_STRING = /\?\S*/g;
249491
249488
  async function readBody(response) {
249492
249489
  try {
249493
249490
  const text = (await response.clone().text()).trim();
@@ -249501,8 +249498,9 @@ async function sendApiRequest(method, path, body) {
249501
249498
  const response = await base44Client(path.replace(/^\/+/, ""), {
249502
249499
  method,
249503
249500
  body,
249504
- headers: body === undefined ? {} : { "Content-Type": "application/json" },
249505
- timeout: false
249501
+ headers: typeof body === "string" ? { "Content-Type": "application/json" } : {},
249502
+ timeout: false,
249503
+ retry: { methods: ["get", "head"] }
249506
249504
  });
249507
249505
  return {
249508
249506
  status: response.status,
@@ -249511,20 +249509,27 @@ async function sendApiRequest(method, path, body) {
249511
249509
  };
249512
249510
  } catch (error) {
249513
249511
  const apiError = await ApiError.fromHttpError(error, `calling ${method} ${path}`);
249514
- const raw = error instanceof HTTPError ? await readBody(error.response) : undefined;
249515
- const message = raw && apiError.statusCode !== undefined ? `Error calling ${method} ${path}: HTTP ${apiError.statusCode}` : apiError.message;
249516
- throw new ApiError(message, {
249517
- statusCode: apiError.statusCode,
249518
- requestUrl: apiError.requestUrl,
249519
- requestMethod: apiError.requestMethod,
249520
- requestId: apiError.requestId,
249521
- hints: apiError.hints,
249522
- details: raw ? [`Response body: ${raw}`] : apiError.details
249523
- });
249512
+ const response = error instanceof HTTPError ? error.response : undefined;
249513
+ const raw = response ? await readBody(response) : undefined;
249514
+ const retryAfter = response?.headers.get("retry-after") ?? undefined;
249515
+ throw redactedApiError(apiError, raw, method, path, retryAfter);
249524
249516
  }
249525
249517
  }
249518
+ function redactedApiError(apiError, raw, method, path, retryAfter) {
249519
+ const wait = retryAfter ? ` (retry after ${/^\d+$/.test(retryAfter) ? `${retryAfter} s` : retryAfter})` : "";
249520
+ const reason = apiError.statusCode !== undefined ? `HTTP ${apiError.statusCode}${wait}` : (apiError.cause?.message ?? "request failed").replace(QUERY_STRING, "");
249521
+ const message = `Error calling ${method} ${path.split("?", 1)[0]}: ${reason}`;
249522
+ return new ApiError(message, {
249523
+ statusCode: apiError.statusCode,
249524
+ requestUrl: apiError.requestUrl?.split("?", 1)[0],
249525
+ requestMethod: apiError.requestMethod,
249526
+ requestId: apiError.requestId,
249527
+ hints: apiError.hints,
249528
+ details: raw ? [`Response body: ${raw}`] : apiError.details
249529
+ });
249530
+ }
249526
249531
  // src/core/api-spec/spec.ts
249527
- import { rename, stat as stat3 } from "node:fs/promises";
249532
+ import { rename, rm, stat as stat3 } from "node:fs/promises";
249528
249533
  import { join as join22 } from "node:path";
249529
249534
  var SPEC_CACHE_TTL_MS = 60 * 60 * 1000;
249530
249535
  function getSpecCachePath(apiUrl = getBase44ApiUrl()) {
@@ -249579,12 +249584,19 @@ async function loadSpec(options = {}) {
249579
249584
  const text = await downloadSpec();
249580
249585
  const spec = parseSpec(text);
249581
249586
  const partial = `${path}.${process.pid}.tmp`;
249582
- await writeFile(partial, text);
249583
- await rename(partial, path);
249587
+ try {
249588
+ await writeFile(partial, text);
249589
+ await rename(partial, path);
249590
+ } catch {
249591
+ await rm(partial, { force: true }).catch(() => {
249592
+ return;
249593
+ });
249594
+ }
249584
249595
  return { text, spec };
249585
249596
  }
249586
249597
  // src/cli/commands/api/shared.ts
249587
249598
  import { fstatSync } from "node:fs";
249599
+ import { basename as basename6 } from "node:path";
249588
249600
  function formatJson(value) {
249589
249601
  return toJsonStdout(value, { compact: !process.stdout.isTTY });
249590
249602
  }
@@ -249636,6 +249648,41 @@ async function readRequestBody(flag, log) {
249636
249648
  }
249637
249649
  return flag;
249638
249650
  }
249651
+ var FORM_FILE = /^@(.+?)(?:;type=([^;]+))?$/;
249652
+ async function readFormBody(fields) {
249653
+ const form = new FormData;
249654
+ for (const field of fields) {
249655
+ const separator = field.indexOf("=");
249656
+ if (separator < 1) {
249657
+ throw new InvalidInputError(`--form takes name=value or name=@file, got "${field}".`);
249658
+ }
249659
+ const name = field.slice(0, separator);
249660
+ const value = field.slice(separator + 1);
249661
+ const file = FORM_FILE.exec(value);
249662
+ if (file?.[1]) {
249663
+ const type = file[2] ?? "application/octet-stream";
249664
+ const blob = new Blob([await readFile(file[1])], { type });
249665
+ form.append(name, blob, basename6(file[1]));
249666
+ } else {
249667
+ form.append(name, value);
249668
+ }
249669
+ }
249670
+ return form;
249671
+ }
249672
+ function withoutQuery(path) {
249673
+ return path.split("?", 1)[0] ?? "";
249674
+ }
249675
+ function reportedFetchCommand(method, path, options) {
249676
+ return {
249677
+ name: "api fetch",
249678
+ args: [method, withoutQuery(path)],
249679
+ options: {
249680
+ ...options,
249681
+ ...options.body === undefined ? {} : { body: "[not reported]" },
249682
+ ...Array.isArray(options.form) && options.form.length > 0 ? { form: "[not reported]" } : {}
249683
+ }
249684
+ };
249685
+ }
249639
249686
 
249640
249687
  // src/cli/commands/api/catalog.ts
249641
249688
  async function catalogAction(ctx, area, options) {
@@ -249660,7 +249707,7 @@ async function findOperation(ctx, method, path, { refresh, flag }) {
249660
249707
  const { spec } = await loadSpecTask(ctx, refresh);
249661
249708
  const op = matchOperation(listOperations(spec), method, path);
249662
249709
  if (!op) {
249663
- throw new InvalidInputError(`No operation in the API spec matches ${method} ${path}.`, {
249710
+ throw new InvalidInputError(`No operation in the API spec matches ${method} ${withoutQuery(path)}.`, {
249664
249711
  hints: [
249665
249712
  {
249666
249713
  message: "The cached spec may predate this route; fetch it again",
@@ -249675,6 +249722,9 @@ async function findOperation(ctx, method, path, { refresh, flag }) {
249675
249722
  }
249676
249723
  return { spec, op };
249677
249724
  }
249725
+ var MULTIPART_HINT = "Send this body with --form name=value, and --form name=@path for files.";
249726
+ var CONTRACT_HELP = `
249727
+ The contract is JSON; cut it down with e.g. | jq '.parameters, .requestBodySchema'`;
249678
249728
  var TEXT_CONTENT_TYPES = [
249679
249729
  "json",
249680
249730
  "text/",
@@ -249714,9 +249764,13 @@ async function formatResponse({ status, contentType, body }, output, jsonMode) {
249714
249764
  }
249715
249765
  }
249716
249766
  async function fetchAction(ctx, methodArg, path, options, command) {
249767
+ ctx.errorReporter.setContext({
249768
+ command: reportedFetchCommand(methodArg ?? "", path ?? "", { ...options })
249769
+ });
249717
249770
  if (!methodArg || !path) {
249718
249771
  if (options.help) {
249719
- const usage = command.helpInformation();
249772
+ const usage = `${command.helpInformation()}${CONTRACT_HELP}
249773
+ `;
249720
249774
  return { stdout: ctx.jsonMode ? formatJson({ usage }) : usage };
249721
249775
  }
249722
249776
  throw new InvalidInputError("Provide a method and a path, e.g. GET /api/apps.");
@@ -249727,7 +249781,10 @@ async function fetchAction(ctx, methodArg, path, options, command) {
249727
249781
  refresh: options.refresh,
249728
249782
  flag: "--help"
249729
249783
  });
249730
- return { stdout: formatJson(getOperationContract(spec, op)) };
249784
+ const contract = getOperationContract(spec, op);
249785
+ return {
249786
+ stdout: formatJson(contract.requestBodyContentType === "multipart/form-data" ? { ...contract, hint: MULTIPART_HINT } : contract)
249787
+ };
249731
249788
  }
249732
249789
  if (options.readOnly) {
249733
249790
  const { op } = await findOperation(ctx, method, path, {
@@ -249738,15 +249795,11 @@ async function fetchAction(ctx, methodArg, path, options, command) {
249738
249795
  throw new InvalidInputError(`Refused under --read-only: ${op.method} ${op.path} is not a read-only operation. Nothing was sent.`);
249739
249796
  }
249740
249797
  }
249741
- ctx.errorReporter.setContext({
249742
- command: {
249743
- name: "api fetch",
249744
- args: [method, path.split("?", 1)[0] ?? ""],
249745
- options: { ...options, body: options.body && "[not reported]" }
249746
- }
249747
- });
249798
+ if (options.body !== undefined && options.form.length > 0) {
249799
+ throw new InvalidInputError("Use --body or --form, not both.");
249800
+ }
249748
249801
  await ensureAuth(ctx);
249749
- const body = await readRequestBody(options.body, ctx.log);
249802
+ const body = options.form.length > 0 ? await readFormBody(options.form) : await readRequestBody(options.body, ctx.log);
249750
249803
  const response = await ctx.runTask(`Calling ${method} ${path}`, () => sendApiRequest(method, path, body), {
249751
249804
  successMessage: `Called ${method} ${path}`,
249752
249805
  errorMessage: "Request failed"
@@ -249759,7 +249812,7 @@ function getApiFetchCommand() {
249759
249812
  return new Base44Command("fetch", {
249760
249813
  requireAuth: false,
249761
249814
  requireAppContext: false
249762
- }).description("Call a Base44 API operation; add --help after a path for its contract").helpOption(false).argument("[method]", "HTTP method, e.g. GET or POST").argument("[path]", 'Concrete path with any query string, e.g. "/api/apps/<app-id>/workflows/runs?status=failed"').option("--body <json>", "Request body: JSON, @file.json, or @- to read it from stdin").option("--output <file>", "Save the response body to a file").option("--read-only", "Refuse, before sending, any operation the spec does not mark read-only").option("--refresh", REFRESH_OPTION_HELP).option("-h, --help", "With a method and path, print that operation's contract; otherwise this help").action(fetchAction);
249815
+ }).description("Call a Base44 API operation; add --help after a path for its contract").helpOption(false).argument("[method]", "HTTP method, e.g. GET or POST").argument("[path]", 'Concrete path with any query string, e.g. "/api/apps/<app-id>/workflows/runs?status=failed"').option("--body <json>", "Request body: JSON, @file.json, or @- to read it from stdin").option("--form <field>", "Multipart field: name=value, or name=@file to attach a file (repeatable)", (field, fields) => [...fields, field], []).option("--output <file>", "Save the response body to a file").option("--read-only", "Refuse, before sending, any operation the spec does not mark read-only").option("--refresh", REFRESH_OPTION_HELP).option("-h, --help", "With a method and path, print that operation's contract; otherwise this help").addHelpText("after", CONTRACT_HELP).action(fetchAction);
249763
249816
  }
249764
249817
 
249765
249818
  // src/cli/commands/api/grep.ts
@@ -251578,7 +251631,7 @@ function getBuildCommand() {
251578
251631
  }
251579
251632
 
251580
251633
  // src/cli/commands/project/create.ts
251581
- import { basename as basename6, resolve as resolve11 } from "node:path";
251634
+ import { basename as basename7, resolve as resolve11 } from "node:path";
251582
251635
  var import_kebabCase = __toESM(require_kebabCase(), 1);
251583
251636
 
251584
251637
  // src/cli/commands/project/scaffold-shared.ts
@@ -251743,8 +251796,8 @@ async function createInteractive(options, ctx) {
251743
251796
  name: () => {
251744
251797
  return options.name ? Promise.resolve(options.name) : Ze({
251745
251798
  message: "What is the name of your project?",
251746
- placeholder: basename6(process.cwd()),
251747
- initialValue: basename6(process.cwd()),
251799
+ placeholder: basename7(process.cwd()),
251800
+ initialValue: basename7(process.cwd()),
251748
251801
  validate: (value) => {
251749
251802
  if (!value || value.trim().length === 0) {
251750
251803
  return "Every project deserves a name";
@@ -252439,7 +252492,7 @@ function getLogsCommand() {
252439
252492
  }
252440
252493
 
252441
252494
  // src/cli/commands/project/scaffold.ts
252442
- import { basename as basename7, resolve as resolve12 } from "node:path";
252495
+ import { basename as basename8, resolve as resolve12 } from "node:path";
252443
252496
  function resolveAppId(options) {
252444
252497
  const appId = options.appId;
252445
252498
  if (!appId) {
@@ -252456,7 +252509,7 @@ async function scaffoldAction(ctx, name, options, command) {
252456
252509
  const { log, runTask } = ctx;
252457
252510
  const appId = resolveAppId(command.optsWithGlobals());
252458
252511
  const resolvedPath = resolve12("./");
252459
- const projectName = (name ?? basename7(resolvedPath)).trim();
252512
+ const projectName = (name ?? basename8(resolvedPath)).trim();
252460
252513
  const template = await getTemplateById("backend-only");
252461
252514
  log.info(`Scaffolding project at ${resolvedPath}`);
252462
252515
  const { projectId } = await runTask("Setting up your project...", async () => {
@@ -262381,7 +262434,7 @@ var bundleAppRequestSchema = objectType({
262381
262434
  var ACTIVATION_MODE = "pull";
262382
262435
 
262383
262436
  // ../functions-compiler/src/deno-bundle.ts
262384
- import { mkdtemp, rm, writeFile as writeFile2 } from "node:fs/promises";
262437
+ import { mkdtemp, rm as rm2, writeFile as writeFile2 } from "node:fs/promises";
262385
262438
  import { tmpdir as tmpdir2 } from "node:os";
262386
262439
  import path21 from "node:path";
262387
262440
 
@@ -262988,7 +263041,7 @@ async function bundleToModule(prepared, nodeModulesDir = "none") {
262988
263041
  warnings: result.warnings.map((w) => w.text)
262989
263042
  };
262990
263043
  } finally {
262991
- await rm(dir, { recursive: true, force: true });
263044
+ await rm2(dir, { recursive: true, force: true });
262992
263045
  }
262993
263046
  }
262994
263047
  function buildFailureErrors(e, dir) {
@@ -273353,4 +273406,4 @@ export {
273353
273406
  runCLI
273354
273407
  };
273355
273408
 
273356
- //# debugId=ED51B8A8CDD1B22D64756E2164756E21
273409
+ //# debugId=FFC47D2E535D22E664756E2164756E21