shutro-erp 0.2.1 → 0.3.1

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 (273) hide show
  1. package/README.md +74 -67
  2. package/dist/account_portal/.kosh/bindings.d.ts +22 -0
  3. package/dist/account_portal/.kosh/bindings.js +15 -0
  4. package/dist/account_portal/.kosh/schema.d.ts +1380 -0
  5. package/dist/account_portal/index.d.ts +3 -0
  6. package/dist/account_portal/index.js +73 -0
  7. package/dist/account_portal/module.d.ts +5983 -0
  8. package/dist/account_portal/module.js +18 -0
  9. package/dist/accounting/.kosh/bindings.js +1 -1
  10. package/dist/accounting/.kosh/schema.d.ts +172 -9
  11. package/dist/accounting/chart.d.ts +40 -8
  12. package/dist/accounting/chart.js +124 -30
  13. package/dist/accounting/closing.d.ts +6 -1
  14. package/dist/accounting/closing.js +5 -1
  15. package/dist/accounting/demo.js +6 -5
  16. package/dist/accounting/index.d.ts +3 -3
  17. package/dist/accounting/index.js +200 -23
  18. package/dist/accounting/module.d.ts +3326 -1086
  19. package/dist/accounting/module.js +408 -66
  20. package/dist/accounting/payments.d.ts +25 -0
  21. package/dist/accounting/payments.js +96 -4
  22. package/dist/accounting/web/RegisterPayment.d.ts +3 -2
  23. package/dist/accounting/web/RegisterPayment.js +39 -6
  24. package/dist/accounting/web/SettingsPage.js +5 -1
  25. package/dist/accounting/web/messages.js +10 -0
  26. package/dist/assistant/routes.js +1 -1
  27. package/dist/automation/engine.js +1 -1
  28. package/dist/automation/web/messages.js +2 -2
  29. package/dist/cli/apps.d.ts +1 -1
  30. package/dist/cli/apps.js +14 -14
  31. package/dist/cli/bin.js +3 -0
  32. package/dist/cli/index.js +11 -11
  33. package/dist/cli/init.d.ts +1 -1
  34. package/dist/cli/init.js +224 -78
  35. package/dist/cli/launch.d.ts +16 -0
  36. package/dist/cli/launch.js +138 -0
  37. package/dist/cli/project.d.ts +7 -7
  38. package/dist/cli/project.js +25 -25
  39. package/dist/cli/prompt.d.ts +35 -0
  40. package/dist/cli/prompt.js +48 -2
  41. package/dist/cli/templates.d.ts +14 -6
  42. package/dist/cli/templates.js +61 -34
  43. package/dist/cli/tui.d.ts +7 -0
  44. package/dist/cli/tui.js +254 -0
  45. package/dist/cli/ui.d.ts +21 -8
  46. package/dist/cli/ui.js +65 -19
  47. package/dist/contacts/.kosh/bindings.js +1 -1
  48. package/dist/contacts/.kosh/schema.d.ts +33 -1
  49. package/dist/contacts/index.js +24 -1
  50. package/dist/contacts/module.d.ts +42 -0
  51. package/dist/contacts/module.js +3 -1
  52. package/dist/import/convert.d.ts +55 -0
  53. package/dist/import/convert.js +224 -0
  54. package/dist/import/csv.d.ts +28 -0
  55. package/dist/import/csv.js +115 -0
  56. package/dist/import/index.d.ts +33 -0
  57. package/dist/import/index.js +60 -0
  58. package/dist/import/mapping.d.ts +15 -0
  59. package/dist/import/mapping.js +33 -0
  60. package/dist/import/match.d.ts +24 -0
  61. package/dist/import/match.js +60 -0
  62. package/dist/import/models.d.ts +2 -0
  63. package/dist/import/models.js +102 -0
  64. package/dist/import/routes.d.ts +13 -0
  65. package/dist/import/routes.js +392 -0
  66. package/dist/import/run.d.ts +131 -0
  67. package/dist/import/run.js +568 -0
  68. package/dist/import/service.d.ts +153 -0
  69. package/dist/import/service.js +367 -0
  70. package/dist/import/web/ImportButton.d.ts +7 -0
  71. package/dist/import/web/ImportButton.js +31 -0
  72. package/dist/import/web/ImportWizard.d.ts +75 -0
  73. package/dist/import/web/ImportWizard.js +217 -0
  74. package/dist/import/web/index.d.ts +13 -0
  75. package/dist/import/web/index.js +18 -0
  76. package/dist/import/web/messages.d.ts +2 -0
  77. package/dist/import/web/messages.js +139 -0
  78. package/dist/import/worker.d.ts +3 -0
  79. package/dist/import/worker.js +52 -0
  80. package/dist/import/xlsx.d.ts +20 -0
  81. package/dist/import/xlsx.js +192 -0
  82. package/dist/l10n_bd/.kosh/bindings.d.ts +22 -0
  83. package/dist/l10n_bd/.kosh/bindings.js +15 -0
  84. package/dist/l10n_bd/.kosh/schema.d.ts +1311 -0
  85. package/dist/l10n_bd/documents.d.ts +8 -0
  86. package/dist/l10n_bd/documents.js +183 -0
  87. package/dist/l10n_bd/index.d.ts +8 -0
  88. package/dist/l10n_bd/index.js +156 -0
  89. package/dist/l10n_bd/module.d.ts +5705 -0
  90. package/dist/l10n_bd/module.js +163 -0
  91. package/dist/l10n_bd/mushak.d.ts +67 -0
  92. package/dist/l10n_bd/mushak.js +139 -0
  93. package/dist/l10n_bd/routes.d.ts +3 -0
  94. package/dist/l10n_bd/routes.js +205 -0
  95. package/dist/l10n_bd/web/MushakPage.d.ts +7 -0
  96. package/dist/l10n_bd/web/MushakPage.js +53 -0
  97. package/dist/l10n_bd/web/index.d.ts +13 -0
  98. package/dist/l10n_bd/web/index.js +18 -0
  99. package/dist/l10n_bd/web/messages.d.ts +2 -0
  100. package/dist/l10n_bd/web/messages.js +101 -0
  101. package/dist/l10n_in/.kosh/bindings.d.ts +22 -0
  102. package/dist/l10n_in/.kosh/bindings.js +15 -0
  103. package/dist/l10n_in/.kosh/schema.d.ts +1373 -0
  104. package/dist/l10n_in/documents.d.ts +8 -0
  105. package/dist/l10n_in/documents.js +114 -0
  106. package/dist/l10n_in/einvoice.d.ts +253 -0
  107. package/dist/l10n_in/einvoice.js +485 -0
  108. package/dist/l10n_in/gstr.d.ts +109 -0
  109. package/dist/l10n_in/gstr.js +430 -0
  110. package/dist/l10n_in/index.d.ts +20 -0
  111. package/dist/l10n_in/index.js +232 -0
  112. package/dist/l10n_in/module.d.ts +7823 -0
  113. package/dist/l10n_in/module.js +427 -0
  114. package/dist/l10n_in/routes-einvoice.d.ts +15 -0
  115. package/dist/l10n_in/routes-einvoice.js +124 -0
  116. package/dist/l10n_in/routes.d.ts +17 -0
  117. package/dist/l10n_in/routes.js +282 -0
  118. package/dist/l10n_in/web/EInvoicePanel.d.ts +8 -0
  119. package/dist/l10n_in/web/EInvoicePanel.js +63 -0
  120. package/dist/l10n_in/web/GstPage.d.ts +7 -0
  121. package/dist/l10n_in/web/GstPage.js +57 -0
  122. package/dist/l10n_in/web/GstSettings.d.ts +10 -0
  123. package/dist/l10n_in/web/GstSettings.js +36 -0
  124. package/dist/l10n_in/web/index.d.ts +15 -0
  125. package/dist/l10n_in/web/index.js +22 -0
  126. package/dist/l10n_in/web/messages.d.ts +2 -0
  127. package/dist/l10n_in/web/messages.js +166 -0
  128. package/dist/mail/index.js +2 -2
  129. package/dist/modules-sdk/demo.d.ts +2 -0
  130. package/dist/modules-sdk/demo.js +7 -3
  131. package/dist/modules-sdk/index.d.ts +2 -2
  132. package/dist/modules-sdk/index.js +1 -1
  133. package/dist/modules-sdk/loader.js +2 -2
  134. package/dist/modules-sdk/manifest.d.ts +132 -0
  135. package/dist/modules-sdk/manifest.js +22 -0
  136. package/dist/notify/inbox.js +21 -0
  137. package/dist/notify/messages.js +7 -3
  138. package/dist/notify/models.d.ts +1 -1
  139. package/dist/notify/models.js +2 -0
  140. package/dist/notify/routes.js +1 -1
  141. package/dist/notify/web/messages.js +5 -1
  142. package/dist/portal/.kosh/bindings.d.ts +22 -0
  143. package/dist/portal/.kosh/bindings.js +15 -0
  144. package/dist/portal/.kosh/schema.d.ts +376 -0
  145. package/dist/portal/announce.d.ts +16 -0
  146. package/dist/portal/announce.js +117 -0
  147. package/dist/portal/documents.d.ts +17 -0
  148. package/dist/portal/documents.js +61 -0
  149. package/dist/portal/index.d.ts +20 -0
  150. package/dist/portal/index.js +65 -0
  151. package/dist/portal/module.d.ts +1405 -0
  152. package/dist/portal/module.js +84 -0
  153. package/dist/portal/routes-documents.d.ts +2 -0
  154. package/dist/portal/routes-documents.js +391 -0
  155. package/dist/portal/routes.d.ts +13 -0
  156. package/dist/portal/routes.js +313 -0
  157. package/dist/portal/rules.d.ts +11 -0
  158. package/dist/portal/rules.js +25 -0
  159. package/dist/portal/web/PortalAccess.d.ts +8 -0
  160. package/dist/portal/web/PortalAccess.js +66 -0
  161. package/dist/portal/web/index.d.ts +12 -0
  162. package/dist/portal/web/index.js +17 -0
  163. package/dist/portal/web/messages.d.ts +2 -0
  164. package/dist/portal/web/messages.js +33 -0
  165. package/dist/product/.kosh/bindings.js +1 -1
  166. package/dist/product/.kosh/schema.d.ts +42 -3
  167. package/dist/product/demo.js +6 -5
  168. package/dist/product/index.d.ts +1 -0
  169. package/dist/product/index.js +17 -4
  170. package/dist/product/module.d.ts +346 -14
  171. package/dist/product/module.js +48 -18
  172. package/dist/product/taxes.d.ts +59 -0
  173. package/dist/product/taxes.js +61 -0
  174. package/dist/project/.kosh/bindings.js +1 -1
  175. package/dist/project/.kosh/schema.d.ts +33 -1
  176. package/dist/project/module.d.ts +20 -0
  177. package/dist/project_portal/.kosh/bindings.d.ts +22 -0
  178. package/dist/project_portal/.kosh/bindings.js +15 -0
  179. package/dist/project_portal/.kosh/schema.d.ts +701 -0
  180. package/dist/project_portal/index.d.ts +9 -0
  181. package/dist/project_portal/index.js +42 -0
  182. package/dist/project_portal/module.d.ts +2142 -0
  183. package/dist/project_portal/module.js +22 -0
  184. package/dist/purchase/.kosh/bindings.js +1 -1
  185. package/dist/purchase/.kosh/schema.d.ts +45 -4
  186. package/dist/purchase/demo.js +3 -3
  187. package/dist/purchase/index.js +1 -1
  188. package/dist/purchase/module.d.ts +232 -8
  189. package/dist/purchase/module.js +51 -9
  190. package/dist/purchase_account/.kosh/bindings.js +1 -1
  191. package/dist/purchase_account/.kosh/schema.d.ts +174 -10
  192. package/dist/purchase_account/module.d.ts +2008 -641
  193. package/dist/purchase_account/module.js +2 -2
  194. package/dist/purchase_portal/.kosh/bindings.d.ts +22 -0
  195. package/dist/purchase_portal/.kosh/bindings.js +15 -0
  196. package/dist/purchase_portal/.kosh/schema.d.ts +852 -0
  197. package/dist/purchase_portal/index.d.ts +9 -0
  198. package/dist/purchase_portal/index.js +53 -0
  199. package/dist/purchase_portal/module.d.ts +2758 -0
  200. package/dist/purchase_portal/module.js +18 -0
  201. package/dist/reporting/documents.js +20 -3
  202. package/dist/reporting/index.d.ts +1 -0
  203. package/dist/reporting/index.js +1 -0
  204. package/dist/reporting/routes-documents.js +9 -2
  205. package/dist/reporting/words.d.ts +11 -0
  206. package/dist/reporting/words.js +229 -0
  207. package/dist/sale_account/.kosh/bindings.js +1 -1
  208. package/dist/sale_account/.kosh/schema.d.ts +174 -10
  209. package/dist/sale_account/module.d.ts +2008 -641
  210. package/dist/sale_account/module.js +3 -3
  211. package/dist/sale_portal/.kosh/bindings.d.ts +22 -0
  212. package/dist/sale_portal/.kosh/bindings.js +15 -0
  213. package/dist/sale_portal/.kosh/schema.d.ts +766 -0
  214. package/dist/sale_portal/index.d.ts +9 -0
  215. package/dist/sale_portal/index.js +71 -0
  216. package/dist/sale_portal/module.d.ts +2718 -0
  217. package/dist/sale_portal/module.js +19 -0
  218. package/dist/sale_project/.kosh/bindings.js +1 -1
  219. package/dist/sale_project/.kosh/schema.d.ts +45 -4
  220. package/dist/sale_project/module.d.ts +390 -10
  221. package/dist/sales/.kosh/bindings.js +1 -1
  222. package/dist/sales/.kosh/schema.d.ts +45 -4
  223. package/dist/sales/demo.js +3 -3
  224. package/dist/sales/index.js +67 -1
  225. package/dist/sales/module.d.ts +232 -8
  226. package/dist/sales/module.js +51 -12
  227. package/dist/server/api/client.js +1 -1
  228. package/dist/server/api/openapi.js +17 -1
  229. package/dist/server/apps.js +5 -5
  230. package/dist/server/auth/api-keys.d.ts +13 -0
  231. package/dist/server/auth/api-keys.js +65 -0
  232. package/dist/server/auth/base.js +26 -0
  233. package/dist/server/auth/crypto.js +1 -1
  234. package/dist/server/auth/scopes.d.ts +12 -0
  235. package/dist/server/auth/scopes.js +51 -0
  236. package/dist/server/auth/service.d.ts +59 -1
  237. package/dist/server/auth/service.js +236 -6
  238. package/dist/server/demo.d.ts +1 -1
  239. package/dist/server/demo.js +1 -1
  240. package/dist/server/env-names.d.ts +2 -0
  241. package/dist/server/env-names.js +16 -0
  242. package/dist/server/host.js +5 -1
  243. package/dist/server/identity.d.ts +7 -0
  244. package/dist/server/index.d.ts +2 -1
  245. package/dist/server/index.js +2 -1
  246. package/dist/server/registry.js +1 -1
  247. package/dist/server/server.d.ts +5 -0
  248. package/dist/server/server.js +71 -6
  249. package/dist/settings/routes.js +1 -1
  250. package/dist/settings/web/ApiKeysPage.d.ts +8 -0
  251. package/dist/settings/web/ApiKeysPage.js +70 -0
  252. package/dist/settings/web/index.d.ts +1 -0
  253. package/dist/settings/web/index.js +4 -0
  254. package/dist/settings/web/messages.js +62 -0
  255. package/dist/storage/routes.js +1 -1
  256. package/dist/studio/build.js +1 -1
  257. package/dist/studio/generate.js +4 -4
  258. package/dist/studio/module.js +5 -5
  259. package/dist/studio/web/messages.js +2 -2
  260. package/dist/ui/api.d.ts +2 -0
  261. package/dist/ui/app.js +27 -4
  262. package/dist/ui/context.d.ts +3 -1
  263. package/dist/ui/context.js +2 -2
  264. package/dist/ui/i18n/messages.js +2 -2
  265. package/dist/ui/index.d.ts +1 -1
  266. package/dist/ui/index.js +1 -1
  267. package/dist/ui/lib/format.d.ts +2 -0
  268. package/dist/ui/lib/format.js +3 -1
  269. package/dist/ui/pwa/worker.js +2 -2
  270. package/dist/ui/registry.d.ts +15 -0
  271. package/dist/ui/registry.js +20 -0
  272. package/dist/ui/views/FormView.js +19 -6
  273. package/package.json +52 -4
