pgkiln 0.84.0 → 0.85.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.
Files changed (81) hide show
  1. package/docs/apex-feature-parity.md +2 -2
  2. package/docs/guide/03-builder.md +14 -5
  3. package/docs/guide/12-development.md +6 -1
  4. package/package.json +1 -1
  5. package/src/app.ts +9 -0
  6. package/src/builder/ai-agents.ts +86 -85
  7. package/src/builder/ai-builder.ts +30 -30
  8. package/src/builder/ai.ts +93 -92
  9. package/src/builder/api.ts +47 -47
  10. package/src/builder/app-groups.ts +33 -32
  11. package/src/builder/appfromfile.ts +57 -58
  12. package/src/builder/appsheets.ts +33 -32
  13. package/src/builder/appwizard.ts +65 -65
  14. package/src/builder/arrange.ts +23 -22
  15. package/src/builder/assistant.ts +39 -38
  16. package/src/builder/automations.ts +32 -31
  17. package/src/builder/availability.ts +24 -24
  18. package/src/builder/backups.ts +30 -31
  19. package/src/builder/blueprints.ts +62 -61
  20. package/src/builder/create-page.ts +52 -50
  21. package/src/builder/cross-app.ts +37 -35
  22. package/src/builder/dataload.ts +2 -2
  23. package/src/builder/datasets.ts +107 -106
  24. package/src/builder/dependencies.ts +20 -19
  25. package/src/builder/designer.ts +36 -36
  26. package/src/builder/diagnostics.ts +58 -57
  27. package/src/builder/features.ts +39 -35
  28. package/src/builder/feedback.ts +23 -20
  29. package/src/builder/find-workspace.ts +28 -24
  30. package/src/builder/form-designer.ts +36 -35
  31. package/src/builder/gallery.ts +25 -29
  32. package/src/builder/globalization.ts +50 -49
  33. package/src/builder/i18n/de.ts +3453 -0
  34. package/src/builder/i18n/en.ts +3453 -0
  35. package/src/builder/i18n/es.ts +3453 -0
  36. package/src/builder/i18n/fr.ts +3453 -0
  37. package/src/builder/i18n/it.ts +3453 -0
  38. package/src/builder/i18n/ja.ts +3453 -0
  39. package/src/builder/i18n/ko.ts +3453 -0
  40. package/src/builder/i18n/nl.ts +3453 -0
  41. package/src/builder/i18n/pt.ts +3453 -0
  42. package/src/builder/i18n/zh-cn.ts +3453 -0
  43. package/src/builder/i18n/zh-tw.ts +3453 -0
  44. package/src/builder/i18n.ts +17 -3
  45. package/src/builder/json-sources.ts +63 -62
  46. package/src/builder/layouts.ts +12 -11
  47. package/src/builder/locks.ts +29 -28
  48. package/src/builder/login-page.ts +26 -25
  49. package/src/builder/mail.ts +148 -147
  50. package/src/builder/newapp.ts +15 -12
  51. package/src/builder/plugins.ts +47 -44
  52. package/src/builder/process-jobs.ts +11 -10
  53. package/src/builder/pwa.ts +22 -21
  54. package/src/builder/remote-databases.ts +55 -56
  55. package/src/builder/report-settings.ts +1 -1
  56. package/src/builder/reporter.ts +43 -42
  57. package/src/builder/rest-catalogs.ts +87 -86
  58. package/src/builder/rest.ts +9 -8
  59. package/src/builder/routes.ts +27 -27
  60. package/src/builder/search.ts +14 -13
  61. package/src/builder/static-files.ts +31 -32
  62. package/src/builder/subscriptions.ts +47 -45
  63. package/src/builder/supporting.ts +23 -18
  64. package/src/builder/team.ts +179 -179
  65. package/src/builder/telemetry.ts +59 -62
  66. package/src/builder/templates.ts +66 -65
  67. package/src/builder/themeroller.ts +60 -63
  68. package/src/builder/ui-defaults.ts +21 -20
  69. package/src/builder/ui.ts +2 -1
  70. package/src/builder/upgrade.ts +25 -24
  71. package/src/builder/utilities.ts +48 -47
  72. package/src/builder/vector-providers.ts +67 -66
  73. package/src/builder/websources.ts +43 -42
  74. package/src/builder/wizards.ts +95 -89
  75. package/src/builder/workflow-migrate.ts +30 -31
  76. package/src/builder/workflows.ts +40 -39
  77. package/src/builder/workingcopies.ts +41 -49
  78. package/src/builder/workspace-backups.ts +21 -20
  79. package/src/builder/workspace-mail.ts +17 -16
  80. package/src/builder/workspace-resources.ts +104 -112
  81. package/src/builder/workspace-transfer.ts +34 -26
