primitive-admin 1.1.0-alpha.44 → 1.1.0-alpha.46

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.
Files changed (51) hide show
  1. package/dist/bin/primitive.js +2 -0
  2. package/dist/bin/primitive.js.map +1 -1
  3. package/dist/src/commands/blob-buckets.js +20 -7
  4. package/dist/src/commands/blob-buckets.js.map +1 -1
  5. package/dist/src/commands/collections.js +11 -0
  6. package/dist/src/commands/collections.js.map +1 -1
  7. package/dist/src/commands/databases.js +34 -49
  8. package/dist/src/commands/databases.js.map +1 -1
  9. package/dist/src/commands/sync.js +116 -28
  10. package/dist/src/commands/sync.js.map +1 -1
  11. package/dist/src/commands/vars.d.ts +8 -0
  12. package/dist/src/commands/vars.js +110 -0
  13. package/dist/src/commands/vars.js.map +1 -0
  14. package/dist/src/commands/workflows.js +126 -20
  15. package/dist/src/commands/workflows.js.map +1 -1
  16. package/dist/src/lib/api-client.d.ts +10 -0
  17. package/dist/src/lib/api-client.js +19 -0
  18. package/dist/src/lib/api-client.js.map +1 -1
  19. package/dist/src/lib/codegen-shared/generatedFiles.d.ts +61 -0
  20. package/dist/src/lib/codegen-shared/generatedFiles.js +127 -0
  21. package/dist/src/lib/codegen-shared/generatedFiles.js.map +1 -0
  22. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.d.ts +68 -0
  23. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js +168 -0
  24. package/dist/src/lib/codegen-shared/resolveCodegenSourceDir.js.map +1 -0
  25. package/dist/src/lib/db-codegen/dbGenerator.d.ts +30 -5
  26. package/dist/src/lib/db-codegen/dbGenerator.js +227 -165
  27. package/dist/src/lib/db-codegen/dbGenerator.js.map +1 -1
  28. package/dist/src/lib/db-codegen/dbTemplates.d.ts +11 -23
  29. package/dist/src/lib/db-codegen/dbTemplates.js +21 -41
  30. package/dist/src/lib/db-codegen/dbTemplates.js.map +1 -1
  31. package/dist/src/lib/db-codegen/dbTsTypes.d.ts +7 -3
  32. package/dist/src/lib/db-codegen/dbTsTypes.js +23 -1
  33. package/dist/src/lib/db-codegen/dbTsTypes.js.map +1 -1
  34. package/dist/src/lib/generated-allowlist.js +15 -0
  35. package/dist/src/lib/generated-allowlist.js.map +1 -1
  36. package/dist/src/lib/toml-metadata-config.d.ts +10 -2
  37. package/dist/src/lib/toml-metadata-config.js +51 -15
  38. package/dist/src/lib/toml-metadata-config.js.map +1 -1
  39. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.d.ts +129 -0
  40. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js +269 -0
  41. package/dist/src/lib/workflow-codegen/generated-schema-descriptor.js.map +1 -0
  42. package/dist/src/lib/workflow-codegen/generator.d.ts +74 -0
  43. package/dist/src/lib/workflow-codegen/generator.js +215 -0
  44. package/dist/src/lib/workflow-codegen/generator.js.map +1 -0
  45. package/dist/src/lib/workflow-codegen/naming.d.ts +33 -0
  46. package/dist/src/lib/workflow-codegen/naming.js +81 -0
  47. package/dist/src/lib/workflow-codegen/naming.js.map +1 -0
  48. package/dist/src/lib/workflow-codegen/schemaToTs.d.ts +64 -0
  49. package/dist/src/lib/workflow-codegen/schemaToTs.js +282 -0
  50. package/dist/src/lib/workflow-codegen/schemaToTs.js.map +1 -0
  51. package/package.json +4 -2