@@ -0,0 +1,138 @@
1
+ /**
2
+ * After `shutro-erp init`: the project's dependencies installed with the package manager that ran the installer, the API
3
+ * and the back-office started, each waited for until it answers, and the back-office opened in the default browser.
4
+ * Both keep running in this terminal until Ctrl+C, which stops them together. Their output is kept, and shown when
5
+ * one of them fails to start.
6
+ */
7
+ import { spawn, spawnSync } from 'node:child_process';
8
+ /** The package manager that ran the CLI (`npx`, `pnpm dlx`, `yarn dlx`, `bunx`), from its user agent; npm otherwise. */
9
+ export function packageManager(env = process.env) {
10
+ const agent = env['npm_config_user_agent'] ?? '';
11
+ for (const pm of ['pnpm', 'yarn', 'bun'])
12
+ if (agent.startsWith(`${pm}/`))
13
+ return pm;
14
+ return 'npm';
15
+ }
16
+ /** The command that opens `url` in the default browser on `platform`. */
17
+ export function browserCommand(url, platform = process.platform) {
18
+ if (platform === 'win32')
19
+ return ['cmd', ['/c', 'start', '""', url]];
20
+ if (platform === 'darwin')
21
+ return ['open', [url]];
22
+ return ['xdg-open', [url]];
23
+ }
24
+ const windows = process.platform === 'win32';
25
+ /** A child process running `pm args` in `cwd`, its output kept (the last 200 lines). */
26
+ function start(pm, args, cwd) {
27
+ const child = spawn(pm, args, {
28
+ cwd,
29
+ env: { ...process.env, FORCE_COLOR: '0' },
30
+ stdio: ['ignore', 'pipe', 'pipe'],
31
+ // npm, pnpm… are .cmd scripts on Windows; elsewhere a process group, stopped as a whole.
32
+ shell: windows,
33
+ detached: !windows,
34
+ });
35
+ const lines = [];
36
+ const keep = (chunk) => {
37
+ lines.push(...chunk
38
+ .toString()
39
+ .split(/\r?\n/)
40
+ .filter((l) => l.trim() !== ''));
41
+ lines.splice(0, Math.max(0, lines.length - 200));
42
+ };
43
+ child.stdout?.on('data', keep);
44
+ child.stderr?.on('data', keep);
45
+ const exited = new Promise((done) => child.on('close', (code) => done(code ?? 1)));
46
+ return { child, lines, exited };
47
+ }
48
+ /** Stops a started process and what it started. */
49
+ function stop(child) {
50
+ if (child.pid === undefined || child.exitCode !== null)
51
+ return;
52
+ try {
53
+ if (windows)
54
+ spawnSync('taskkill', ['/pid', String(child.pid), '/T', '/F'], { stdio: 'ignore' });
55
+ else
56
+ process.kill(-child.pid, 'SIGTERM');
57
+ }
58
+ catch {
59
+ // already gone
60
+ }
61
+ }
62
+ /** Resolves when `url` answers (any HTTP status below 500); rejects when `exited` settles first or on timeout. */
63
+ async function answers(url, exited, seconds) {
64
+ let gone = null;
65
+ void exited.then((code) => {
66
+ gone = code;
67
+ });
68
+ for (let i = 0; i < seconds * 2; i++) {
69
+ if (gone !== null)
70
+ throw new Error(`it stopped (exit code ${gone})`);
71
+ try {
72
+ const res = await fetch(url, { signal: AbortSignal.timeout(1000) });
73
+ if (res.status < 500)
74
+ return;
75
+ }
76
+ catch {
77
+ // not yet
78
+ }
79
+ await new Promise((r) => setTimeout(r, 500));
80
+ }
81
+ throw new Error(`no answer at ${url} within ${seconds}s`);
82
+ }
83
+ /**
84
+ * Installs, starts and opens the installation; returns when the user stops it (Ctrl+C) or a server stops by itself.
85
+ */
86
+ export async function launch(o, ui) {
87
+ const pm = packageManager();
88
+ await ui.task(`Installing the dependencies (${pm} install)`, async (task) => {
89
+ task.update('the first run downloads them');
90
+ const run = start(pm, ['install'], o.dir);
91
+ const code = await run.exited;
92
+ if (code !== 0)
93
+ throw new Error(`${pm} install failed:\n${run.lines.slice(-15).join('\n')}`);
94
+ });
95
+ const api = start(pm, ['run', 'start'], o.dir);
96
+ const web = start(pm, ['run', 'web'], o.dir);
97
+ const stopAll = () => {
98
+ stop(api.child);
99
+ stop(web.child);
100
+ };
101
+ process.once('exit', stopAll);
102
+ const failed = (name, run) => (error) => {
103
+ stopAll();
104
+ const tail = run.lines.slice(-15).join('\n');
105
+ throw new Error(`The ${name} did not start: ${error instanceof Error ? error.message : String(error)}${tail === '' ? '' : `\n${tail}`}`);
106
+ };
107
+ const apiUrl = `http://localhost:${o.port}`;
108
+ const webUrl = `http://localhost:${o.webPort}`;
109
+ await ui
110
+ .task(`Starting the API on ${apiUrl}`, () => answers(`${apiUrl}/health/ready`, api.exited, 90))
111
+ .catch(failed('API', api));
112
+ await ui
113
+ .task(`Starting the back-office on ${webUrl}`, () => answers(webUrl, web.exited, 90))
114
+ .catch(failed('back-office', web));
115
+ const [command, args] = browserCommand(webUrl);
116
+ const opened = spawn(command, args, { stdio: 'ignore', detached: true, shell: false });
117
+ opened.on('error', () => ui.info(`Open ${webUrl} in your browser.`));
118
+ opened.unref();
119
+ ui.ok(`Opened ${ui.style('cyan', webUrl)} in your browser · sign in as "${o.login}"`);
120
+ ui.outro(`${ui.style('green', 'Shutro ERP is running')} ${ui.style('gray', `back-office ${webUrl} · API ${apiUrl} · Ctrl+C stops both`)}`);
121
+ // Until Ctrl+C, or until one of them stops by itself (its last lines are shown).
122
+ const which = await Promise.race([
123
+ new Promise((done) => {
124
+ process.once('SIGINT', () => done('stopped'));
125
+ process.once('SIGTERM', () => done('stopped'));
126
+ }),
127
+ api.exited.then(() => 'api'),
128
+ web.exited.then(() => 'web'),
129
+ ]);
130
+ stopAll();
131
+ if (which === 'stopped') {
132
+ ui.write(`\n ${ui.style('gray', `Stopped. Start again with: ${pm} start and ${pm} run web`)}\n`);
133
+ return;
134
+ }
135
+ const run = which === 'api' ? api : web;
136
+ ui.fail(`The ${which === 'api' ? 'API' : 'back-office'} stopped:`);
137
+ ui.write(`${run.lines.slice(-15).join('\n')}\n`);
138
+ }
@@ -1,13 +1,13 @@
1
1
  import type { KoshModule } from '../modules-sdk/index.js';