@@ -7,9 +7,10 @@ import { html } from '../html.ts';
7
7
  import { icon } from '../icons.ts';
8
8
  import { readMultipart } from '../runtime/files.ts';
9
9
  import { clientIp, logActivity, type Session } from '../session.ts';
10
- import { describeWorkspaceFile, exportWorkspace, importWorkspace, PART_LABELS, PARTS, type Part } from '../workspace-export.ts';
10
+ import { describeWorkspaceFile, exportWorkspace, importWorkspace, PARTS, type Part } from '../workspace-export.ts';
11
11
  import { back, BASE, csrf, developer, flash, region, send, shell, type Body, type Req } from './ui.ts';
12
12
  import { currentWorkspace, workspaceAdmin } from './workspaces.ts';
13
+ import { scopedTx as tx, type BuilderKey } from './i18n.ts';
13
14
 
14
15
  // Workspace utilities → Export / import (APEX: Workspace Utilities → Export):
15
16
  // the current workspace's components as one file, and such a file imported
@@ -19,7 +20,14 @@ import { currentWorkspace, workspaceAdmin } from './workspaces.ts';
19
20
 
20
21
  const RESULT = '__WS_IMPORT_RESULT';
21
22
  const page = `${BASE}/workspace-export`;
22
- const crumbs: [string, string?][] = [['App Builder', BASE], ['Workspace utilities', `${BASE}/utilities`], ['Export and import']];
23
+ const crumbs = (): [string, string?][] => [[tx('rail.app_builder'), BASE], [tx('wu.title'), `${BASE}/utilities`], [tx('wu.export')]];
24
+ /** The kinds of component in the developer's language (PART_LABELS in English: src/workspace-export.ts). */
25
+ const PART_KEYS: Record<Part, BuilderKey> = {
26
+ settings: 'wtr.part.settings', developers: 'wtr.part.developers', app_groups: 'wtr.part.app_groups', extension_links: 'wtr.part.extension_links',
27
+ rest_catalogs: 'wtr.part.rest_catalogs', sql_scripts: 'wtr.part.sql_scripts', blueprints: 'wtr.part.blueprints', data_generators: 'wtr.part.data_generators',
28
+ datasets: 'wtr.part.datasets', team: 'wtr.part.team', workspace_files: 'wtr.part.workspace_files', remote_servers: 'wtr.part.remote_servers',
29
+ web_credentials: 'wtr.part.web_credentials',
30
+ };
23
31
  const MAX_MB = 20;
24
32
 
25
33
  let version: string | null | undefined;