@@ -0,0 +1,8 @@
1
+ import { Command } from "commander";
2
+ /**
3
+ * `primitive vars` — non-secret per-app config scalars (issue #1364). The
4
+ * plaintext twin of `primitive secrets`: same key format and limits, but
5
+ * values are NOT secret — `list` shows them, and they appear unmasked in
6
+ * debug traces. Never store a credential in a var; use `primitive secrets`.
7
+ */
8
+ export declare function registerVarsCommands(program: Command): void;
@@ -0,0 +1,110 @@
1
+ import { ApiClient } from "../lib/api-client.js";
2
+ import { resolveAppId } from "../lib/config.js";
3
+ import { success, error, info, keyValue, formatTable, formatDate, json, } from "../lib/output.js";
4
+ /**
5
+ * `primitive vars` — non-secret per-app config scalars (issue #1364). The
6
+ * plaintext twin of `primitive secrets`: same key format and limits, but
7
+ * values are NOT secret — `list` shows them, and they appear unmasked in
8
+ * debug traces. Never store a credential in a var; use `primitive secrets`.
9
+ */
10
+ export function registerVarsCommands(program) {
11
+ const vars = program
12
+ .command("vars")
13
+ .description("Manage non-secret app config vars (values are visible; use secrets for credentials)")
14
+ .addHelpText("after", `
15
+ Examples:
16
+ $ primitive vars list
17
+ $ primitive vars set ADMIN_GROUP_ID --value grp_01ABC --summary "Admins group id"
18
+ $ primitive vars set ADMIN_GROUP_ID --value grp_01NEW (updates existing)
19
+ $ primitive vars delete ADMIN_GROUP_ID
20
+
21
+ Vars bind into CEL rules via the declared \`vars.*\` namespace — declare them
22
+ with \`vars = ["ADMIN_GROUP_ID"]\` in the config's access manifest.
23
+ `);
24
+ // List vars (values shown — non-secret)
25
+ vars
26
+ .command("list")
27
+ .description("List all config vars for an app (values are shown)")
28
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
29
+ .option("--json", "Output as JSON")
30
+ .action(async (options) => {
31
+ const resolvedAppId = resolveAppId(undefined, options);
32
+ const client = new ApiClient();
33
+ try {
34
+ const varsList = await client.listAppConfigVars(resolvedAppId);
35
+ if (options.json) {
36
+ json(varsList);
37
+ return;
38
+ }
39
+ if (!varsList || varsList.length === 0) {
40
+ info("No vars found.");
41
+ return;
42
+ }
43
+ console.log(formatTable(varsList, [
44
+ { header: "KEY", key: "key" },
45
+ { header: "VALUE", key: "value" },
46
+ { header: "SUMMARY", key: "summary" },
47
+ { header: "CREATED", key: "createdAt", format: formatDate },
48
+ { header: "UPDATED", key: "updatedAt", format: formatDate },
49
+ ]));
50
+ }
51
+ catch (err) {
52
+ error(err.message);
53
+ process.exit(1);
54
+ }
55
+ });
56
+ // Set var (create or update — by-key upsert)
57
+ vars
58
+ .command("set")
59
+ .description("Create or update a config var")
60
+ .argument("<key>", "Var key (e.g., ADMIN_GROUP_ID)")
61
+ .option("--value <value>", "Var value (non-secret)")
62
+ .option("--summary <text>", "Description of the var")
63
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
64
+ .option("--json", "Output as JSON")
65
+ .action(async (key, options) => {
66
+ const resolvedAppId = resolveAppId(undefined, options);
67
+ if (!options.value) {
68
+ error("--value is required");
69
+ process.exit(1);
70
+ }
71
+ const client = new ApiClient();
72
+ try {
73
+ const payload = { value: options.value };
74
+ if (options.summary !== undefined) {
75
+ payload.summary = options.summary;
76
+ }
77
+ const result = await client.upsertAppConfigVar(resolvedAppId, key, payload);
78
+ if (options.json) {
79
+ json(result);
80
+ return;
81
+ }
82
+ success(`Var '${key}' saved.`);
83
+ keyValue("Key", result?.key || key);
84
+ keyValue("Value", result?.value ?? options.value);
85
+ }
86
+ catch (err) {
87
+ error(err.message);
88
+ process.exit(1);
89
+ }
90
+ });
91
+ // Delete var (key-addressed — no id lookup needed)
92
+ vars
93
+ .command("delete")
94
+ .description("Delete a config var")
95
+ .argument("<key>", "Var key (e.g., ADMIN_GROUP_ID)")
96
+ .option("--app <app-id>", "App ID (uses current app if not specified)")
97
+ .action(async (key, options) => {
98
+ const resolvedAppId = resolveAppId(undefined, options);
99
+ const client = new ApiClient();
100
+ try {
101
+ await client.deleteAppConfigVar(resolvedAppId, key);
102
+ success(`Var '${key}' deleted.`);
103
+ }
104
+ catch (err) {
105
+ error(err.message);
106
+ process.exit(1);
107
+ }
108
+ });
109
+ }
110
+ //# sourceMappingURL=vars.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vars.js","sourceRoot":"","sources":["../../../src/commands/vars.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,12 @@
1
- import { readFileSync, writeFileSync, statSync } from "fs";
1
+ import { readFileSync, writeFileSync, statSync, readdirSync, } from "fs";
2
2
  import { basename } from "path";
