@mainset/cli 0.4.4-rc.1 → 0.5.0-rc.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 (42) hide show
  1. package/dist/esm/commands/index.mjs +1 -0
  2. package/dist/esm/commands/init/index.mjs +1 -0
  3. package/dist/esm/commands/init/init.mjs +113 -0
  4. package/dist/esm/commands/node-sourcer.mjs +4 -10
  5. package/dist/esm/commands/web-app.mjs +16 -4
  6. package/dist/esm/mainset-cli.mjs +2 -1
  7. package/dist/esm/utils/process-runner/process-runner.mjs +16 -3
  8. package/dist/types/commands/index.d.mts +1 -0
  9. package/dist/types/commands/init/index.d.mts +1 -0
  10. package/dist/types/commands/init/init.d.mts +3 -0
  11. package/dist/types/utils/process-runner/process-runner.d.mts +2 -2
  12. package/package.json +3 -2
  13. package/src/commands/init/templates/application/.env.example +1 -0
  14. package/src/commands/init/templates/application/config/proxy.config.d.mts +5 -0
  15. package/src/commands/init/templates/application/config/proxy.config.mjs +10 -0
  16. package/src/commands/init/templates/application/config/serve-static.config.mjs +7 -0
  17. package/src/commands/init/templates/application/config/ssr-server.config.mts +57 -0
  18. package/src/commands/init/templates/application/package.json +24 -0
  19. package/src/commands/init/templates/application/src/@types/bundler-webpack.d.ts +1 -0
  20. package/src/commands/init/templates/application/src/app.browser.tsx +7 -0
  21. package/src/commands/init/templates/application/src/app.server.tsx +15 -0
  22. package/src/commands/init/templates/application/src/index.csr.ts +9 -0
  23. package/src/commands/init/templates/application/src/index.ssr.ts +9 -0
  24. package/src/commands/init/templates/application/src/index.template.html +20 -0
  25. package/src/commands/init/templates/application/src/pages/Home/HomePage.tsx +26 -0
  26. package/src/commands/init/templates/application/src/pages/Home/home-page.module.scss +9 -0
  27. package/src/commands/init/templates/application/src/pages/Home/index.ts +1 -0
  28. package/src/commands/init/templates/application/src/pages/index.ts +1 -0
  29. package/src/commands/init/templates/application/src/styles/global.scss +1 -0
  30. package/src/commands/init/templates/application/src/styles/normalize.scss +8 -0
  31. package/src/commands/init/templates/application/tsconfig.json +9 -0
  32. package/src/commands/init/templates/configuration/.dockerignore +34 -0
  33. package/src/commands/init/templates/configuration/.husky/commit-msg +1 -0
  34. package/src/commands/init/templates/configuration/.husky/pre-commit +1 -0
  35. package/src/commands/init/templates/configuration/.prettierignore +25 -0
  36. package/src/commands/init/templates/configuration/Dockerfile +93 -0
  37. package/src/commands/init/templates/configuration/commitlint.config.mjs +3 -0
  38. package/src/commands/init/templates/configuration/compose.yaml +59 -0
  39. package/src/commands/init/templates/configuration/eslint.config.mjs +6 -0
  40. package/src/commands/init/templates/configuration/lint-staged.config.mjs +3 -0
  41. package/src/commands/init/templates/configuration/prettier.config.mjs +5 -0
  42. package/src/commands/init/templates/configuration/stylelint.config.mjs +6 -0
@@ -1,3 +1,4 @@
1
+ export * from './init/index.mjs';
1
2
  export * from './node-sourcer.mjs';
2
3
  export * from './source-code.mjs';
3
4
  export * from './web-app.mjs';