2
2
  import { type KoshServer } from '../server/index.js';
3
- /** The Kosh modules a file or package exports: `modules`, the default export, or every module export. */
3
+ /** The Shutro ERP modules a file or package exports: `modules`, the default export, or every module export. */
4
4
  export declare function modulesOf(specifier: string, cwd: string): Promise<KoshModule[]>;
5
5
  /** Opens the installation in `cwd`: `.env`, the modules, a server (not listening). */
6
6
  export declare function openProject(cwd: string, modulesFile: string, options?: {
7
7
  /** `schema`: installed and disabled modules (what migrations keep); default: the installed ones (what is served). */
8
8
  readonly set?: 'served' | 'schema';
9
9
  }): Promise<KoshServer>;
10
- /** What seed data is loaded with: a server's, or an app change's transaction (`kosh apps`). */
10
+ /** What seed data is loaded with: a server's, or an app change's transaction (`shutro-erp apps`). */
11
11
  export interface SeedContext {
12
12
  readonly shutro: KoshServer['shutro'];
13
13
  readonly driver: Pick<KoshServer['driver'], 'query'>;
@@ -24,13 +24,13 @@ export declare function migrateProject(server: KoshServer, dir: string, write: (
24
24
  allowDestructive?: boolean;
25
25
  seed?: boolean;
26
26
  }): Promise<number>;
27
- /** `kosh migrate [status] …` */
27
+ /** `shutro-erp migrate [status] …` */
28
28
  export declare function runMigrate(argv: readonly string[], write: (t: string) => void, cwd: string): Promise<number>;
29
- /** `kosh seed [module …]` */
29
+ /** `shutro-erp seed [module …]` */
30
30
  export declare function runSeed(argv: readonly string[], write: (t: string) => void, cwd: string): Promise<number>;
31
- /** The files of a new module (`kosh module create <name>`). */
31
+ /** The files of a new module (`shutro-erp module create <name>`). */
32
32
  export declare function moduleFiles(name: string): Record<string, string>;
33
- /** `kosh module create <name>`: scaffolds src/modules/<name>/ and lists it in src/modules.ts. */
33
+ /** `shutro-erp module create <name>`: scaffolds src/modules/<name>/ and lists it in src/modules.ts. */
34
34
  export declare function runModule(argv: readonly string[], write: (t: string) => void, cwd: string): Promise<number>;
35
- /** `kosh user create <login> …` and `kosh user password <login>`. */
35
+ /** `shutro-erp user create <login> …` and `shutro-erp user password <login>`. */
36
36
  export declare function runUser(argv: readonly string[], write: (t: string) => void, cwd: string, prompter?: import('./prompt.js').Prompter): Promise<number>;
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Commands that work on an installation (a folder made by `kosh init`): `kosh migrate`, `kosh seed`,
3
- * `kosh module create`. They read `.env` (DATABASE_URL, storage, ...) and the module list in `src/modules.ts`.
2
+ * Commands that work on an installation (a folder made by `shutro-erp init`): `shutro-erp migrate`, `shutro-erp seed`,
3
+ * `shutro-erp module create`. They read `.env` (DATABASE_URL, storage, ...) and the module list in `src/modules.ts`.
4
4
  *
5
5
  * - Schema migrations are versioned SQL files in `migrations/`, planned from the modules against the live database
6
6
  * (Shutro migrate) and applied in order; each module's data migrations (`migrations` in its Shutro module) run
@@ -21,7 +21,7 @@ function isModule(value) {
21
21
  'accessLevels' in value &&
22
22
  'name' in value);
23
23
  }