3
+ import * as path from "path";
3
4
  import { parseConfigToml, stringifyConfigToml } from "../lib/config-toml.js";
4
5
  import { lookup as mimeLookup } from "mime-types";
6
+ import { generateWorkflowTypes, } from "../lib/workflow-codegen/generator.js";
5
7
  import { ApiClient } from "../lib/api-client.js";
6
8
  import { resolveAppId } from "../lib/config.js";
9
+ import { resolveCodegenSourceDir } from "../lib/codegen-shared/resolveCodegenSourceDir.js";
7
10
  import { validateWorkflowToml, formatWorkflowTomlErrors, } from "../lib/workflow-toml-validator.js";
8
11
  import { expandWorkflow } from "../lib/workflow-fragments.js";
9
12
  import { buildWorkflowPayloadFromToml } from "../lib/workflow-payload.js";
@@ -246,7 +249,7 @@ Examples:
246
249
  workflows
247
250
  .command("get")
248
251
  .description("Get workflow details")
249
- .argument("<workflow-id>", "Workflow ID")
252
+ .argument("<workflow-id>", "Workflow ID or key")
250
253
  .option("--app <app-id>", "App ID (uses current app if not specified)")
251
254
  .option("--json", "Output as JSON")
252
255
  .action(async (workflowId, options) => {
@@ -327,7 +330,7 @@ Examples:
327
330
  workflows
328
331
  .command("update")
329
332
  .description("Update workflow metadata, or push a new body with --from-file")
330
- .argument("<workflow-id>", "Workflow ID")
333
+ .argument("<workflow-id>", "Workflow ID or key")
331
334
  .option("--app <app-id>", "App ID (uses current app if not specified)")
332
335
  .option("--name <name>", "Display name")
333
336
  .option("--description <desc>", "Description")
@@ -506,7 +509,7 @@ Examples:
506
509
  workflows
507
510
  .command("delete")
508
511
  .description("Delete or archive a workflow")
509
- .argument("<workflow-id>", "Workflow ID")
512
+ .argument("<workflow-id>", "Workflow ID or key")
510
513
  .option("--app <app-id>", "App ID (uses current app if not specified)")
511
514
  .option("--hard", "Permanently delete instead of archive")
512
515
  .option("-y, --yes", "Skip confirmation prompt")
@@ -573,7 +576,7 @@ Examples:
573
576
  draft
574
577
  .command("update")
575
578
  .description("Update workflow draft steps (deprecated; use 'workflows configs' for staged rollouts)")
576
- .argument("<workflow-id>", "Workflow ID")
579
+ .argument("<workflow-id>", "Workflow ID or key")
577
580
  .option("--app <app-id>", "App ID (uses current app if not specified)")
578
581
  .option("--from-file <path>", "Load steps from TOML file")
579
582
  .option("--json", "Output as JSON")
@@ -642,7 +645,7 @@ Examples:
642
645
  workflows
643
646
  .command("publish")
644
647
  .description("Publish the current draft as a new revision (deprecated for new-model workflows; use 'workflows configs activate')")
645
- .argument("<workflow-id>", "Workflow ID")
648
+ .argument("<workflow-id>", "Workflow ID or key")
646
649
  .option("--app <app-id>", "App ID (uses current app if not specified)")
647
650
  .option("--json", "Output as JSON")
648
651
  .action(async (workflowId, options) => {
@@ -686,7 +689,7 @@ Examples:
686
689
  workflows
687
690
  .command("preview")
688
691
  .description("Run a preview execution of the workflow")
689
- .argument("<workflow-id>", "Workflow ID")
692
+ .argument("<workflow-id>", "Workflow ID or key")
690
693
  .option("--app <app-id>", "App ID (uses current app if not specified)")
691
694
  .option("--config <config-id>", "Use specific configuration")
692
695
  .option("--draft", "Force preview of the draft version, even if active is newer")
@@ -841,7 +844,7 @@ Examples:
841
844
  runs
842
845
  .command("list")
843
846
  .description("List workflow runs")
844
- .argument("<workflow-id>", "Workflow ID")
847
+ .argument("<workflow-id>", "Workflow ID or key")
845
848
  .option("--app <app-id>", "App ID (uses current app if not specified)")
846
849
  .option("--status <status>", "Filter by status: pending, running, completed, failed")
847
850
  .option("--limit <n>", "Number of runs to show", "20")
@@ -879,7 +882,7 @@ Examples:
879
882
  runs
880
883
  .command("status")
881
884
  .description("Get status of a workflow run")
882
- .argument("<workflow-id>", "Workflow ID")
885
+ .argument("<workflow-id>", "Workflow ID or key")
883
886
  .argument("<run-id>", "Run ID")
884
887
  .option("--app <app-id>", "App ID (uses current app if not specified)")
885
888
  .option("--json", "Output as JSON")
@@ -928,7 +931,7 @@ Examples:
928
931
  runs
929
932
  .command("steps")
930
933
  .description("Show step-level details for a workflow run")
931
- .argument("<workflow-id>", "Workflow ID")
934
+ .argument("<workflow-id>", "Workflow ID or key")
932
935
  .argument("<run-id>", "Run ID")
933
936
  .option("--app <app-id>", "App ID")
934
937
  .option("--json", "Output as JSON")
@@ -978,7 +981,7 @@ Examples:
978
981
  runs
979
982
  .command("step-detail")
980
983
  .description("Show full details for a single step")
981
- .argument("<workflow-id>", "Workflow ID")
984
+ .argument("<workflow-id>", "Workflow ID or key")
982
985
  .argument("<run-id>", "Run ID")
983
986
  .argument("<step-id>", "Step ID")
984
987
  .option("--app <app-id>", "App ID")
@@ -1100,7 +1103,7 @@ Examples:
1100
1103
  runs
1101
1104
  .command("error")
1102
1105
  .description("Show error details for a failed workflow run")
1103
- .argument("<workflow-id>", "Workflow ID")
1106
+ .argument("<workflow-id>", "Workflow ID or key")
1104
1107
  .argument("<run-id>", "Run ID")
1105
1108
  .option("--app <app-id>", "App ID")
1106
1109
  .option("--json", "Output as JSON")
@@ -1169,7 +1172,7 @@ Examples:
1169
1172
  runs
1170
1173
  .command("failures")
1171
1174
  .description("List recent workflow run failures")
1172
- .argument("<workflow-id>", "Workflow ID")
1175
+ .argument("<workflow-id>", "Workflow ID or key")
1173
1176
  .option("--app <app-id>", "App ID")
1174
1177
  .option("--limit <n>", "Number of failures to show", "10")
1175
1178
  .option("--json", "Output as JSON")
@@ -1398,7 +1401,7 @@ Examples:
1398
1401
  configs
1399
1402
  .command("list")
1400
1403
  .description("List configurations for a workflow")
1401
- .argument("<workflow-id>", "Workflow ID")
1404
+ .argument("<workflow-id>", "Workflow ID or key")
1402
1405
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1403
1406
  .option("--json", "Output as JSON")
1404
1407
  .action(async (workflowId, options) => {
@@ -1438,7 +1441,7 @@ Examples:
1438
1441
  configs
1439
1442
  .command("get")
1440
1443
  .description("Get configuration details")
1441
- .argument("<workflow-id>", "Workflow ID")
1444
+ .argument("<workflow-id>", "Workflow ID or key")
1442
1445
  .argument("<config-id>", "Configuration ID")
1443
1446
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1444
1447
  .option("--json", "Output as JSON")
@@ -1474,7 +1477,7 @@ Examples:
1474
1477
  configs
1475
1478
  .command("create")
1476
1479
  .description("Create a new configuration")
1477
- .argument("<workflow-id>", "Workflow ID")
1480
+ .argument("<workflow-id>", "Workflow ID or key")
1478
1481
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1479
1482
  .option("--name <name>", "Configuration name (required)")
1480
1483
  .option("--description <desc>", "Description")
@@ -1528,7 +1531,7 @@ Examples:
1528
1531
  configs
1529
1532
  .command("update")
1530
1533
  .description("Update a configuration")
1531
- .argument("<workflow-id>", "Workflow ID")
1534
+ .argument("<workflow-id>", "Workflow ID or key")
1532
1535
  .argument("<config-id>", "Configuration ID")
1533
1536
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1534
1537
  .option("--name <name>", "Configuration name")
@@ -1584,7 +1587,7 @@ Examples:
1584
1587
  configs
1585
1588
  .command("activate")
1586
1589
  .description("Set a configuration as the active (default) configuration")
1587
- .argument("<workflow-id>", "Workflow ID")
1590
+ .argument("<workflow-id>", "Workflow ID or key")
1588
1591
  .argument("<config-id>", "Configuration ID")
1589
1592
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1590
1593
  .option("--json", "Output as JSON")
@@ -1609,7 +1612,7 @@ Examples:
1609
1612
  configs
1610
1613
  .command("duplicate")
1611
1614
  .description("Duplicate a configuration")
1612
- .argument("<workflow-id>", "Workflow ID")
1615
+ .argument("<workflow-id>", "Workflow ID or key")
1613
1616
  .argument("<config-id>", "Configuration ID to duplicate")
1614
1617
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1615
1618
  .option("--name <name>", "Name for the new configuration")
@@ -1637,7 +1640,7 @@ Examples:
1637
1640
  configs
1638
1641
  .command("archive")
1639
1642
  .description("Archive a configuration")
1640
- .argument("<workflow-id>", "Workflow ID")
1643
+ .argument("<workflow-id>", "Workflow ID or key")
1641
1644
  .argument("<config-id>", "Configuration ID")
1642
1645
  .option("--app <app-id>", "App ID (uses current app if not specified)")
1643
1646
  .option("-y, --yes", "Skip confirmation prompt")
@@ -2456,6 +2459,109 @@ Examples:
2456
2459
  process.exit(1);
2457
2460
  }
2458
2461
  });
2462
+ // ============================================
2463
+ // Codegen command (issue #1442)
2464
+ // ============================================
2465
+ workflows
2466
+ .command("codegen")
2467
+ .description("Generate typed TypeScript signatures from the local workflows/*.toml schemas. Emits one <key>.generated.ts per workflow (<Key>Input/<Key>Output types + a <key>(client) typed invoker factory returning { runSync?, start }).")
2468
+ .argument("[workflow-key]", "Generate for a single workflow (defaults to every workflows/*.toml found)")
2469
+ .option("--app <app-id>", "App ID")
2470
+ .option("--sync-dir <path>", "Override path to the sync directory (defaults to ./.primitive/sync/<env>/<appId>/)")
2471
+ .option("-o, --output <dir>", "Output directory for the <key>.generated.ts files (defaults to <sync-dir>/workflows/generated/)")
2472
+ .option("--check", "Exit non-zero if generated output is out of date (CI guard); does not write.")
2473
+ .option("--json", "Output the result summary as JSON")
2474
+ .action(async (workflowKey, options) => {
2475
+ try {
2476
+ // 1. Resolve the single source workflows/ directory via the shared
2477
+ // active-environment resolver (issue #1510). Honors --sync-dir /
2478
+ // --app overrides, resolves the active env
2479
+ // (--env → PRIMITIVE_ENV → defaultEnvironment → single-env), and
2480
+ // preserves the legacy per-app scan fallback in bare-dir mode.
2481
+ const workflowsSourceDir = resolveCodegenSourceDir({
2482
+ subdir: "workflows",
2483
+ options: { app: options.app, syncDir: options.syncDir },
2484
+ });
2485
+ // 2. Collect the source .toml files (one per workflow), filtered to a
2486
+ // single workflow when an argument is given. Match on the file stem;
2487
+ // the generator resolves the real key from `[workflow].key`.
2488
+ const inputs = [];
2489
+ for (const fileName of readdirSync(workflowsSourceDir)) {
2490
+ if (!fileName.endsWith(".toml"))
2491
+ continue;
2492
+ const stem = fileName.slice(0, -".toml".length);
2493
+ if (workflowKey && stem !== workflowKey)
2494
+ continue;
2495
+ const tomlPath = path.join(workflowsSourceDir, fileName);
2496
+ inputs.push({
2497
+ fileStem: stem,
2498
+ tomlPath,
2499
+ tomlContent: readFileSync(tomlPath, "utf-8"),
2500
+ });
2501
+ }
2502
+ if (inputs.length === 0) {
2503
+ error(workflowKey
2504
+ ? `No workflows/${workflowKey}.toml found under .primitive/sync/.`
2505
+ : "No workflows/*.toml files found to generate from.");
2506
+ process.exit(1);
2507
+ }
2508
+ // 3. Resolve the output directory. Default: a `generated/` subdir of
2509
+ // the first source dir (keeps generated output beside the schema).
2510
+ const outputDir = options.output
2511
+ ? path.resolve(options.output)
2512
+ : path.join(path.dirname(inputs[0].tomlPath), "generated");
2513
+ // 4. Run codegen (or --check).
2514
+ const codegenResult = await generateWorkflowTypes({
2515
+ inputs,
2516
+ outputDir,
2517
+ check: !!options.check,
2518
+ singleWorkflow: !!workflowKey,
2519
+ });
2520
+ if (options.check) {
2521
+ if (codegenResult.mismatches.length > 0) {
2522
+ if (options.json) {
2523
+ json({ ok: false, mismatches: codegenResult.mismatches });
2524
+ }
2525
+ else {
2526
+ error(`Check failed: ${codegenResult.mismatches.length} file(s) out of date.`);
2527
+ for (const m of codegenResult.mismatches) {
2528
+ error(` ${m.reason}: ${path.relative(process.cwd(), m.filePath)}`);
2529
+ }
2530
+ info("Run `primitive workflows codegen` to regenerate.");
2531
+ }
2532
+ process.exit(1);
2533
+ }
2534
+ if (options.json) {
2535
+ json({ ok: true, checked: codegenResult.writtenFiles.length });
2536
+ }
2537
+ else {
2538
+ success(`Check passed: ${codegenResult.writtenFiles.length} file(s) up to date.`);
2539
+ }
2540
+ return;
2541
+ }
2542
+ if (options.json) {
2543
+ json({
2544
+ written: codegenResult.writtenFiles,
2545
+ deleted: codegenResult.deletedFiles,
2546
+ });
2547
+ return;
2548
+ }
2549
+ success(`Generated ${codegenResult.writtenFiles.length} file(s)` +
2550
+ (codegenResult.deletedFiles.length > 0
2551
+ ? `, deleted ${codegenResult.deletedFiles.length} stale file(s).`
2552
+ : "."));
2553
+ for (const f of codegenResult.writtenFiles) {
2554
+ keyValue(" wrote", path.relative(process.cwd(), f));
2555
+ }
2556
+ for (const f of codegenResult.deletedFiles) {
2557
+ keyValue(" deleted", path.relative(process.cwd(), f));
2558
+ }
2559
+ }
2560
+ catch (err) {
2561
+ error(err.message);
2562
+ process.exit(1);
2563
+ }
2564
+ });
2459
2565
  }
2460
2566
  function formatFileSize(bytes) {
2461
2567
  if (bytes < 1024)