@@ -0,0 +1 @@
1
+ export * from './init.mjs';
@@ -0,0 +1,113 @@
1
+ import { execSync } from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { stdin as input, stdout as output } from 'node:process';
5
+ import * as readline from 'node:readline/promises';
6
+ import { runtimePathById } from '../../runtime/index.mjs';
7
+ import { consoleColorize, initProcessCatchErrorLogger, } from '../../utils/index.mjs';
8
+ async function handleConfigurationDependencies(targetDir) {
9
+ const rl = readline.createInterface({ input, output });
10
+ let shouldInstall = true;
11
+ try {
12
+ const answer = await rl.question('Do you want to install development dependencies (@mainset/dev-stack-fe etc.)? (Y/n) ');
13
+ shouldInstall = answer.trim().toLowerCase() !== 'n';
14
+ }
15
+ catch (_a) {
16
+ // default true
17
+ }
18
+ if (shouldInstall) {
19
+ let packageManager = 'npm';
20
+ try {
21
+ const answer = await rl.question('Which package manager do you use? (npm/pnpm/yarn/bun/other) [npm]: ');
22
+ packageManager = answer.trim().toLowerCase() || 'npm';
23
+ }
24
+ catch (_b) {
25
+ // default npm
26
+ }
27
+ rl.close();
28
+ let installCommand = '';
29
+ const packages = [
30
+ '@mainset/dev-stack-fe',
31
+ '@mainset/cli',
32
+ 'cross-env',
33
+ 'eslint',
34
+ 'husky',
35
+ 'prettier',
36
+ 'stylelint',
37
+ ].join(' ');
38
+ if (['npm', 'pnpm', 'yarn', 'bun'].includes(packageManager)) {
39
+ const cmd = packageManager === 'npm' ? 'install' : 'add';
40
+ installCommand = `${packageManager} ${cmd} -D ${packages}`;
41
+ }
42
+ else {
43
+ // assume 'other' is the binary name user typed if not in basic list
44
+ installCommand = `${packageManager} i -D ${packages}`;
45
+ }
46
+ console.log(consoleColorize('BLUE', `\nInstalling dependencies with: ${installCommand}\n`));
47
+ try {
48
+ execSync(installCommand, { cwd: targetDir, stdio: 'inherit' });
49
+ console.log(consoleColorize('BRIGHT_GREEN', '\n✅ Dependencies installed successfully.\n'));
50
+ }
51
+ catch (error) {
52
+ console.error(consoleColorize('BRIGHT_RED', `\n❌ Failed to install dependencies: ${error.message}\n`));
53
+ }
54
+ // Close readline if not installing
55
+ }
56
+ else {
57
+ rl.close();
58
+ }
59
+ }
60
+ function registerInitCommand(program) {
61
+ program
62
+ .command('init [appName]')
63
+ .description('Run init project or configuration process')
64
+ .option('-m, --mode <type>', 'Initialization mode: app, package or config', 'application')
65
+ .option('-p, --path <relativePath>', 'Initialization path', './')
66
+ .action(async (appName, options) => {
67
+ // Use runtimePathById.root to resolve the target directory relative to the project root
68
+ const targetDir = path.resolve(runtimePathById.root, options.path, appName || '');
69
+ if (options.mode === 'application') {
70
+ if (!appName) {
71
+ console.error(consoleColorize('BRIGHT_RED', `\n[mainset cli][init] App name is required for application initialization.`));
72
+ process.exit(1);
73
+ }
74
+ // ========== Application mode ==========
75
+ console.log('\n🏗️ [mainset cli] init: application');
76
+ try {
77
+ // Use runtimePathById.msCLISrc to resolve the template directory relative to the CLI runtime
78
+ const templateDir = path.resolve(runtimePathById.msCLISrc, '../../../src/commands/init/templates/application');
79
+ if (fs.existsSync(targetDir)) {
80
+ console.error(consoleColorize('BRIGHT_YELLOW', `\n[mainset cli][init] Directory "${appName}" already exists at "${targetDir}". Aborting.`));
81
+ process.exit(1);
82
+ }
83
+ fs.cpSync(templateDir, targetDir, { recursive: true });
84
+ console.log(`\n✅ Application "${appName}" initialized successfully.\n`);
85
+ }
86
+ catch (error) {
87
+ initProcessCatchErrorLogger('init', error, 'application');
88
+ }
89
+ }
90
+ else if (options.mode === 'config') {
91
+ // ========== Configuration mode ==========
92
+ console.log('\n🏗️ [mainset cli] init: configuration');
93
+ try {
94
+ const templateDir = path.resolve(runtimePathById.msCLISrc, '../../../src/commands/init/templates/configuration');
95
+ if (!fs.existsSync(templateDir)) {
96
+ console.log(consoleColorize('BRIGHT_YELLOW', `\n[mainset cli][init] Configuration template not found at ${templateDir}`));
97
+ return;
98
+ }
99
+ fs.cpSync(templateDir, targetDir, { recursive: true });
100
+ console.log('\n✅ Configuration initialized successfully.\n');
101
+ await handleConfigurationDependencies(targetDir);
102
+ }
103
+ catch (error) {
104
+ initProcessCatchErrorLogger('init', error, 'configuration');
105
+ }
106
+ }
107
+ else {
108
+ console.error(consoleColorize('BRIGHT_YELLOW', `[mainset cli][init] Unknown init mode: "${options.mode}"`));
109
+ process.exit(1);
110
+ }
111
+ });
112
+ }
113
+ export { registerInitCommand };
@@ -10,8 +10,6 @@ function registerNodeSourcerCommand(program) {
10
10
  .requiredOption('-e, --exec <type>', 'Execution mode: build or watch')
11
11
  // .option('-b, --builder <builder>', 'Builder tool (default: rslib)', 'rslib')
12
12
  .option('-c, --config <path>', 'Path to config file', './rslib.config.mts')
13
- .option('--noTypes', 'Skip type-only compilation step', false)
14
- .option('--noPurge', 'Skip purging the dist folder before build', false)
15
13
  .action((options) => {
16
14
  // Step 0: determinate command params
17
15
  const customRslibConfigPath = path.resolve(runtimePathById.root, options.config);
@@ -25,14 +23,12 @@ function registerNodeSourcerCommand(program) {
25
23
  console.log('\n🏗️ [mainset cli] node-sourcer: build');
26
24
  try {
27
25
  // Step 1: purge dist folder
28
- if (!options.noPurge)
29
- execImmediatePurgeDist();
26
+ execImmediatePurgeDist();
30
27
  // Step 2: build source code
31
28
  console.log('\n📦 Compiling Source Code with Rslib ...');
32
29
  execImmediateRslibCLICommand(`build --config ${rslibConfigPath}`);
33
30
  // Step 3: build type only
34
- if (!options.noTypes)
35
- execImmediateTypeScriptCompileTypeOnly();
31
+ execImmediateTypeScriptCompileTypeOnly();
36
32
  console.log('\n✅ Build completed successfully\n');
37
33
  }
38
34
  catch (error) {
@@ -44,8 +40,7 @@ function registerNodeSourcerCommand(program) {
44
40
  console.log('\n🏗️ [mainset cli] node-sourcer: watch');
45
41
  try {
46
42
  // Step 1: purge dist folder
47
- if (!options.noPurge)
48
- execImmediatePurgeDist();
43
+ execImmediatePurgeDist();
49
44
  // Step 2: watch source code
50
45
  runStreamingRslibCLICommand([
51
46
  'build',
@@ -54,8 +49,7 @@ function registerNodeSourcerCommand(program) {
54
49
  '--watch',
55
50
  ]);
56
51
  // Step 3: watch type only
57
- if (!options.noTypes)
58
- runStreamingTypeScriptCompileTypeOnly();
52
+ runStreamingTypeScriptCompileTypeOnly();
59
53
  }
60
54
  catch (error) {
61
55
  initProcessCatchErrorLogger('node-sourcer', error, 'watch');
@@ -48,7 +48,11 @@ function registerWebAppCommand(program) {
48
48
  execImmediatePurgeDist();
49
49
  // Step 2: build:ssr-webapp source code
50
50
  console.log('\n📦 Compiling SSR WebApp with Webpack ...');
51
- execImmediateCommand(`MS_CLI__WEBPACK_SERVE_MODE=ssr ${webpackCLICommandPath} --config ${webpackSSRConfigPath}`);
51
+ execImmediateCommand(`${webpackCLICommandPath} --config ${webpackSSRConfigPath}`, {
52
+ env: {
53
+ MS_CLI__WEBPACK_SERVE_MODE: 'ssr',
54
+ },
55
+ });
52
56
  /*
53
57
  // Step 3: build:ssr-server source code
54
58
  console.log('\n📦 Compiling SSR Server with Rslib ...');
@@ -65,7 +69,11 @@ function registerWebAppCommand(program) {
65
69
  execImmediatePurgeDist();
66
70
  // Step 2: build:csr-webapp source code
67
71
  console.log('\n📦 Compiling CSR WebApp with Webpack ...');
68
- execImmediateCommand(`MS_CLI__WEBPACK_SERVE_MODE=csr ${webpackCLICommandPath} --config ${webpackCSRConfigPath}`);
72
+ execImmediateCommand(`${webpackCLICommandPath} --config ${webpackCSRConfigPath}`, {
73
+ env: {
74
+ MS_CLI__WEBPACK_SERVE_MODE: 'csr',
75
+ },
76
+ });
69
77
  console.log('\n✅ CSR Build completed successfully\n');
70
78
  }
71
79
  }
@@ -93,7 +101,9 @@ function registerWebAppCommand(program) {
93
101
  // Step 2: watch:ssr-webapp source code of web app
94
102
  {
95
103
  runCommand: () => runStreamingCommand(webpackCLICommandPath, ['--config', webpackSSRConfigPath, '--watch'], {
96
- env: Object.assign(Object.assign({}, process.env), { MS_CLI__WEBPACK_SERVE_MODE: 'ssr' }),
104
+ env: {
105
+ MS_CLI__WEBPACK_SERVE_MODE: 'ssr',
106
+ },
97
107
  }),
98
108
  waitForOutput: 'compiled successfully',
99
109
  },
@@ -115,7 +125,9 @@ function registerWebAppCommand(program) {
115
125
  : path.resolve(runtimePathById.root, 'node_modules', '@mainset/bundler-webpack/dist/esm/webpack-config/dev-server.csr.config.mjs');
116
126
  // Step 1: watch:csr-server / start:csr-server source code
117
127
  runStreamingCommand(webpackCLICommandPath, ['serve', '--config', webpackDevServerConfigPath, '--open'], {
118
- env: Object.assign(Object.assign({}, process.env), { MS_CLI__WEBPACK_SERVE_MODE: 'csr' }),
128
+ env: {
129
+ MS_CLI__WEBPACK_SERVE_MODE: 'csr',
130
+ },
119
131
  });
120
132
  }
121
133
  }
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
- import { registerNodeSourcerCommand, registerSourceCodeCommand, registerWebAppCommand, } from './commands/index.mjs';
3
+ import { registerInitCommand, registerNodeSourcerCommand, registerSourceCodeCommand, registerWebAppCommand, } from './commands/index.mjs';
4
4
  import { verifyOrSetNodeEnv } from './utils/index.mjs';
5
5
  // !IMPORTANT: Set NODE_ENV is case it is not passed
6
6
  verifyOrSetNodeEnv();
@@ -9,6 +9,7 @@ program
9
9
  .name('ms-cli')
10
10
  .description('CLI to manage frontend tooling and infrastructure')
11
11
  .version('0.1.0');
12
+ registerInitCommand(program);
12
13
  registerSourceCodeCommand(program);
13
14
  registerNodeSourcerCommand(program);
14
15
  registerWebAppCommand(program);
@@ -1,3 +1,14 @@
1
+ var __rest = (this && this.__rest) || function (s, e) {
2
+ var t = {};
3
+ for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
4
+ t[p] = s[p];
5
+ if (s != null && typeof Object.getOwnPropertySymbols === "function")
6
+ for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
7
+ if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
8
+ t[p[i]] = s[p[i]];
9
+ }
10
+ return t;
11
+ };
1
12
  import { execSync, spawn } from 'child_process';
2
13
  import { consoleColorize } from '../index.mjs';
3
14
  import { processManager } from './process-manager.mjs';
@@ -22,9 +33,10 @@ function initProcessCatchErrorLogger(commandName, error, commandParam) {
22
33
  *
23
34
  * Example: execImmediateCommand('rspack --config ./config/rspack.config.ts');
24
35
  */
25
- function execImmediateCommand(fullCommandString) {
36
+ function execImmediateCommand(fullCommandString, options = {}) {
26
37
  try {
27
- execSync(fullCommandString, { stdio: 'inherit' });
38
+ const { env } = options, restExecSyncOptions = __rest(options, ["env"]);
39
+ execSync(fullCommandString, Object.assign({ stdio: 'inherit', env: Object.assign(Object.assign({}, process.env), env) }, restExecSyncOptions));
28
40
  }
29
41
  catch (error) {
30
42
  initProcessCatchErrorLogger('execImmediateCommand', error);
@@ -38,7 +50,8 @@ function execImmediateCommand(fullCommandString) {
38
50
  */
39
51
  function runStreamingCommand(command, args, options = {}) {
40
52
  try {
41
- const child = spawn(command, args, Object.assign({ stdio: 'inherit', shell: true }, options));
53
+ const { env } = options, restSpawnOptions = __rest(options, ["env"]);
54
+ const child = spawn(command, args, Object.assign({ stdio: 'inherit', shell: true, env: Object.assign(Object.assign({}, process.env), env) }, restSpawnOptions));
42
55
  // Track the process using ProcessManager as it could live in the background after crashing
43
56
  // Examples: server stacks when files removed while server is running
44
57
  // 1. rm -rf ./dist
@@ -1,3 +1,4 @@
1
+ export * from './init/index.mjs';
1
2
  export * from './node-sourcer.mjs';
2
3
  export * from './source-code.mjs';
3
4
  export * from './web-app.mjs';
@@ -0,0 +1 @@
1
+ export * from './init.mjs';
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ declare function registerInitCommand(program: Command): void;
3
+ export { registerInitCommand };
@@ -1,4 +1,4 @@
1
- import type { SpawnOptions } from 'child_process';
1
+ import type { ExecSyncOptions, SpawnOptions } from 'child_process';
2
2
  declare function initProcessCatchErrorLogger(commandName: string, error: Error | unknown, commandParam?: string): void;
3
3
  /**
4
4
  * Executes a short-lived command synchronously.
@@ -6,7 +6,7 @@ declare function initProcessCatchErrorLogger(commandName: string, error: Error |
6
6
  *
7
7
  * Example: execImmediateCommand('rspack --config ./config/rspack.config.ts');
8
8
  */
9
- declare function execImmediateCommand(fullCommandString: string): void;
9
+ declare function execImmediateCommand(fullCommandString: string, options?: ExecSyncOptions): void;
10
10
  /**
11
11
  * Runs a long-lived streaming command asynchronously.
12
12
  * The command and its arguments must be passed separately.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mainset/cli",
3
- "version": "0.4.4-rc.1",
3
+ "version": "0.5.0-rc.1",
4
4
  "description": "A unified CLI tool for accelerating development, based on mainset vision of front-end infrastructure",
5
5
  "homepage": "https://github.com/mainset/dev-stack-fe/tree/main/packages/cli",
6
6
  "bugs": {
@@ -15,7 +15,8 @@
15
15
  "type": "module",
16
16
  "sideEffects": false,
17
17
  "files": [
18
- "dist"
18
+ "dist",
19
+ "src/commands/init/templates"
19
20
  ],
20
21
  "exports": {
21
22
  "./runtime": {
@@ -0,0 +1 @@
1
+ API_REMOTE_URL=https://httpbin.org
@@ -0,0 +1,5 @@
1
+ import type { SSRConfigParams } from '@mainset/cli/ssr-server';
2
+
3
+ declare const proxyConfigByPath: SSRConfigParams['proxyConfigByPath'];
4
+
5
+ export default proxyConfigByPath;
@@ -0,0 +1,10 @@
1
+ const proxyConfigByPath = {
2
+ '/api-example/': {
3
+ context: ['/api-example/'],
4
+ target: process.env.API_REMOTE_URL,
5
+ pathRewrite: { '^/api-example/': '/' },
6
+ changeOrigin: true,
7
+ },
8
+ };
9
+
10
+ export default proxyConfigByPath;
@@ -0,0 +1,7 @@
1
+ import proxyConfigByPath from './proxy.config.mjs';
2
+
3
+ const serveStaticConfig = {
4
+ proxyConfigByPath,
5
+ };
6
+
7
+ export default serveStaticConfig;
@@ -0,0 +1,57 @@
1
+ import { runtimePathById } from '@mainset/cli/runtime';
2
+ import type { SSRConfigParams } from '@mainset/cli/ssr-server';
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import ReactDOMServer from 'react-dom/server';
6
+
7
+ import provideServerReactApp from '../src/app.server';
8
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
9
+ // @ts-ignore
10
+ import proxyConfigByPath from './proxy.config.mjs';
11
+
12
+ const ssrServerConfig: SSRConfigParams = {
13
+ serveStatics: [
14
+ {
15
+ rootPath: path.join(runtimePathById.dist, 'public'),
16
+ },
17
+ ],
18
+ proxyConfigByPath,
19
+ renderSSRContentByPath: {
20
+ '/{*any}': ({ reqUrl, fullUrl }) =>
21
+ new Promise((resolve, reject) => {
22
+ fs.readFile(
23
+ path.join(runtimePathById.dist, 'public', 'server.html'),
24
+ 'utf8',
25
+ async (err, htmlData) => {
26
+ if (err) {
27
+ return reject(err);
28
+ }
29
+
30
+ // Const context = {}; // Context to track SSR rendering information
31
+
32
+ try {
33
+ // Pass both requestUrl (relative) and fullUrl to provider of React App for server-side rendering
34
+ const appHtml = await provideServerReactApp({
35
+ reqUrl,
36
+ fullUrl,
37
+ // Context
38
+ });
39
+
40
+ const content = htmlData.replace(
41
+ '<div id="example-react"></div>',
42
+ `<div id="example-react">${ReactDOMServer.renderToString(
43
+ appHtml,
44
+ )}</div>`,
45
+ );
46
+
47
+ return resolve(content);
48
+ } catch (error) {
49
+ return reject(error);
50
+ }
51
+ },
52
+ );
53
+ }),
54
+ },
55
+ };
56
+
57
+ export default ssrServerConfig;
@@ -0,0 +1,24 @@
1
+ {
2
+ "engines": {
3
+ "node": ">=24.14.0"
4
+ },
5
+ "scripts": {
6
+ "dev": "cross-env NODE_ENV=development ms-cli web-app --exec serve",
7
+ "dev:csr": "cross-env NODE_ENV=development ms-cli web-app --exec serve --serveMode csr",
8
+ "build": "ms-cli web-app --exec build",
9
+ "build:csr": "ms-cli web-app --exec build --serveMode csr",
10
+ "serve": "ms-cli web-app --exec serve-static",
11
+ "serve:csr": "ms-cli web-app --exec serve-static --serveMode csr"
12
+ },
13
+ "dependencies": {
14
+ "react": "^19.2.0",
15
+ "react-dom": "^19.2.0"
16
+ },
17
+ "devDependencies": {
18
+ "@mainset/bundler-webpack": "^0.2.0",
19
+ "@mainset/cli": "^0.4.4",
20
+ "@mainset/dev-stack-fe": "^0.3.1",
21
+ "@types/react": "^19.2.2",
22
+ "@types/react-dom": "^19.2.1"
23
+ }
24
+ }
@@ -0,0 +1 @@
1
+ import '@mainset/bundler-webpack/global-types';
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+
3
+ import { HomePage } from './pages';
4
+
5
+ const BrowserReactApp = <HomePage />;
6
+
7
+ export default BrowserReactApp;
@@ -0,0 +1,15 @@
1
+ import BrowserReactApp from './app.browser';
2
+
3
+ type ProvideServerReactAppParams = {
4
+ reqUrl: string;
5
+ fullUrl: string;
6
+ // Context?: Record<string, any>;
7
+ };
8
+
9
+ const provideServerReactApp = (params?: ProvideServerReactAppParams) => {
10
+ console.log({ params });
11
+
12
+ return BrowserReactApp;
13
+ };
14
+
15
+ export default provideServerReactApp;
@@ -0,0 +1,9 @@
1
+ import ReactDOMClient from 'react-dom/client';
2
+
3
+ import BrowserReactApp from './app.browser';
4
+
5
+ const targetContainer = document.getElementById('example-react');
6
+
7
+ if (targetContainer) {
8
+ ReactDOMClient.createRoot(targetContainer).render(BrowserReactApp);
9
+ }
@@ -0,0 +1,9 @@
1
+ import ReactDOMClient from 'react-dom/client';
2
+
3
+ import BrowserReactApp from './app.browser';
4
+
5
+ const targetContainer = document.getElementById('example-react');
6
+
7
+ if (targetContainer) {
8
+ ReactDOMClient.hydrateRoot(targetContainer, BrowserReactApp);
9
+ }
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <!-- Required meta tags -->
5
+ <meta charset="utf-8" />
6
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
7
+ <meta
8
+ name="viewport"
9
+ content="width=device-width, initial-scale=1, shrink-to-fit=no"
10
+ />
11
+
12
+ <!-- styles -->
13
+ </head>
14
+
15
+ <body>
16
+ <div id="example-react"></div>
17
+
18
+ <!-- SPA -->
19
+ </body>
20
+ </html>
@@ -0,0 +1,26 @@
1
+ import React, { useEffect } from 'react';
2
+
3
+ import * as styles from './home-page.module.scss';
4
+
5
+ const HomePage = () => {
6
+ const [apiResponse, setApiResponse] = React.useState<{
7
+ origin: string;
8
+ } | null>(null);
9
+
10
+ useEffect(() => {
11
+ fetch('/api-example/ip')
12
+ .then((response) => response.json())
13
+ .then((responseAsJson) => {
14
+ setApiResponse(responseAsJson);
15
+ });
16
+ }, []);
17
+
18
+ return (
19
+ <div className={styles['home-page__welcome-section']}>
20
+ <h1>Hello world</h1>
21
+ <p>Your IP is: {apiResponse?.origin || '...'}</p>
22
+ </div>
23
+ );
24
+ };
25
+
26
+ export { HomePage };
@@ -0,0 +1,9 @@
1
+ .home-page {
2
+ &__welcome-section {
3
+ display: flex;
4
+ align-items: center;
5
+ justify-content: center;
6
+ flex-direction: column;
7
+ height: 100vh;
8
+ }
9
+ }
@@ -0,0 +1 @@
1
+ export * from './HomePage';
@@ -0,0 +1 @@
1
+ export { HomePage } from './Home';
@@ -0,0 +1 @@
1
+ @use './normalize.scss';
@@ -0,0 +1,8 @@
1
+ body,
2
+ h1,
3
+ h2,
4
+ h3,
5
+ hr,
6
+ p {
7
+ margin: 0;
8
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": [
3
+ "@mainset/dev-stack-fe/tsconfig--bundler",
4
+ "@mainset/dev-stack-fe/tschunk--react"
5
+ ],
6
+ "compilerOptions": {
7
+ "outDir": "./dist/esm"
8
+ }
9
+ }
@@ -0,0 +1,34 @@
1
+ # Include any files or directories that you don't want to be copied to your
2
+ # container here (e.g., local build artifacts, temporary files, etc.).
3
+ #
4
+ # For more help, visit the .dockerignore file reference guide at
5
+ # https://docs.docker.com/go/build-context-dockerignore/
6
+
7
+ **/.classpath
8
+ **/.dockerignore
9
+ **/.env
10
+ **/.git
11
+ **/.gitignore
12
+ **/.project
13
+ **/.settings
14
+ **/.toolstarget
15
+ **/.vs
16
+ **/.vscode
17
+ **/.next
18
+ **/.cache
19
+ **/*.*proj.user
20
+ **/*.dbmdl
21
+ **/*.jfm
22
+ **/charts
23
+ **/docker-compose*
24
+ **/compose.y*ml
25
+ **/Dockerfile*
26
+ **/node_modules
27
+ **/npm-debug.log
28
+ **/obj
29
+ **/secrets.dev.yaml
30
+ **/values.dev.yaml
31
+ **/build
32
+ **/dist
33
+ LICENSE
34
+ README.md
@@ -0,0 +1 @@
1
+ npx commitlint@^19 --edit
@@ -0,0 +1 @@
1
+ npx lint-staged@^16
@@ -0,0 +1,25 @@
1
+ # general
2
+ LICENSE
3
+ .gitignore
4
+ .keep
5
+
6
+ # configuration files
7
+ .dockerignore
8
+ Dockerfile
9
+
10
+ # package manager
11
+ pnpm-lock.yaml
12
+ .npmrc.example
13
+
14
+ # packages
15
+ .prettierignore
16
+ .husky
17
+
18
+ # files
19
+ *.sh
20
+ *.toml
21
+ *.env*
22
+
23
+ # images
24
+ *.ico
25
+ *.svg
@@ -0,0 +1,93 @@
1
+ # syntax=docker/dockerfile:1
2
+
3
+ # Comments are provided throughout this file to help you get started.
4
+ # If you need more help, visit the Dockerfile reference guide at
5
+ # https://docs.docker.com/go/dockerfile-reference/
6
+
7
+ # Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
8
+
9
+ ARG NODE_VERSION=24.14.0
10
+ ARG PNPM_VERSION=10.30.3
11
+
12
+ FROM node:${NODE_VERSION}-alpine as base
13
+
14
+ # Install pnpm.
15
+ RUN --mount=type=cache,target=/root/.npm \
16
+ npm install -g pnpm@${PNPM_VERSION}
17
+
18
+ WORKDIR /usr/src/web-app--react
19
+
20
+ ################################################################################
21
+ # Create a stage for installing production dependencies.
22
+ FROM base AS install-dependencies
23
+
24
+ # Download dependencies as a separate step to take advantage of Docker's caching.
25
+ # Leverage a cache mount to /root/.local/share/pnpm/store to speed up subsequent builds.
26
+ # Leverage a bind mounts to package.json and pnpm-lock.yaml to avoid having to copy them into
27
+ # into this layer.
28
+ RUN \
29
+ # mount root files
30
+ --mount=type=bind,source=package.json,target=package.json \
31
+ --mount=type=bind,source=pnpm-lock.yaml,target=pnpm-lock.yaml \
32
+ # mount cache and install dependencies
33
+ --mount=type=cache,target=/root/.local/share/pnpm/store \
34
+ (\
35
+ # Install dependencies with pnpm
36
+ # pnpm install --prod --frozen-lockfile
37
+ pnpm install --frozen-lockfile \
38
+ )
39
+
40
+ ################################################################################
41
+ # Create a stage for building the packages source code for Server Side Rendering
42
+ FROM install-dependencies as build-ssr
43
+
44
+ # Copy the rest of the source files into the image.
45
+ COPY . .
46
+
47
+ # Run the build script.
48
+ RUN pnpm run build
49
+
50
+ ################################################################################
51
+ # Create a new {production} stage that will be running the application
52
+ FROM install-dependencies AS production
53
+
54
+ # Use production node environment
55
+ ENV NODE_ENV production
56
+
57
+ # Run the application as a non-root user.
58
+ USER node
59
+
60
+ # Copy compiled {/dist} code from the {build} Docker stage into the image.
61
+ COPY --chown=node:node --from=build-ssr /usr/src/web-app--react/dist ./dist
62
+ # Copy other required files and folders to launch the application
63
+ COPY ./package.json ./
64
+ COPY ./config ./config
65
+
66
+ # Expose the port that the application listens on.
67
+ EXPOSE 8080
68
+
69
+ # Run the application.
70
+ CMD ["pnpm", "run", "serve"]
71
+
72
+ ################################################################################
73
+ # Create a new {development} stage that will be running the application
74
+ FROM install-dependencies AS development
75
+
76
+ # Use production node environment
77
+ ENV NODE_ENV development
78
+
79
+ # Run the application as a non-root user.
80
+ USER node
81
+
82
+ # Copy compiled {/dist} code from the {build} Docker stage into the image.
83
+ COPY --chown=node:node --from=build-ssr /usr/src/web-app--react/dist ./dist
84
+
85
+ # Copy the rest of the source files into the image.
86
+ # !IMPORTANT: the {--chown=node:node} required, server crashes on file change, files re-generated with root permissions
87
+ COPY --chown=node:node . .
88
+
89
+ # Expose the port that the application listens on.
90
+ EXPOSE 8080
91
+
92
+ # Run the application.
93
+ CMD ["pnpm", "run", "dev"]
@@ -0,0 +1,3 @@
1
+ import { mainsetCommitlintConfig } from '@mainset/dev-stack-fe/commitlint';
2
+
3
+ export default mainsetCommitlintConfig;
@@ -0,0 +1,59 @@
1
+ # Comments are provided throughout this file to help you get started.
2
+ # If you need more help, visit the Docker Compose reference guide at
3
+ # https://docs.docker.com/go/compose-spec-reference/
4
+
5
+ # Here the instructions define your application as a service called "server".
6
+ # This service is built from the Dockerfile in the current directory.
7
+ # You can add other services your application may depend on here, such as a
8
+ # database or a cache. For examples, see the Awesome Compose repository:
9
+ # https://github.com/docker/awesome-compose
10
+ services:
11
+ web-app--react:
12
+ build:
13
+ context: .
14
+ target: production
15
+ # NOTE: start production service by default without {--profile} flag
16
+ profiles: [prod]
17
+ env_file:
18
+ - ./.env
19
+ ports:
20
+ - 8080:8080
21
+ labels:
22
+ - 'traefik.enable=true'
23
+ - 'traefik.http.routers.web-app--react.rule=Host(`react-boilerplate.mainset.${DEV__DOMAIN_LOCALHOST:-localhost}`)'
24
+ - 'traefik.http.services.web-app--react.loadbalancer.server.port=8080'
25
+ networks:
26
+ - network--dev
27
+ web-app--react-dev:
28
+ extends: web-app--react
29
+ build:
30
+ target: development
31
+ env_file:
32
+ - ./.env.development
33
+ volumes:
34
+ # Bind Mount code for Live Update
35
+ - ./src:/usr/src/web-app--react/src
36
+ # !IMPORTANT: ensure that the volume mounting does not overwrite any of the {node_modules} directory
37
+ - /usr/src/web-app--react/node_modules
38
+ # !IMPORTANT: ensure that the volume mounting does not overwrite any of the {dist} static directory
39
+ - /usr/src/web-app--react/dist
40
+ profiles: [dev]
41
+
42
+ # development service
43
+ # make the service available under custom domain on local computer
44
+ traefik:
45
+ image: traefik:v2.5
46
+ command:
47
+ - '--api.insecure=true'
48
+ - '--providers.docker=true'
49
+ - '--entrypoints.web.address=:80'
50
+ ports:
51
+ - '80:80'
52
+ volumes:
53
+ - '/var/run/docker.sock:/var/run/docker.sock:ro'
54
+ networks:
55
+ - network--dev
56
+
57
+ # development
58
+ networks:
59
+ network--dev:
@@ -0,0 +1,6 @@
1
+ import {
2
+ defineConfig,
3
+ eslintMainsetConfig,
4
+ } from '@mainset/dev-stack-fe/eslint';
5
+
6
+ export default defineConfig([...eslintMainsetConfig]);
@@ -0,0 +1,3 @@
1
+ import { mainsetLintStaged } from '@mainset/dev-stack-fe/lint-staged';
2
+
3
+ export default mainsetLintStaged;
@@ -0,0 +1,5 @@
1
+ import mainsetPrettierConfig from '@mainset/dev-stack-fe/prettier';
2
+
3
+ export default {
4
+ ...mainsetPrettierConfig,
5
+ };
@@ -0,0 +1,6 @@
1
+ import mainsetStylelintConfig from '@mainset/dev-stack-fe/stylelint';
2
+
3
+ /** @type {import('stylelint').Config} */
4
+ export default {
5
+ ...mainsetStylelintConfig,
6
+ };