24
- /** The Kosh modules a file or package exports: `modules`, the default export, or every module export. */
24
+ /** The Shutro ERP modules a file or package exports: `modules`, the default export, or every module export. */
25
25
  export async function modulesOf(specifier, cwd) {
26
26
  const target = specifier.startsWith('.') ||
27
27
  specifier.startsWith('/') ||
@@ -39,7 +39,7 @@ export async function modulesOf(specifier, cwd) {
39
39
  }
40
40
  const all = Object.values(exported).filter(isModule);
41
41
  if (all.length === 0)
42
- throw new Error(`${specifier} exports no Kosh modules (defineModule from shutro-erp/modules-sdk).`);
42
+ throw new Error(`${specifier} exports no Shutro ERP modules (defineModule from shutro-erp/modules-sdk).`);
43
43
  return all;
44
44
  }
45
45
  /** Opens the installation in `cwd`: `.env`, the modules, a server (not listening). */
@@ -49,9 +49,9 @@ export async function openProject(cwd, modulesFile, options = {}) {
49
49
  process.loadEnvFile(envFile);
50
50
  const databaseUrl = process.env['DATABASE_URL'];
51
51
  if (databaseUrl === undefined || databaseUrl === '')
52
- throw new Error('DATABASE_URL is not set: run kosh init, or add it to .env.');
52
+ throw new Error('DATABASE_URL is not set: run shutro-erp init, or add it to .env.');
53
53
  if (!existsSync(resolve(cwd, modulesFile)))
54
- throw new Error(`${modulesFile} does not exist: run kosh init, or pass --modules <file>.`);
54
+ throw new Error(`${modulesFile} does not exist: run shutro-erp init, or pass --modules <file>.`);
55
55
  process.env['KOSH_SCHEDULES'] = 'off';
56
56
  // The module list is the catalogue; the database says which of them are installed (RFC 0008).
57
57
  const modules = await installedModules(databaseUrl, await modulesOf(modulesFile, cwd), options.set === 'schema' ? ['installed', 'disabled'] : ['installed']);
@@ -93,7 +93,7 @@ export async function seedWith(context, write, only) {
93
93
  : context.modules.filter((m) => only.includes(m.name));
94
94
  for (const d of await installDefaults(context.shutro, context.modules, {
95
95
  company: companyId,
96
- reason: 'kosh seed',
96
+ reason: 'shutro-erp seed',
97
97
  }))
98
98
  if (wanted.some((m) => m.name === d.module))
99
99
  write(` ${d.module.padEnd(16)} ${d.summary}
@@ -117,7 +117,7 @@ async function apply(server, dir, write, allowDestructive) {
117
117
  }
118
118
  catch (error) {
119
119
  if (error instanceof Error && error.name === 'DestructiveMigrationError') {
120
- write(`${error.message}\nReview it, then run kosh migrate --allow-destructive.\n`);
120
+ write(`${error.message}\nReview it, then run shutro-erp migrate --allow-destructive.\n`);
121
121
  return 1;
122
122
  }
123
123
  throw error;
@@ -152,7 +152,7 @@ export async function migrateProject(server, dir, write, options = {}) {
152
152
  if (options.planOnly === true)
153
153
  return 0;
154
154
  if (plan.destructive.length > 0 && options.allowDestructive !== true) {
155
- write('It drops or changes existing data: review the file, then run kosh migrate --allow-destructive.\n');
155
+ write('It drops or changes existing data: review the file, then run shutro-erp migrate --allow-destructive.\n');
156
156
  return 1;
157
157
  }
158
158
  if (plan.statements.length > 0 || pending.length === 0) {
@@ -173,11 +173,11 @@ export async function migrateProject(server, dir, write, options = {}) {
173
173
  await seed(server, write, served !== undefined && served.length > 0 ? served : undefined);
174
174
  }
175
175
  else
176
- write('No company yet: seed data waits for kosh init.\n');
176
+ write('No company yet: seed data waits for shutro-erp init.\n');
177
177
  }
178
178
  return 0;
179
179
  }
180
- /** `kosh migrate [status] …` */
180
+ /** `shutro-erp migrate [status] …` */
181
181
  export async function runMigrate(argv, write, cwd) {
182
182
  const { values, positionals } = parseArgs({
183
183
  args: [...argv],
@@ -217,7 +217,7 @@ export async function runMigrate(argv, write, cwd) {
217
217
  await server.close();
218
218
  }
219
219
  }
220
- /** `kosh seed [module …]` */
220
+ /** `shutro-erp seed [module …]` */
221
221
  export async function runSeed(argv, write, cwd) {
222
222
  const { values, positionals } = parseArgs({
223
223
  args: [...argv],
@@ -239,7 +239,7 @@ const title = (name) => name
239
239
  .split(/[-_]+/)
240
240
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
241
241
  .join(' ');
242
- /** The files of a new module (`kosh module create <name>`). */
242
+ /** The files of a new module (`shutro-erp module create <name>`). */
243
243
  export function moduleFiles(name) {
244
244
  const id = camel(name);
245
245
  const label = title(name);
@@ -265,7 +265,7 @@ export const Item = defineModel('${model}', {
265
265
  });
266
266
  `,
267
267
  'data.ts': `/**
268
- * Seed data of the ${label} module: \`kosh seed\` (and \`kosh migrate\`) create these records, update them when they
268
+ * Seed data of the ${label} module: \`shutro-erp seed\` (and \`shutro-erp migrate\`) create these records, update them when they
269
269
  * change here, and delete records removed from this list. \`noupdate\` records are created once, then left to the users.
270
270
  * Refer to other records by external id: orm.ref('res.currency', 'base.currency_bdt').
271
271
  */
@@ -277,7 +277,7 @@ export const data = [
277
277
  `,
278
278
  'migrations.ts': `/**
279
279
  * Data migrations of the ${label} module: each runs once, when an installed module is upgraded past its version
280
- * (raise \`version\` in index.ts, then \`kosh migrate\`). Schema changes need none: \`kosh migrate\` plans them.
280
+ * (raise \`version\` in index.ts, then \`shutro-erp migrate\`). Schema changes need none: \`shutro-erp migrate\` plans them.
281
281
  */
282
282
  import type { DataMigration } from 'shutro-erp/orm';
283
283
 
@@ -293,7 +293,7 @@ export const migrations: DataMigration[] = [
293
293
  `,
294
294
  'index.ts': `/**
295
295
  * The ${label} module: its Shutro models, groups, seed data and data migrations, plus access levels, an app,
296
- * menus, actions and views. Installed by listing it in src/modules.ts, then \`kosh migrate\`.
296
+ * menus, actions and views. Installed by listing it in src/modules.ts, then \`shutro-erp migrate\`.
297
297
  */
298
298
  import { base, baseShutro } from 'shutro-erp';
299
299
  import { defineModule } from 'shutro-erp/modules-sdk';
@@ -340,11 +340,11 @@ export const ${id} = defineModule({
340
340
  `,
341
341
  };
342
342
  }
343
- /** `kosh module create <name>`: scaffolds src/modules/<name>/ and lists it in src/modules.ts. */
343
+ /** `shutro-erp module create <name>`: scaffolds src/modules/<name>/ and lists it in src/modules.ts. */
344
344
  export async function runModule(argv, write, cwd) {
345
345
  const [action, name] = argv;
346
346
  if (action !== 'create' || name === undefined) {
347
- write('Usage: kosh module create <name> (lower-case letters, digits and -, e.g. library)\n');
347
+ write('Usage: shutro-erp module create <name> (lower-case letters, digits and -, e.g. library)\n');
348
348
  return 2;
349
349
  }
350
350
  if (!/^[a-z][a-z0-9-]{1,40}$/.test(name) ||
@@ -379,7 +379,7 @@ export async function runModule(argv, write, cwd) {
379
379
  else
380
380
  write(` add ${id} to the modules list in src/modules.ts\n`);
381
381
  }
382
- write(`\nNext: kosh apps install ${name} (creates its tables and loads its seed data, in one transaction)\n`);
382
+ write(`\nNext: shutro-erp apps install ${name} (creates its tables and loads its seed data, in one transaction)\n`);
383
383
  return 0;
384
384
  }
385
385
  /** The access levels a role list names: `reporting.user`, `messaging.user`, … (groups of installed modules). */
@@ -396,7 +396,7 @@ function groupsOf(server, roles) {
396
396
  throw new Error(`Unknown role(s): ${unknown.join(', ')}. Known: ${[...known].sort().join(', ')}.`);
397
397
  return list;
398
398
  }
399
- /** `kosh user create <login> …` and `kosh user password <login>`. */
399
+ /** `shutro-erp user create <login> …` and `shutro-erp user password <login>`. */
400
400
  export async function runUser(argv, write, cwd, prompter) {
401
401
  const { values, positionals } = parseArgs({
402
402
  args: [...argv],
@@ -411,8 +411,8 @@ export async function runUser(argv, write, cwd, prompter) {
411
411
  });
412
412
  const [action, login] = positionals;
413
413
  if ((action !== 'create' && action !== 'password') || login === undefined) {
414
- write('Usage: kosh user create <login> [--name <name>] [--email <email>] [--roles a.user,b.manager] [--password <pw>]\n' +
415
- ' kosh user password <login> [--password <pw>]\n');
414
+ write('Usage: shutro-erp user create <login> [--name <name>] [--email <email>] [--roles a.user,b.manager] [--password <pw>]\n' +
415
+ ' shutro-erp user password <login> [--password <pw>]\n');
416
416
  return 2;
417
417
  }
418
418
  const { terminalPrompter } = await import('./prompt.js');
@@ -436,7 +436,7 @@ export async function runUser(argv, write, cwd, prompter) {
436
436
  params: [],
437
437
  });
438
438
  if (company === undefined)
439
- throw new Error('No company yet: run kosh init first.');
439
+ throw new Error('No company yet: run shutro-erp init first.');
440
440
  const [user] = await server.driver.query({
441
441
  text: 'SELECT id::text AS id FROM res_users WHERE login = $1',
442
442
  params: [login],
@@ -444,7 +444,7 @@ export async function runUser(argv, write, cwd, prompter) {
444
444
  if (action === 'password') {
445
445
  if (user === undefined)
446
446
  throw new Error(`No user "${login}".`);
447
- await server.shutro.bootstrap({ reason: `kosh user password ${login}` }, (env) => auth.setPassword(env, Number(user.id), password));
447
+ await server.shutro.bootstrap({ reason: `shutro-erp user password ${login}` }, (env) => auth.setPassword(env, Number(user.id), password));
448
448
  write(`Password of "${login}" changed.\n`);
449
449
  return 0;
450
450
  }
@@ -459,7 +459,7 @@ export async function runUser(argv, write, cwd, prompter) {
459
459
  groups: groupsOf(server, values.roles),
460
460
  });
461
461
  if (values.email !== undefined)
462
- await server.shutro.bootstrap({ reason: `kosh user create ${login}` }, async (env) => {
462
+ await server.shutro.bootstrap({ reason: `shutro-erp user create ${login}` }, async (env) => {
463
463
  await env.model('res.users')
464
464
  .browse([id])
465
465
  .write({ email: values.email });
@@ -7,11 +7,46 @@ export interface Question {
7
7
  /** An error message for an invalid answer (asked again), or `null`. */
8
8
  readonly validate?: (value: string) => string | null;
9
9
  }
10
+ /** One option of a choice: what is answered, what is shown, an optional hint beside it. */
11
+ export interface Choice {
12
+ readonly value: string;
13
+ readonly label: string;
14
+ readonly hint?: string;
15
+ }
16
+ export interface SelectQuestion {
17
+ readonly message: string;
18
+ readonly choices: readonly Choice[];
19
+ /** The value chosen when Enter is pressed at once. */
20
+ readonly default?: string;
21
+ }
22
+ export interface MultiQuestion {
23
+ readonly message: string;
24
+ readonly choices: readonly Choice[];
25
+ /** The values ticked to begin with. */
26
+ readonly defaults?: readonly string[];
27
+ /** At least one. */
28
+ readonly required?: boolean;
29
+ }
10
30
  export interface Prompter {
11
31
  ask(question: Question): Promise<string>;
12
32
  confirm(message: string, fallback: boolean): Promise<boolean>;
33
+ /** One of several (a list chosen with the keyboard); without it, {@link choose} asks a question instead. */
34
+ select?(question: SelectQuestion): Promise<string>;
35
+ /** Several of several; without it, {@link chooseMany} asks for a comma-separated list. */
36
+ multiselect?(question: MultiQuestion): Promise<string[]>;
13
37
  close(): void;
14
38
  }
39
+ /**
40
+ * One of `choices`: the prompter's list when it has one, otherwise the question `fallback` (pipes, scripts, tests),
41
+ * answered with an option's number, value or label.
42
+ */
43
+ export declare function choose(prompter: Prompter, q: SelectQuestion & {
44
+ readonly fallback: string;
45
+ }): Promise<string>;
46
+ /** Several of `choices`: the prompter's list, or a comma-separated answer to `fallback`. */
47
+ export declare function chooseMany(prompter: Prompter, q: MultiQuestion & {
48
+ readonly fallback: string;
49
+ }): Promise<string[]>;
15
50
  /** Thrown when the input ends before every question is answered (piped answers ran out, Ctrl+D). */
16
51
  export declare class InputEndedError extends Error {
17
52
  constructor(question: string);
@@ -1,8 +1,54 @@
1
1
  /**
2
- * Terminal questions for `kosh init`, on `node:readline` (no dependency). Answers can be scripted for tests and
2
+ * Terminal questions for `shutro-erp init`, on `node:readline` (no dependency). Answers can be scripted for tests and
3
3
  * non-interactive installs (`--yes` with flags): every question goes through {@link Prompter}.
4
4
  */
5
5
  import { createInterface } from 'node:readline';
6
+ /**
7
+ * One of `choices`: the prompter's list when it has one, otherwise the question `fallback` (pipes, scripts, tests),
8
+ * answered with an option's number, value or label.
9
+ */
10
+ export async function choose(prompter, q) {
11
+ if (prompter.select !== undefined)
12
+ return prompter.select(q);
13
+ const find = (answer) => {
14
+ const a = answer.trim().toLowerCase();
15
+ if (/^\d+$/.test(a))
16
+ return q.choices[Number(a) - 1];
17
+ return q.choices.find((c) => c.value.toLowerCase() === a || c.label.toLowerCase() === a);
18
+ };
19
+ const answer = await prompter.ask({
20
+ message: q.fallback,
21
+ ...(q.default === undefined ? {} : { default: q.default }),
22
+ validate: (v) => find(v) === undefined ? `One of ${q.choices.map((c) => c.value).join(', ')}` : null,
23
+ });
24
+ return find(answer).value;
25
+ }
26
+ /** Several of `choices`: the prompter's list, or a comma-separated answer to `fallback`. */
27
+ export async function chooseMany(prompter, q) {
28
+ if (prompter.multiselect !== undefined)
29
+ return prompter.multiselect(q);
30
+ const known = new Set(q.choices.map((c) => c.value));
31
+ const answer = await prompter.ask({
32
+ message: q.fallback,
33
+ default: (q.defaults ?? []).join(','),
34
+ validate: (v) => {
35
+ const items = v
36
+ .split(',')
37
+ .map((x) => x.trim())
38
+ .filter((x) => x !== '');
39
+ const unknown = items.filter((x) => !known.has(x));
40
+ if (unknown.length > 0)
41
+ return `Unknown: ${unknown.join(', ')}`;
42
+ return q.required === true && items.length === 0 ? 'Choose at least one.' : null;
43
+ },
44
+ });
45
+ return [
46
+ ...new Set(answer
47
+ .split(',')
48
+ .map((x) => x.trim())
49
+ .filter((x) => x !== '')),
50
+ ];
51
+ }
6
52
  /** Thrown when the input ends before every question is answered (piped answers ran out, Ctrl+D). */
7
53
  export class InputEndedError extends Error {
8
54
  constructor(question) {
@@ -104,7 +150,7 @@ export function defaultsPrompter() {
104
150
  return {
105
151
  async ask(q) {
106
152
  if (q.default === undefined)
107
- throw new Error(`"${q.message}" has no default: pass it as a flag (kosh init --help).`);
153
+ throw new Error(`"${q.message}" has no default: pass it as a flag (shutro-erp init --help).`);
108
154
  const problem = q.validate?.(q.default) ?? null;
109
155
  if (problem !== null)
110
156
  throw new Error(`${q.message}: ${problem}`);
@@ -1,8 +1,8 @@
1
1
  /**
2
- * The starter project `kosh init` writes: an API server and the back-office app, both importing `shutro-erp`.
2
+ * The starter project `shutro-erp init` writes: an API server and the back-office app, both importing `shutro-erp`.
3
3
  * Server files run on Node.js directly (type stripping); the back-office is a Vite app proxying `/api` to the server.
4
4
  */
5
- export type AppName = 'permissions' | 'reporting' | 'messaging' | 'contacts' | 'product' | 'sales' | 'purchase' | 'accounting' | 'sale_account' | 'purchase_account' | 'project' | 'sale_project';
5
+ export type AppName = 'permissions' | 'reporting' | 'messaging' | 'contacts' | 'product' | 'sales' | 'purchase' | 'accounting' | 'sale_account' | 'purchase_account' | 'project' | 'sale_project' | 'l10n_bd' | 'l10n_in';
6
6
  export interface AppSpec {
7
7
  readonly label: string;
8
8
  /** Shown next to the app in the installer (where the terminal shows pictographs). */
@@ -13,17 +13,21 @@ export interface AppSpec {
13
13
  readonly web?: string;
14
14
  /** A bridge (RFC 0008 §5.1): not offered in the installer; proposed when the apps it connects are installed. */
15
15
  readonly bridge?: readonly AppName[];
16
+ /** A localization (RFC 0016): installed, with its bridged apps, for a company in this country (ISO 3166). */
17
+ readonly country?: string;
16
18
  }
17
- /** The apps `kosh init` can install (base and Settings are always there), in the order the installer lists them. */
19
+ /** The apps `shutro-erp init` can install (base and Settings are always there), in the order the installer lists them. */
18
20
  export declare const APPS: Readonly<Record<AppName, AppSpec>>;
19
21
  export interface StarterOptions {
20
22
  readonly name: string;
21
23
  readonly apps: readonly AppName[];
22
24
  readonly port: number;
25
+ /** The back-office's port in development (default 5173). */
26
+ readonly webPort?: number;
23
27
  readonly koshVersion: string;
24
28
  /** The database name of the Docker PostgreSQL service (compose.yaml). */
25
29
  readonly dockerDb?: string;
26
- /** Attachments in a folder (KOSH_STORAGE_DIR) rather than S3. */
30
+ /** Attachments in a folder (SHUTRO_ERP_STORAGE_DIR) rather than S3. */
27
31
  readonly fileStorage?: boolean;
28
32
  }
29
33
  /** The project's package.json (merged into an existing one: missing scripts and dependencies are added). */
@@ -52,5 +56,9 @@ export declare function tsconfig(): string;
52
56
  export declare function readme(options: StarterOptions): string;
53
57
  /** The apps the installer offers (every app but the bridges, which come by themselves). */
54
58
  export declare const SELECTABLE_APPS: readonly AppName[];
55
- /** The bridges whose every connected app is in `apps`. */
56
- export declare function bridgesFor(apps: readonly AppName[]): AppName[];
59
+ /** The bridges whose every connected app is in `apps`, and the localization of `country` among them. */
60
+ export declare function bridgesFor(apps: readonly AppName[], country?: string): AppName[];
61
+ /** The names of the countries with a localization (the installer's list). */
62
+ export declare const COUNTRY_NAMES: Readonly<Record<string, string>>;
63
+ /** The countries with a localization, for the installer's question. */
64
+ export declare const LOCALIZED_COUNTRIES: readonly string[];