@@ -41,16 +49,16 @@ const chosen = (b: Body | undefined): Part[] => {
41
49
 
42
50
  async function adminOr403(s: Session, reply: FastifyReply) {
43
51
  if (s.workspace && (await workspaceAdmin(s.username, s.workspace.id))) return true;
44
- await send(reply.code(403), s, shell(s, 'Export and import', crumbs, html`<h1 class="u-mb1">Export and import</h1>
45
- <div class="alert alert-error" role="alert">Only administrators and the workspace's administrators export or import a workspace's components.</div>`));
52
+ await send(reply.code(403), s, shell(s, tx('wu.export'), crumbs(), html`<h1 class="u-mb1">${tx('wu.export')}</h1>
53
+ <div class="alert alert-error" role="alert">${tx('wtr.admins_only')}</div>`));
46
54
  return false;
47
55
  }
48
56
 
49
57
  const partBoxes = (counts?: Partial<Record<Part, number>>) =>
50
- html`<fieldset class="field" data-wide><legend class="label">Components</legend>${PARTS.map((p) =>
58
+ html`<fieldset class="field" data-wide><legend class="label">${tx('wtr.components')}</legend>${PARTS.map((p) =>
51
59
  counts && counts[p] === undefined
52
60
  ? ''
53
- : html`<label class="check"><input type="checkbox" name="part" value="${p}" checked> ${PART_LABELS[p]}${counts ? html` <span class="muted">(${p === 'settings' ? 'in the file' : p === 'team' ? `${counts[p]} issue(s)` : counts[p]})</span>` : ''}</label>`)}</fieldset>`;
61
+ : html`<label class="check"><input type="checkbox" name="part" value="${p}" checked> ${tx(PART_KEYS[p])}${counts ? html` <span class="muted">(${p === 'settings' ? tx('wtr.in_file') : p === 'team' ? tx('wtr.issues', { n: counts[p] }) : counts[p]})</span>` : ''}</label>`)}</fieldset>`;
54
62
 
55
63
  export async function workspaceTransferRoutes(app: FastifyInstance) {
56
64
  app.get(page, async (req: Req, reply) => {
@@ -59,22 +67,22 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
59
67
  const result = s.state[RESULT] ? (JSON.parse(s.state[RESULT]!) as string[]) : null;
60
68
  delete s.state[RESULT];
61
69
  const name = s.workspace!.name;
62
- const main = html`<div class="title-row"><h1>Export and import</h1></div>
63
- <p class="muted u-mt0">The components of the workspace ${name} as one file: to copy them to another workspace or installation, or to keep them with your applications' files. Applications are exported on their own (App Builder → an application → Export).</p>
64
- ${result ? html`<div class="alert alert-success" role="status"><b>Imported into ${name}:</b><ul>${result.map((l) => html`<li>${l}</li>`)}</ul></div>` : ''}
65
- ${region('Export', html`<form method="post" action="${page}">${csrf(s)}
70
+ const main = html`<div class="title-row"><h1>${tx('wu.export')}</h1></div>
71
+ <p class="muted u-mt0">${tx('wtr.intro', { name })}</p>
72
+ ${result ? html`<div class="alert alert-success" role="status"><b>${tx('wtr.imported_into', { name })}</b><ul>${result.map((l) => html`<li>${l}</li>`)}</ul></div>` : ''}
73
+ ${region(tx('wtr.export'), html`<form method="post" action="${page}">${csrf(s)}
66
74
  ${partBoxes()}
67
- <p class="muted">Not in the file: the workspace's name, its isolation and database roles, passwords and secrets, script runs and other history.</p>
68
- <div class="buttons"><button class="btn btn-hot">${icon('download')} Export</button></div></form>`)}
69
- ${region('Import', html`<form method="post" action="${BASE}/workspace-import" enctype="multipart/form-data">${csrf(s)}
70
- <p class="muted u-mt0">A file exported from a workspace, imported into <b>${name}</b>. Nothing is deleted; developers are added only when this installation has a developer of that name.</p>
75
+ <p class="muted">${tx('wtr.not_in_file')}</p>
76
+ <div class="buttons"><button class="btn btn-hot">${icon('download')} ${tx('wtr.export')}</button></div></form>`)}
77
+ ${region(tx('wtr.import'), html`<form method="post" action="${BASE}/workspace-import" enctype="multipart/form-data">${csrf(s)}
78
+ <p class="muted u-mt0">${tx.rich('wtr.import_intro', { name: html`<b>${name}</b>` })}</p>
71
79
  <div class="form-grid">
72
- <div class="field" data-wide><label class="label" for="f_file">Workspace file</label><input id="f_file" type="file" name="file" accept=".json,application/json" required></div>
80
+ <div class="field" data-wide><label class="label" for="f_file">${tx('wtr.file')}</label><input id="f_file" type="file" name="file" accept=".json,application/json" required></div>
73
81
  ${partBoxes()}
74
- <div class="field" data-wide><label class="check"><input type="checkbox" name="replace" value="true"> Replace components with the same name (otherwise this workspace's own are kept)</label></div>
82
+ <div class="field" data-wide><label class="check"><input type="checkbox" name="replace" value="true"> ${tx('wtr.replace')}</label></div>
75
83
  </div>
76
- <div class="buttons"><button class="btn">${icon('upload')} Import</button></div></form>`)}`;
77
- return send(reply, s, shell(s, 'Export and import', crumbs, main));
84
+ <div class="buttons"><button class="btn">${icon('upload')} ${tx('wtr.import')}</button></div></form>`)}`;
85
+ return send(reply, s, shell(s, tx('wu.export'), crumbs(), main));
78
86
  });
79
87
 
80
88
  app.post(page, async (req: Req, reply) => {
@@ -82,7 +90,7 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
82
90
  if (!s || !(await adminOr403(s, reply))) return;
83
91
  const parts = chosen(req.body);
84
92
  if (!parts.length) {
85
- flash(s, 'Choose at least one kind of component.', 'error');
93
+ flash(s, tx('wtr.choose_part'), 'error');
86
94
  return back(reply, s, page);
87
95
  }
88
96
  const doc = await exportWorkspace(currentWorkspace(s), parts, { version: pgkilnVersion() ?? undefined, by: s.username });
@@ -104,23 +112,23 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
104
112
  const s = await developer(req, reply);
105
113
  if (!s || !(await adminOr403(s, reply))) return;
106
114
  try {
107
- if (tooBig) throw new Error(`The file is larger than ${MAX_MB} MB.`);
108
- if (!text.trim()) throw new Error('Choose a workspace file.');
115
+ if (tooBig) throw new Error(tx('wtr.too_big', { n: MAX_MB }));
116
+ if (!text.trim()) throw new Error(tx('wtr.choose_file'));
109
117
  let doc: unknown;
110
118
  try {
111
119
  doc = JSON.parse(text);
112
120
  } catch {
113
- throw new Error('The file is not valid JSON.');
121
+ throw new Error(tx('wtr.not_json'));
114
122
  }
115
123
  describeWorkspaceFile(doc);
116
124
  const parts = chosen(req.body);
117
- if (!parts.length) throw new Error('Choose at least one kind of component.');
125
+ if (!parts.length) throw new Error(tx('wtr.choose_part'));
118
126
  const ws = currentWorkspace(s);
119
127
  const result = await owner.tx((c) => importWorkspace(c, ws, doc, parts, { replace: req.body?.replace === 'true', by: s.username }));
120
128
  await logActivity({ appId: null, username: s.username, event: 'workspace', ip: clientIp(req), detail: `imported into workspace ${ws}: ${result.added} added, ${result.replaced} replaced, ${result.skipped} kept` });
121
129
  s.state[RESULT] = JSON.stringify(result.lines);
122
130
  } catch (e) {
123
- flash(s, `Import failed, nothing was changed: ${friendly(e)}`, 'error');
131
+ flash(s, tx('wtr.failed', { reason: friendly(e) }), 'error');
124
132
  }
125
133
  return back(reply, s, page);
126
134
  });
@@ -129,8 +137,8 @@ export async function workspaceTransferRoutes(app: FastifyInstance) {
129
137
  /** A database refusal in words (the transaction rolled back). */
130
138
  function friendly(e: unknown) {
131
139
  const err = e as { message: string; constraint?: string; table?: string; code?: string };
132
- if (err.code === '23514' || err.code === '23502' || err.code === '22P02') return `the file has a value this installation refuses (${err.table ?? ''}${err.constraint ? `: ${err.constraint}` : ''}).`;
133
- if (err.code === '23505') return `the file has two components with the same name (${err.constraint ?? err.table ?? ''}).`;
140
+ if (err.code === '23514' || err.code === '23502' || err.code === '22P02') return tx('wtr.refused_value', { detail: `${err.table ?? ''}${err.constraint ? `: ${err.constraint}` : ''}` });
141
+ if (err.code === '23505') return tx('wtr.duplicate', { detail: err.constraint ?? err.table ?? '' });
134
142
  return err.message;
135
143
  }
136
144