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.
- package/README.md +74 -67
- package/dist/account_portal/.kosh/bindings.d.ts +22 -0
- package/dist/account_portal/.kosh/bindings.js +15 -0
- package/dist/account_portal/.kosh/schema.d.ts +1380 -0
- package/dist/account_portal/index.d.ts +3 -0
- package/dist/account_portal/index.js +73 -0
- package/dist/account_portal/module.d.ts +5983 -0
- package/dist/account_portal/module.js +18 -0
- package/dist/accounting/.kosh/bindings.js +1 -1
- package/dist/accounting/.kosh/schema.d.ts +172 -9
- package/dist/accounting/chart.d.ts +40 -8
- package/dist/accounting/chart.js +124 -30
- package/dist/accounting/closing.d.ts +6 -1
- package/dist/accounting/closing.js +5 -1
- package/dist/accounting/demo.js +6 -5
- package/dist/accounting/index.d.ts +3 -3
- package/dist/accounting/index.js +200 -23
- package/dist/accounting/module.d.ts +3326 -1086
- package/dist/accounting/module.js +408 -66
- package/dist/accounting/payments.d.ts +25 -0
- package/dist/accounting/payments.js +96 -4
- package/dist/accounting/web/RegisterPayment.d.ts +3 -2
- package/dist/accounting/web/RegisterPayment.js +39 -6
- package/dist/accounting/web/SettingsPage.js +5 -1
- package/dist/accounting/web/messages.js +10 -0
- package/dist/assistant/routes.js +1 -1
- package/dist/automation/engine.js +1 -1
- package/dist/automation/web/messages.js +2 -2
- package/dist/cli/apps.d.ts +1 -1
- package/dist/cli/apps.js +14 -14
- package/dist/cli/bin.js +3 -0
- package/dist/cli/index.js +11 -11
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.js +224 -78
- package/dist/cli/launch.d.ts +16 -0
- package/dist/cli/launch.js +138 -0
- package/dist/cli/project.d.ts +7 -7
- package/dist/cli/project.js +25 -25
- package/dist/cli/prompt.d.ts +35 -0
- package/dist/cli/prompt.js +48 -2
- package/dist/cli/templates.d.ts +14 -6
- package/dist/cli/templates.js +61 -34
- package/dist/cli/tui.d.ts +7 -0
- package/dist/cli/tui.js +254 -0
- package/dist/cli/ui.d.ts +21 -8
- package/dist/cli/ui.js +65 -19
- package/dist/contacts/.kosh/bindings.js +1 -1
- package/dist/contacts/.kosh/schema.d.ts +33 -1
- package/dist/contacts/index.js +24 -1
- package/dist/contacts/module.d.ts +42 -0
- package/dist/contacts/module.js +3 -1
- package/dist/import/convert.d.ts +55 -0
- package/dist/import/convert.js +224 -0
- package/dist/import/csv.d.ts +28 -0
- package/dist/import/csv.js +115 -0
- package/dist/import/index.d.ts +33 -0
- package/dist/import/index.js +60 -0
- package/dist/import/mapping.d.ts +15 -0
- package/dist/import/mapping.js +33 -0
- package/dist/import/match.d.ts +24 -0
- package/dist/import/match.js +60 -0
- package/dist/import/models.d.ts +2 -0
- package/dist/import/models.js +102 -0
- package/dist/import/routes.d.ts +13 -0
- package/dist/import/routes.js +392 -0
- package/dist/import/run.d.ts +131 -0
- package/dist/import/run.js +568 -0
- package/dist/import/service.d.ts +153 -0
- package/dist/import/service.js +367 -0
- package/dist/import/web/ImportButton.d.ts +7 -0
- package/dist/import/web/ImportButton.js +31 -0
- package/dist/import/web/ImportWizard.d.ts +75 -0
- package/dist/import/web/ImportWizard.js +217 -0
- package/dist/import/web/index.d.ts +13 -0
- package/dist/import/web/index.js +18 -0
- package/dist/import/web/messages.d.ts +2 -0
- package/dist/import/web/messages.js +139 -0
- package/dist/import/worker.d.ts +3 -0
- package/dist/import/worker.js +52 -0
- package/dist/import/xlsx.d.ts +20 -0
- package/dist/import/xlsx.js +192 -0
- package/dist/l10n_bd/.kosh/bindings.d.ts +22 -0
- package/dist/l10n_bd/.kosh/bindings.js +15 -0
- package/dist/l10n_bd/.kosh/schema.d.ts +1311 -0
- package/dist/l10n_bd/documents.d.ts +8 -0
- package/dist/l10n_bd/documents.js +183 -0
- package/dist/l10n_bd/index.d.ts +8 -0
- package/dist/l10n_bd/index.js +156 -0
- package/dist/l10n_bd/module.d.ts +5705 -0
- package/dist/l10n_bd/module.js +163 -0
- package/dist/l10n_bd/mushak.d.ts +67 -0
- package/dist/l10n_bd/mushak.js +139 -0
- package/dist/l10n_bd/routes.d.ts +3 -0
- package/dist/l10n_bd/routes.js +205 -0
- package/dist/l10n_bd/web/MushakPage.d.ts +7 -0
- package/dist/l10n_bd/web/MushakPage.js +53 -0
- package/dist/l10n_bd/web/index.d.ts +13 -0
- package/dist/l10n_bd/web/index.js +18 -0
- package/dist/l10n_bd/web/messages.d.ts +2 -0
- package/dist/l10n_bd/web/messages.js +101 -0
- package/dist/l10n_in/.kosh/bindings.d.ts +22 -0
- package/dist/l10n_in/.kosh/bindings.js +15 -0
- package/dist/l10n_in/.kosh/schema.d.ts +1373 -0
- package/dist/l10n_in/documents.d.ts +8 -0
- package/dist/l10n_in/documents.js +114 -0
- package/dist/l10n_in/einvoice.d.ts +253 -0
- package/dist/l10n_in/einvoice.js +485 -0
- package/dist/l10n_in/gstr.d.ts +109 -0
- package/dist/l10n_in/gstr.js +430 -0
- package/dist/l10n_in/index.d.ts +20 -0
- package/dist/l10n_in/index.js +232 -0
- package/dist/l10n_in/module.d.ts +7823 -0
- package/dist/l10n_in/module.js +427 -0
- package/dist/l10n_in/routes-einvoice.d.ts +15 -0
- package/dist/l10n_in/routes-einvoice.js +124 -0
- package/dist/l10n_in/routes.d.ts +17 -0
- package/dist/l10n_in/routes.js +282 -0
- package/dist/l10n_in/web/EInvoicePanel.d.ts +8 -0
- package/dist/l10n_in/web/EInvoicePanel.js +63 -0
- package/dist/l10n_in/web/GstPage.d.ts +7 -0
- package/dist/l10n_in/web/GstPage.js +57 -0
- package/dist/l10n_in/web/GstSettings.d.ts +10 -0
- package/dist/l10n_in/web/GstSettings.js +36 -0
- package/dist/l10n_in/web/index.d.ts +15 -0
- package/dist/l10n_in/web/index.js +22 -0
- package/dist/l10n_in/web/messages.d.ts +2 -0
- package/dist/l10n_in/web/messages.js +166 -0
- package/dist/mail/index.js +2 -2
- package/dist/modules-sdk/demo.d.ts +2 -0
- package/dist/modules-sdk/demo.js +7 -3
- package/dist/modules-sdk/index.d.ts +2 -2
- package/dist/modules-sdk/index.js +1 -1
- package/dist/modules-sdk/loader.js +2 -2
- package/dist/modules-sdk/manifest.d.ts +132 -0
- package/dist/modules-sdk/manifest.js +22 -0
- package/dist/notify/inbox.js +21 -0
- package/dist/notify/messages.js +7 -3
- package/dist/notify/models.d.ts +1 -1
- package/dist/notify/models.js +2 -0
- package/dist/notify/routes.js +1 -1
- package/dist/notify/web/messages.js +5 -1
- package/dist/portal/.kosh/bindings.d.ts +22 -0
- package/dist/portal/.kosh/bindings.js +15 -0
- package/dist/portal/.kosh/schema.d.ts +376 -0
- package/dist/portal/announce.d.ts +16 -0
- package/dist/portal/announce.js +117 -0
- package/dist/portal/documents.d.ts +17 -0
- package/dist/portal/documents.js +61 -0
- package/dist/portal/index.d.ts +20 -0
- package/dist/portal/index.js +65 -0
- package/dist/portal/module.d.ts +1405 -0
- package/dist/portal/module.js +84 -0
- package/dist/portal/routes-documents.d.ts +2 -0
- package/dist/portal/routes-documents.js +391 -0
- package/dist/portal/routes.d.ts +13 -0
- package/dist/portal/routes.js +313 -0
- package/dist/portal/rules.d.ts +11 -0
- package/dist/portal/rules.js +25 -0
- package/dist/portal/web/PortalAccess.d.ts +8 -0
- package/dist/portal/web/PortalAccess.js +66 -0
- package/dist/portal/web/index.d.ts +12 -0
- package/dist/portal/web/index.js +17 -0
- package/dist/portal/web/messages.d.ts +2 -0
- package/dist/portal/web/messages.js +33 -0
- package/dist/product/.kosh/bindings.js +1 -1
- package/dist/product/.kosh/schema.d.ts +42 -3
- package/dist/product/demo.js +6 -5
- package/dist/product/index.d.ts +1 -0
- package/dist/product/index.js +17 -4
- package/dist/product/module.d.ts +346 -14
- package/dist/product/module.js +48 -18
- package/dist/product/taxes.d.ts +59 -0
- package/dist/product/taxes.js +61 -0
- package/dist/project/.kosh/bindings.js +1 -1
- package/dist/project/.kosh/schema.d.ts +33 -1
- package/dist/project/module.d.ts +20 -0
- package/dist/project_portal/.kosh/bindings.d.ts +22 -0
- package/dist/project_portal/.kosh/bindings.js +15 -0
- package/dist/project_portal/.kosh/schema.d.ts +701 -0
- package/dist/project_portal/index.d.ts +9 -0
- package/dist/project_portal/index.js +42 -0
- package/dist/project_portal/module.d.ts +2142 -0
- package/dist/project_portal/module.js +22 -0
- package/dist/purchase/.kosh/bindings.js +1 -1
- package/dist/purchase/.kosh/schema.d.ts +45 -4
- package/dist/purchase/demo.js +3 -3
- package/dist/purchase/index.js +1 -1
- package/dist/purchase/module.d.ts +232 -8
- package/dist/purchase/module.js +51 -9
- package/dist/purchase_account/.kosh/bindings.js +1 -1
- package/dist/purchase_account/.kosh/schema.d.ts +174 -10
- package/dist/purchase_account/module.d.ts +2008 -641
- package/dist/purchase_account/module.js +2 -2
- package/dist/purchase_portal/.kosh/bindings.d.ts +22 -0
- package/dist/purchase_portal/.kosh/bindings.js +15 -0
- package/dist/purchase_portal/.kosh/schema.d.ts +852 -0
- package/dist/purchase_portal/index.d.ts +9 -0
- package/dist/purchase_portal/index.js +53 -0
- package/dist/purchase_portal/module.d.ts +2758 -0
- package/dist/purchase_portal/module.js +18 -0
- package/dist/reporting/documents.js +20 -3
- package/dist/reporting/index.d.ts +1 -0
- package/dist/reporting/index.js +1 -0
- package/dist/reporting/routes-documents.js +9 -2
- package/dist/reporting/words.d.ts +11 -0
- package/dist/reporting/words.js +229 -0
- package/dist/sale_account/.kosh/bindings.js +1 -1
- package/dist/sale_account/.kosh/schema.d.ts +174 -10
- package/dist/sale_account/module.d.ts +2008 -641
- package/dist/sale_account/module.js +3 -3
- package/dist/sale_portal/.kosh/bindings.d.ts +22 -0
- package/dist/sale_portal/.kosh/bindings.js +15 -0
- package/dist/sale_portal/.kosh/schema.d.ts +766 -0
- package/dist/sale_portal/index.d.ts +9 -0
- package/dist/sale_portal/index.js +71 -0
- package/dist/sale_portal/module.d.ts +2718 -0
- package/dist/sale_portal/module.js +19 -0
- package/dist/sale_project/.kosh/bindings.js +1 -1
- package/dist/sale_project/.kosh/schema.d.ts +45 -4
- package/dist/sale_project/module.d.ts +390 -10
- package/dist/sales/.kosh/bindings.js +1 -1
- package/dist/sales/.kosh/schema.d.ts +45 -4
- package/dist/sales/demo.js +3 -3
- package/dist/sales/index.js +67 -1
- package/dist/sales/module.d.ts +232 -8
- package/dist/sales/module.js +51 -12
- package/dist/server/api/client.js +1 -1
- package/dist/server/api/openapi.js +17 -1
- package/dist/server/apps.js +5 -5
- package/dist/server/auth/api-keys.d.ts +13 -0
- package/dist/server/auth/api-keys.js +65 -0
- package/dist/server/auth/base.js +26 -0
- package/dist/server/auth/crypto.js +1 -1
- package/dist/server/auth/scopes.d.ts +12 -0
- package/dist/server/auth/scopes.js +51 -0
- package/dist/server/auth/service.d.ts +59 -1
- package/dist/server/auth/service.js +236 -6
- package/dist/server/demo.d.ts +1 -1
- package/dist/server/demo.js +1 -1
- package/dist/server/env-names.d.ts +2 -0
- package/dist/server/env-names.js +16 -0
- package/dist/server/host.js +5 -1
- package/dist/server/identity.d.ts +7 -0
- package/dist/server/index.d.ts +2 -1
- package/dist/server/index.js +2 -1
- package/dist/server/registry.js +1 -1
- package/dist/server/server.d.ts +5 -0
- package/dist/server/server.js +71 -6
- package/dist/settings/routes.js +1 -1
- package/dist/settings/web/ApiKeysPage.d.ts +8 -0
- package/dist/settings/web/ApiKeysPage.js +70 -0
- package/dist/settings/web/index.d.ts +1 -0
- package/dist/settings/web/index.js +4 -0
- package/dist/settings/web/messages.js +62 -0
- package/dist/storage/routes.js +1 -1
- package/dist/studio/build.js +1 -1
- package/dist/studio/generate.js +4 -4
- package/dist/studio/module.js +5 -5
- package/dist/studio/web/messages.js +2 -2
- package/dist/ui/api.d.ts +2 -0
- package/dist/ui/app.js +27 -4
- package/dist/ui/context.d.ts +3 -1
- package/dist/ui/context.js +2 -2
- package/dist/ui/i18n/messages.js +2 -2
- package/dist/ui/index.d.ts +1 -1
- package/dist/ui/index.js +1 -1
- package/dist/ui/lib/format.d.ts +2 -0
- package/dist/ui/lib/format.js +3 -1
- package/dist/ui/pwa/worker.js +2 -2
- package/dist/ui/registry.d.ts +15 -0
- package/dist/ui/registry.js +20 -0
- package/dist/ui/views/FormView.js +19 -6
- package/package.json +52 -4
package/dist/cli/templates.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The starter project `
|
|
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 `
|
|
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: '
|
|
93
|
-
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
|
|
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
|
|
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 (
|
|
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(\`
|
|
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="
|
|
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
|
|
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 `#
|
|
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
|
|
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 (
|
|
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', ':-
|
|
349
|
+
const user = v('POSTGRES_USER', ':-shutro');
|
|
332
350
|
const lines = [
|
|
333
|
-
'#
|
|
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', ':-
|
|
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
|
|
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
|
-
'
|
|
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 [
|
|
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
|
|
422
|
-
npx
|
|
423
|
-
npx
|
|
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
|
|
426
|
-
npx
|
|
427
|
-
npx
|
|
428
|
-
npx
|
|
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
|
-
|
|
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;
|
package/dist/cli/tui.js
ADDED
|
@@ -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;
|