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
@@ -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
- /** The apps `kosh init` can install (base and Settings are always there), in the order the installer lists them. */
5
+ /** The apps `shutro-erp init` can install (base and Settings are always there), in the order the installer lists them. */
6
6
  export const APPS = {
7
7
  permissions: {
8
8
  label: 'Roles, field security and approvals',
@@ -67,6 +67,22 @@ export const APPS = {
67
67
  server: 'saleProject',
68
68
  bridge: ['sales', 'project'],
69
69
  },
70
+ l10n_bd: {
71
+ label: 'Bangladesh: chart of accounts, VAT and SD, VDS and TDS, BIN and TIN, July–June year',
72
+ icon: '🇧🇩',
73
+ server: 'l10nBd',
74
+ web: 'registerL10nBdWeb',
75
+ bridge: ['accounting'],
76
+ country: 'BD',
77
+ },
78
+ l10n_in: {
79
+ label: 'India: GST (CGST / SGST / IGST), cess, reverse charge, GSTIN, HSN / SAC, TDS, April–March year',
80
+ icon: '🇮🇳',
81
+ server: 'l10nIn',
82
+ web: 'registerL10nInWeb',
83
+ bridge: ['accounting'],
84
+ country: 'IN',
85
+ },
70
86
  };
71
87
  const REACT = {
72
88
  react: '^19.1.0',
@@ -89,8 +105,8 @@ export function packageJson(options, existing) {
89
105
  start: 'node --env-file=.env src/server.ts',
90
106
  web: 'vite',
91
107
  'build:web': 'vite build',
92
- migrate: 'kosh migrate',
93
- seed: 'kosh seed',
108
+ migrate: 'shutro-erp migrate',
109
+ seed: 'shutro-erp seed',
94
110
  'docker:up': 'docker compose up -d --build',
95
111
  }),
96
112
  engines: { node: '>=24', ...(base['engines'] ?? {}) },
@@ -114,7 +130,7 @@ export function modulesTs(_options) {
114
130
  const imports = apps.map((a) => `import { ${APPS[a].server} } from 'shutro-erp/${a}';`);
115
131
  return `/**
116
132
  * The apps this installation can install (the catalogue). The database records which are installed: Settings → Apps
117
- * in the back-office, or \`npx kosh apps\`, installs, disables and enables them. \`base\` (users, companies, sign-in)
133
+ * in the back-office, or \`npx shutro-erp apps\`, installs, disables and enables them. \`base\` (users, companies, sign-in)
118
134
  * and Settings are always installed. Add your own modules here (\`defineModule\` from \`shutro-erp/modules-sdk\`),
119
135
  * then install them from Settings → Apps.
120
136
  */
@@ -125,15 +141,16 @@ import { notify } from 'shutro-erp/notify';
125
141
  import { realtime } from 'shutro-erp/realtime';
126
142
  import { settings } from 'shutro-erp/settings';
127
143
  import { assistant } from 'shutro-erp/assistant';
144
+ import { dataImport } from 'shutro-erp/import';
128
145
  import { studio } from 'shutro-erp/studio';
129
146
  ${imports.join('\n')}
130
147
 
131
- export const modules = [base, settings, realtime, notify, mail, automation, studio, assistant${apps.map((a) => `, ${APPS[a].server}`).join('')}];
148
+ export const modules = [base, settings, realtime, notify, mail, automation, studio, dataImport, assistant${apps.map((a) => `, ${APPS[a].server}`).join('')}];
132
149
  `;
133
150
  }
134
151
  export function serverTs() {
135
152
  return `/**
136
- * The Kosh API server: \`npm start\` (reads .env). It serves the installed apps of src/modules.ts, and switches to
153
+ * The Shutro ERP API server: \`npm start\` (reads .env). It serves the installed apps of src/modules.ts, and switches to
137
154
  * the new set without a restart when apps are installed or disabled (here or on another server).
138
155
  */
139
156
  import { createAppHost, startAppHost } from 'shutro-erp';
@@ -145,11 +162,11 @@ const host = await createAppHost({
145
162
  catalogue: modules,
146
163
  migrationsDir: 'migrations',
147
164
  ...(env['VALKEY_URL'] ? { valkeyUrl: env['VALKEY_URL'] } : {}),
148
- // Secure cookies need HTTPS: on by default in production (KOSH_SECURE_COOKIES=false for plain HTTP).
149
- auth: { secureCookies: (env['KOSH_SECURE_COOKIES'] ?? String(env['NODE_ENV'] === 'production')) === 'true' },
165
+ // Secure cookies need HTTPS: on by default in production (SHUTRO_ERP_SECURE_COOKIES=false for plain HTTP).
166
+ auth: { secureCookies: (env['SHUTRO_ERP_SECURE_COOKIES'] ?? env['KOSH_SECURE_COOKIES'] ?? String(env['NODE_ENV'] === 'production')) === 'true' },
150
167
  });
151
168
  const address = await startAppHost(host, { port: Number(env['PORT'] ?? 3000), host: env['HOST'] ?? '127.0.0.1' });
152
- process.stdout.write(\`Kosh API on \${address}\\n\`);
169
+ process.stdout.write(\`Shutro ERP API on \${address}\\n\`);
153
170
  `;
154
171
  }
155
172
  export function viteConfig(options) {
@@ -159,13 +176,13 @@ import react from '@vitejs/plugin-react';
159
176
  import { koshPwa } from 'shutro-erp/ui/vite';
160
177
  import { defineConfig } from 'vite';
161
178
 
162
- const api = process.env['KOSH_API'] ?? 'http://localhost:${options.port}';
179
+ const api = process.env['SHUTRO_ERP_API'] ?? process.env['KOSH_API'] ?? 'http://localhost:${options.port}';
163
180
 
164
181
  export default defineConfig({
165
182
  // koshPwa: the service worker (the app opens offline and gets web push)
166
183
  plugins: [react(), tailwindcss(), koshPwa()],
167
- server: { proxy: { '/api': { target: api, ws: true } } },
168
- preview: { proxy: { '/api': { target: api, ws: true } } },
184
+ server: { port: ${options.webPort ?? 5173}, proxy: { '/api': { target: api, ws: true } } },
185
+ preview: { port: ${options.webPort ?? 5173}, proxy: { '/api': { target: api, ws: true } } },
169
186
  });
170
187
  `;
171
188
  }
@@ -201,6 +218,7 @@ export function mainTsx(_options) {
201
218
  { name: 'automation', fn: 'registerAutomationWeb' },
202
219
  { name: 'studio', fn: 'registerStudioWeb' },
203
220
  { name: 'assistant', fn: 'registerAssistantWeb' },
221
+ { name: 'import', fn: 'registerImportWeb' },
204
222
  ].map(({ name, fn }) => {
205
223
  const messages = name === 'permissions' ? null : `${name}Messages`;
206
224
  return {
@@ -245,7 +263,7 @@ export function manifest(options) {
245
263
  }
246
264
  /** The app icon (replace it with the company's). */
247
265
  export function icon() {
248
- return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Kosh">
266
+ return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="Shutro ERP">
249
267
  <rect width="512" height="512" rx="112" fill="#1d4ed8"/>
250
268
  <path d="M156 128h56v112l108-112h72L270 258l128 126h-74L220 282v102h-64z" fill="#fff"/>
251
269
  </svg>
@@ -255,7 +273,7 @@ export function indexCss() {
255
273
  return `@import "tailwindcss";
256
274
  @import "shutro-erp/ui/styles.css";
257
275
 
258
- /* Tailwind scans Kosh's components for the classes they use. */
276
+ /* Tailwind scans Shutro ERP's components for the classes they use. */
259
277
  @source "../../node_modules/shutro-erp/dist";
260
278
  `;
261
279
  }
@@ -268,7 +286,7 @@ var/
268
286
  }
269
287
  /** Three images from one build: the API (migrates, then serves) and nginx with the back-office. */
270
288
  export function dockerfile() {
271
- return `# Kosh images (kosh init). Built by compose.yaml: docker compose up -d --build
289
+ return `# Shutro ERP images (shutro-erp init). Built by compose.yaml: docker compose up -d --build
272
290
  FROM node:24-alpine AS build
273
291
  WORKDIR /app
274
292
  COPY package.json package-lock.json* ./
@@ -288,7 +306,7 @@ COPY --from=build /app/migrations ./migrations
288
306
  RUN mkdir -p /app/var/attachments && chown -R node:node /app/var
289
307
  USER node
290
308
  EXPOSE 3000
291
- CMD ["sh", "-c", "npx kosh migrate && node src/server.ts"]
309
+ CMD ["sh", "-c", "npx shutro-erp migrate && node src/server.ts"]
292
310
 
293
311
  # The back-office: static files, /api proxied to the API.
294
312
  FROM nginx:1.27-alpine AS web
@@ -297,7 +315,7 @@ COPY nginx.conf /etc/nginx/conf.d/default.conf
297
315
  `;
298
316
  }
299
317
  export function nginxConf() {
300
- return `# The back-office (kosh init): the built app, /api proxied to the API service.
318
+ return `# The back-office (shutro-erp init): the built app, /api proxied to the API service.
301
319
  server {
302
320
  listen 80;
303
321
  root /usr/share/nginx/html;
@@ -328,9 +346,9 @@ export function composeYaml(options) {
328
346
  const db = options.dockerDb ?? options.name.replace(/-/g, '_');
329
347
  /** A compose variable: `${NAME}` or `${NAME:-default}` (plain strings: nothing to escape). */
330
348
  const v = (name, fallback) => ['$', '{', name, fallback === undefined ? '' : fallback, '}'].join('');
331
- const user = v('POSTGRES_USER', ':-kosh');
349
+ const user = v('POSTGRES_USER', ':-shutro');
332
350
  const lines = [
333
- '# Kosh in Docker (kosh init).',
351
+ '# Shutro ERP in Docker (shutro-erp init).',
334
352
  '# docker compose up -d db only PostgreSQL (the API runs with npm start)',
335
353
  '# docker compose up -d --build everything: the back-office on http://localhost:8080',
336
354
  `name: ${options.name}`,
@@ -349,7 +367,7 @@ export function composeYaml(options) {
349
367
  ' - db:/var/lib/postgresql',
350
368
  ' healthcheck:',
351
369
  // $$ is a literal $ for compose: the shell in the container expands the variable.
352
- ` test: ["CMD-SHELL", "pg_isready -U $${v('POSTGRES_USER', ':-kosh')}"]`,
370
+ ` test: ["CMD-SHELL", "pg_isready -U $${v('POSTGRES_USER', ':-shutro')}"]`,
353
371
  ' interval: 5s',
354
372
  ' retries: 30',
355
373
  '',
@@ -361,9 +379,11 @@ export function composeYaml(options) {
361
379
  ' env_file: .env',
362
380
  ' environment:',
363
381
  ` DATABASE_URL: ${v('DOCKER_DATABASE_URL', `:-postgres://${user}:${v('POSTGRES_PASSWORD')}@db:5432/${v('POSTGRES_DB', `:-${db}`)}`)}`,
364
- ...(options.fileStorage === false ? [] : [' KOSH_STORAGE_DIR: /app/var/attachments']),
382
+ ...(options.fileStorage === false
383
+ ? []
384
+ : [' SHUTRO_ERP_STORAGE_DIR: /app/var/attachments']),
365
385
  ' # Plain HTTP on localhost; set "true" when the site is served over HTTPS.',
366
- ' KOSH_SECURE_COOKIES: "false"',
386
+ ' SHUTRO_ERP_SECURE_COOKIES: "false"',
367
387
  ' depends_on:',
368
388
  ' db:',
369
389
  ' condition: service_healthy',
@@ -413,19 +433,19 @@ export function tsconfig() {
413
433
  export function readme(options) {
414
434
  return `# ${options.name}
415
435
 
416
- A [Kosh](https://www.npmjs.com/package/shutro-erp) installation, created with \`kosh init\`.
436
+ A [Shutro ERP](https://www.npmjs.com/package/shutro-erp) installation, created with \`shutro-erp init\`.
417
437
 
418
438
  \`\`\`sh
419
439
  npm install
420
440
  npm start # the API on http://localhost:${options.port}
421
- npm run web # the back-office on http://localhost:5173 (sign in as the administrator)
422
- npx kosh migrate # after changing modules: plan and apply database migrations, load seed data
423
- npx kosh seed # load seed data again
441
+ npm run web # the back-office on http://localhost:${options.webPort ?? 5173} (sign in as the administrator)
442
+ npx shutro-erp migrate # after changing modules: plan and apply database migrations, load seed data
443
+ npx shutro-erp seed # load seed data again
424
444
  # Settings → Apps in the back-office installs, enables and disables apps (or, in the terminal:)
425
- npx kosh apps # the apps: installed, disabled, available
426
- npx kosh apps install <name> # install an app (and what it needs) in one transaction
427
- npx kosh apps disable <name> # switch an app off; its data stays (npx kosh apps enable <name>)
428
- npx kosh module create <name> # a new module in src/modules/<name>/
445
+ npx shutro-erp apps # the apps: installed, disabled, available
446
+ npx shutro-erp apps install <name> # install an app (and what it needs) in one transaction
447
+ npx shutro-erp apps disable <name> # switch an app off; its data stays (npx shutro-erp apps enable <name>)
448
+ npx shutro-erp module create <name> # a new module in src/modules/<name>/
429
449
  docker compose up -d --build # everything in Docker: http://localhost:8080
430
450
  \`\`\`
431
451
 
@@ -435,9 +455,16 @@ Settings (database, file storage, port) are in \`.env\`, which is not committed.
435
455
  }
436
456
  /** The apps the installer offers (every app but the bridges, which come by themselves). */
437
457
  export const SELECTABLE_APPS = Object.keys(APPS).filter((a) => APPS[a].bridge === undefined);
438
- /** The bridges whose every connected app is in `apps`. */
439
- export function bridgesFor(apps) {
458
+ /** The bridges whose every connected app is in `apps`, and the localization of `country` among them. */
459
+ export function bridgesFor(apps, country = '') {
440
460
  return Object.keys(APPS).filter((a) => {
441
- return APPS[a].bridge?.every((b) => apps.includes(b)) === true;
461
+ const spec = APPS[a];
462
+ if (spec.country !== undefined && spec.country !== country)
463
+ return false;
464
+ return spec.bridge?.every((b) => apps.includes(b)) === true;
442
465
  });
443
466
  }
467
+ /** The names of the countries with a localization (the installer's list). */
468
+ export const COUNTRY_NAMES = { BD: 'Bangladesh', IN: 'India' };
469
+ /** The countries with a localization, for the installer's question. */
470
+ export const LOCALIZED_COUNTRIES = Object.values(APPS).flatMap((a) => a.country === undefined ? [] : [a.country]);
@@ -0,0 +1,7 @@
1
+ import type { Prompter } from './prompt.js';
2
+ import { type Terminal } from './ui.js';
3
+ /** Thrown when the user stops the installer (Ctrl+C) during a question. */
4
+ export declare class CancelledError extends Error {
5
+ constructor();
6
+ }
7
+ export declare function interactivePrompter(input: NodeJS.ReadStream, output: NodeJS.WriteStream, terminal: Terminal): Prompter;
@@ -0,0 +1,254 @@
1
+ /**
2
+ * The installer's questions on a terminal (no dependency): one question at a time on the rail of `ui.ts`, answered
3
+ * with the keyboard — text with the default shown until typed over, secrets masked, choices with ↑ ↓ and Enter,
4
+ * several choices with Space, Yes / No with ← →. An answered question collapses to one dimmed line; a refused
5
+ * answer shows why under the input. Ctrl+C stops the installer. Not a terminal (pipes, tests): `prompt.ts` asks
6
+ * line by line instead.
7
+ */
8
+ import { emitKeypressEvents } from 'node:readline';
9
+ import { ESC, RAIL, stylerFor, width } from './ui.js';
10
+ /** Thrown when the user stops the installer (Ctrl+C) during a question. */
11
+ export class CancelledError extends Error {
12
+ constructor() {
13
+ super('Cancelled.');
14
+ this.name = 'CancelledError';
15
+ }
16
+ }
17
+ export function interactivePrompter(input, output, terminal) {
18
+ const s = stylerFor(terminal);
19
+ const columns = () => Math.max(40, output.columns ?? terminal.columns);
20
+ /** Terminal rows `text` takes (long lines wrap). */
21
+ const rows = (text) => text
22
+ .split('\n')
23
+ .slice(0, -1)
24
+ .reduce((n, line) => n + Math.max(1, Math.ceil(width(line) / columns())), 0);
25
+ const bar = (status) => status === 'error'
26
+ ? s('yellow', RAIL.bar)
27
+ : status === 'active'
28
+ ? s('cyan', RAIL.bar)
29
+ : s('gray', RAIL.bar);
30
+ const head = (status, message) => {
31
+ const mark = status === 'active'
32
+ ? s('cyan', RAIL.active)
33
+ : status === 'error'
34
+ ? s('yellow', RAIL.error)
35
+ : status === 'cancel'
36
+ ? s('red', RAIL.cancel)
37
+ : s('green', RAIL.done);
38
+ return `${s('gray', RAIL.bar)}\n${mark} ${status === 'done' ? message : s('bold', message)}\n`;
39
+ };
40
+ const foot = (status, error) => status === 'error'
41
+ ? `${s('yellow', RAIL.end)} ${s('yellow', error ?? '')}\n`
42
+ : status === 'active'
43
+ ? `${s('cyan', RAIL.end)}\n`
44
+ : '';
45
+ const cursor = s('inverse', ' ');
46
+ function run(widget) {
47
+ return new Promise((resolve, reject) => {
48
+ let drawn = 0;
49
+ let error = null;
50
+ const draw = (status) => {
51
+ const text = widget.frame(status, error);
52
+ if (drawn > 0)
53
+ output.write(`${ESC}${drawn}A\r`);
54
+ output.write(`${ESC}J${text}`);
55
+ drawn = rows(text);
56
+ };
57
+ const finish = () => {
58
+ input.off('keypress', onKey);
59
+ if (input.isTTY)
60
+ input.setRawMode(false);
61
+ input.pause();
62
+ output.write(`${ESC}?25h`);
63
+ };
64
+ const onKey = (text, key = {}) => {
65
+ if (key.ctrl === true && (key.name === 'c' || key.name === 'd')) {
66
+ draw('cancel');
67
+ finish();
68
+ reject(new CancelledError());
69
+ return;
70
+ }
71
+ const action = widget.key(text, key);
72
+ if (action === 'submit') {
73
+ const value = widget.value();
74
+ const problem = widget.validate?.(value) ?? null;
75
+ if (problem === null) {
76
+ error = null;
77
+ draw('done');
78
+ finish();
79
+ resolve(value);
80
+ return;
81
+ }
82
+ error = problem;
83
+ draw('error');
84
+ return;
85
+ }
86
+ draw(error === null ? 'active' : 'error');
87
+ };
88
+ emitKeypressEvents(input);
89
+ if (input.isTTY)
90
+ input.setRawMode(true);
91
+ input.resume();
92
+ output.write(`${ESC}?25l`);
93
+ input.on('keypress', onKey);
94
+ draw('active');
95
+ });
96
+ }
97
+ /** The question without the indentation of sub-questions (the rail aligns them). */
98
+ const label = (message) => message.trim();
99
+ function text(q) {
100
+ let value = '';
101
+ return run({
102
+ frame(status, error) {
103
+ const shown = q.secret === true ? '•'.repeat(value.length) : value;
104
+ if (status === 'done' || status === 'cancel') {
105
+ const answer = q.secret === true
106
+ ? s('gray', '•'.repeat(Math.min(12, (value || q.default || '').length)))
107
+ : s('gray', value === '' ? (q.default ?? '') : value);
108
+ return `${head(status, label(q.message))}${bar(status)} ${status === 'cancel' ? s('gray', 'cancelled') : answer}\n`;
109
+ }
110
+ const placeholder = value === '' && q.default !== undefined && q.default !== '' && q.secret !== true
111
+ ? s('gray', q.default)
112
+ : '';
113
+ return `${head(status, label(q.message))}${bar(status)} ${shown}${cursor}${placeholder}\n${foot(status, error)}`;
114
+ },
115
+ key(input, key) {
116
+ if (key.name === 'return' || key.name === 'enter')
117
+ return 'submit';
118
+ if (key.name === 'backspace')
119
+ value = value.slice(0, -1);
120
+ else if (key.ctrl === true && key.name === 'u')
121
+ value = '';
122
+ else if (key.name === 'tab' && value === '' && q.default !== undefined)
123
+ value = q.default;
124
+ else if (input !== undefined &&
125
+ key.ctrl !== true &&
126
+ key.meta !== true &&
127
+ [...input].every((ch) => ch >= ' ' && ch !== '\x7f'))
128
+ value += input;
129
+ return undefined;
130
+ },
131
+ value: () => (value.trim() === '' ? (q.default ?? '') : value.trim()),
132
+ validate: (v) => q.validate?.(v) ?? null,
133
+ });
134
+ }
135
+ const labelWidth = (choices) => Math.max(...choices.map((c) => width(c.label)));
136
+ /** An option: its mark, its label (bright when focused, with a ❯ pointer), its hint aligned after it. */
137
+ const optionLine = (c, mark, focused, pad) => {
138
+ const name = focused ? s('bold', c.label) : c.label;
139
+ const gap = ' '.repeat(Math.max(0, pad - width(c.label)) + 2);
140
+ const hint = c.hint === undefined ? '' : `${gap}${s('gray', c.hint)}`;
141
+ return `${focused ? s('cyan', '❯') : ' '} ${mark} ${name}${hint}`;
142
+ };
143
+ function select(q) {
144
+ let at = Math.max(0, q.choices.findIndex((c) => c.value === q.default));
145
+ return run({
146
+ frame(status) {
147
+ const chosen = q.choices[at];
148
+ if (status === 'done' || status === 'cancel')
149
+ return `${head(status, q.message)}${bar(status)} ${s('gray', status === 'cancel' ? 'cancelled' : (chosen?.label ?? ''))}\n`;
150
+ const list = q.choices
151
+ .map((c, i) => `${bar(status)} ${optionLine(c, i === at ? s('cyan', '●') : s('gray', '○'), i === at, labelWidth(q.choices))}`)
152
+ .join('\n');
153
+ return `${head(status, q.message)}${list}\n${foot(status, null)}`;
154
+ },
155
+ key(_text, key) {
156
+ if (key.name === 'return' || key.name === 'enter')
157
+ return 'submit';
158
+ if (key.name === 'up' || key.name === 'k')
159
+ at = (at - 1 + q.choices.length) % q.choices.length;
160
+ if (key.name === 'down' || key.name === 'j' || key.name === 'tab')
161
+ at = (at + 1) % q.choices.length;
162
+ return undefined;
163
+ },
164
+ value: () => q.choices[at]?.value ?? '',
165
+ });
166
+ }
167
+ function multiselect(q) {
168
+ let at = 0;
169
+ const picked = new Set(q.defaults ?? []);
170
+ const values = () => q.choices.filter((c) => picked.has(c.value)).map((c) => c.value);
171
+ return run({
172
+ frame(status, error) {
173
+ if (status === 'done' || status === 'cancel') {
174
+ const names = q.choices.filter((c) => picked.has(c.value)).map((c) => c.label);
175
+ return `${head(status, q.message)}${bar(status)} ${s('gray', status === 'cancel' ? 'cancelled' : names.length === 0 ? 'none' : names.join(', '))}\n`;
176
+ }
177
+ const list = q.choices
178
+ .map((c, i) => {
179
+ const mark = picked.has(c.value) ? s('green', '◼') : s('gray', '◻');
180
+ return `${bar(status)} ${optionLine(c, mark, i === at, labelWidth(q.choices))}`;
181
+ })
182
+ .join('\n');
183
+ const help = s('gray', '↑↓ move · Space toggle · a all · Enter confirm');
184
+ return `${head(status, q.message)}${list}\n${bar(status)} ${help}\n${foot(status, error)}`;
185
+ },
186
+ key(text, key) {
187
+ if (key.name === 'return' || key.name === 'enter')
188
+ return 'submit';
189
+ if (key.name === 'up' || key.name === 'k')
190
+ at = (at - 1 + q.choices.length) % q.choices.length;
191
+ else if (key.name === 'down' || key.name === 'j' || key.name === 'tab')
192
+ at = (at + 1) % q.choices.length;
193
+ else if (key.name === 'space' || text === ' ') {
194
+ const c = q.choices[at];
195
+ if (c !== undefined) {
196
+ if (picked.has(c.value))
197
+ picked.delete(c.value);
198
+ else
199
+ picked.add(c.value);
200
+ }
201
+ }
202
+ else if (key.name === 'a') {
203
+ if (picked.size === q.choices.length)
204
+ picked.clear();
205
+ else
206
+ for (const c of q.choices)
207
+ picked.add(c.value);
208
+ }
209
+ return undefined;
210
+ },
211
+ value: values,
212
+ validate: (v) => (q.required === true && v.length === 0 ? 'Choose at least one.' : null),
213
+ });
214
+ }
215
+ function confirm(message, fallback) {
216
+ let yes = fallback;
217
+ return run({
218
+ frame(status) {
219
+ if (status === 'done' || status === 'cancel')
220
+ return `${head(status, label(message))}${bar(status)} ${s('gray', status === 'cancel' ? 'cancelled' : yes ? 'Yes' : 'No')}\n`;
221
+ const option = (on, name) => on ? `${s('cyan', '●')} ${name}` : `${s('gray', '○')} ${s('gray', name)}`;
222
+ return `${head(status, label(message))}${bar(status)} ${option(yes, 'Yes')} ${s('gray', '/')} ${option(!yes, 'No')}\n${foot(status, null)}`;
223
+ },
224
+ key(text, key) {
225
+ if (key.name === 'return' || key.name === 'enter')
226
+ return 'submit';
227
+ if (key.name === 'left' ||
228
+ key.name === 'right' ||
229
+ key.name === 'tab' ||
230
+ key.name === 'h' ||
231
+ key.name === 'l')
232
+ yes = !yes;
233
+ else if (text?.toLowerCase() === 'y')
234
+ yes = true;
235
+ else if (text?.toLowerCase() === 'n')
236
+ yes = false;
237
+ return undefined;
238
+ },
239
+ value: () => yes,
240
+ });
241
+ }
242
+ return {
243
+ ask: text,
244
+ confirm,
245
+ select,
246
+ multiselect,
247
+ close() {
248
+ if (input.isTTY)
249
+ input.setRawMode(false);
250
+ input.pause();
251
+ output.write(`${ESC}?25h`);
252
+ },
253
+ };
254
+ }
package/dist/cli/ui.d.ts CHANGED
@@ -1,9 +1,3 @@
1
- /**
2
- * The installer's look in the terminal, without dependencies: a logo, numbered steps with icons, spinners for long
3
- * work, status lines (✔ ▲ ✖) and a summary card. Colour, Unicode and animation are detected from the terminal
4
- * (NO_COLOR, FORCE_COLOR, KOSH_ASCII=1, TERM=dumb, Windows consoles without Unicode); output that is not a terminal
5
- * (tests, logs, CI) gets plain lines with the same words and no escape codes.
6
- */
7
1
  /** What the terminal can show. */
8
2
  export interface Terminal {
9
3
  readonly color: boolean;
@@ -22,9 +16,24 @@ type Env = Readonly<Record<string, string | undefined>>;
22
16
  export declare function detectTerminal(stream: {
23
17
  readonly isTTY?: boolean;
24
18
  readonly columns?: number;
25
- }, env?: Env, platform?: string): Terminal;
19
+ }, env?: Env, platform?: string, release?: string): Terminal;
20
+ export declare const ESC = "\u001B[";
26
21
  /** The colours and weights the installer uses. */
27
- export type Style = 'bold' | 'dim' | 'green' | 'yellow' | 'red' | 'cyan' | 'magenta' | 'gray';
22
+ export type Style = 'bold' | 'dim' | 'inverse' | 'underline' | 'green' | 'yellow' | 'red' | 'cyan' | 'magenta' | 'gray';
23
+ /** Styles text for `terminal` (no escape codes without colour). */
24
+ export declare function stylerFor(terminal: Terminal): (name: Style, text: string) => string;
25
+ /** The rail and its marks (Unicode terminals that animate), shared with the interactive prompter. */
26
+ export declare const RAIL: {
27
+ readonly bar: '│';
28
+ readonly start: '┌';
29
+ readonly end: '└';
30
+ readonly active: '◆';
31
+ readonly done: '◇';
32
+ readonly error: '▲';
33
+ readonly cancel: '■';
34
+ };
35
+ /** Whether `terminal` gets the rail look (a TTY that shows Unicode). */
36
+ export declare const railed: (terminal: Terminal) => boolean;
28
37
  /** Columns a string takes: escape codes take none, pictographs two. */
29
38
  export declare function width(text: string): number;
30
39
  /** The step icons: a pictograph where the terminal shows them, a symbol otherwise. */
@@ -58,6 +67,10 @@ export interface Ui {
58
67
  style(name: Style, text: string): string;
59
68
  icon(name: IconName): string;
60
69
  banner(subtitle: string): Promise<void>;
70
+ /** Opens the rail (┌ title) under the banner, with a hint line. */
71
+ intro(title: string, hint: string): void;
72
+ /** Closes the rail (└ text). */
73
+ outro(text: string): void;
61
74
  /** A numbered step title: "🗄️ Step 1/6 · Database". */
62
75
  step(icon: IconName, title: string, hint?: string): void;
63
76
  ok(text: string): void;