@gooddata/create-pluggable-module 11.40.0-alpha.3

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 (93) hide show
  1. package/README.md +59 -0
  2. package/dist/templates/harness/.env.template +9 -0
  3. package/dist/templates/harness/.gitignore +10 -0
  4. package/dist/templates/harness/README.md +45 -0
  5. package/dist/templates/harness/eslint.config.ts +3 -0
  6. package/dist/templates/harness/index.html +13 -0
  7. package/dist/templates/harness/package.json +70 -0
  8. package/dist/templates/harness/src/app.tsx +81 -0
  9. package/dist/templates/harness/tsconfig.json +70 -0
  10. package/dist/templates/harness/typings.d.ts +13 -0
  11. package/dist/templates/harness/vite.config.ts +111 -0
  12. package/dist/templates/module/.gitignore +11 -0
  13. package/dist/templates/module/README.md +35 -0
  14. package/dist/templates/module/eslint.config.ts +3 -0
  15. package/dist/templates/module/package.json +84 -0
  16. package/dist/templates/module/src/App.test.tsx +53 -0
  17. package/dist/templates/module/src/App.tsx +34 -0
  18. package/dist/templates/module/src/AppProviders.tsx +26 -0
  19. package/dist/templates/module/src/index.ts +3 -0
  20. package/dist/templates/module/src/pluggableApp.tsx +56 -0
  21. package/dist/templates/module/src/translations/de-DE.json +6 -0
  22. package/dist/templates/module/src/translations/en-AU.json +6 -0
  23. package/dist/templates/module/src/translations/en-GB.json +6 -0
  24. package/dist/templates/module/src/translations/en-US.json +6 -0
  25. package/dist/templates/module/src/translations/es-419.json +6 -0
  26. package/dist/templates/module/src/translations/es-ES.json +6 -0
  27. package/dist/templates/module/src/translations/fi-FI.json +6 -0
  28. package/dist/templates/module/src/translations/fr-CA.json +6 -0
  29. package/dist/templates/module/src/translations/fr-FR.json +6 -0
  30. package/dist/templates/module/src/translations/id-ID.json +6 -0
  31. package/dist/templates/module/src/translations/it-IT.json +6 -0
  32. package/dist/templates/module/src/translations/ja-JP.json +6 -0
  33. package/dist/templates/module/src/translations/ko-KR.json +6 -0
  34. package/dist/templates/module/src/translations/nl-NL.json +6 -0
  35. package/dist/templates/module/src/translations/pl-PL.json +6 -0
  36. package/dist/templates/module/src/translations/pt-BR.json +6 -0
  37. package/dist/templates/module/src/translations/pt-PT.json +6 -0
  38. package/dist/templates/module/src/translations/ru-RU.json +6 -0
  39. package/dist/templates/module/src/translations/sl-SI.json +6 -0
  40. package/dist/templates/module/src/translations/th-TH.json +6 -0
  41. package/dist/templates/module/src/translations/tr-TR.json +6 -0
  42. package/dist/templates/module/src/translations/translations.ts +71 -0
  43. package/dist/templates/module/src/translations/uk-UA.json +6 -0
  44. package/dist/templates/module/src/translations/vi-VN.json +6 -0
  45. package/dist/templates/module/src/translations/zh-HK.json +6 -0
  46. package/dist/templates/module/src/translations/zh-Hans.json +6 -0
  47. package/dist/templates/module/src/translations/zh-Hant.json +6 -0
  48. package/dist/templates/module/tsconfig.json +65 -0
  49. package/dist/templates/module/vite.config.ts +140 -0
  50. package/dist/templates/module/vitest.config.ts +12 -0
  51. package/esm/engine/__tests__/anchors.test.d.ts +1 -0
  52. package/esm/engine/__tests__/anchors.test.js +44 -0
  53. package/esm/engine/__tests__/copyTemplate.test.d.ts +1 -0
  54. package/esm/engine/__tests__/copyTemplate.test.js +59 -0
  55. package/esm/engine/__tests__/derived.test.d.ts +1 -0
  56. package/esm/engine/__tests__/derived.test.js +30 -0
  57. package/esm/engine/__tests__/jsonAnchors.test.d.ts +1 -0
  58. package/esm/engine/__tests__/jsonAnchors.test.js +52 -0
  59. package/esm/engine/__tests__/repoDetection.test.d.ts +1 -0
  60. package/esm/engine/__tests__/repoDetection.test.js +63 -0
  61. package/esm/engine/__tests__/scrubPackageJsonVersions.d.ts +1 -0
  62. package/esm/engine/__tests__/scrubPackageJsonVersions.js +34 -0
  63. package/esm/engine/__tests__/scrubPackageJsonVersions.test.d.ts +1 -0
  64. package/esm/engine/__tests__/scrubPackageJsonVersions.test.js +79 -0
  65. package/esm/engine/__tests__/snapshot.test.d.ts +1 -0
  66. package/esm/engine/__tests__/snapshot.test.js +93 -0
  67. package/esm/engine/anchors.d.ts +17 -0
  68. package/esm/engine/anchors.js +43 -0
  69. package/esm/engine/checkAnchors.d.ts +12 -0
  70. package/esm/engine/checkAnchors.js +44 -0
  71. package/esm/engine/copyTemplate.d.ts +11 -0
  72. package/esm/engine/copyTemplate.js +70 -0
  73. package/esm/engine/derived.d.ts +32 -0
  74. package/esm/engine/derived.js +85 -0
  75. package/esm/engine/jsonAnchors.d.ts +13 -0
  76. package/esm/engine/jsonAnchors.js +42 -0
  77. package/esm/engine/prompts.d.ts +15 -0
  78. package/esm/engine/prompts.js +157 -0
  79. package/esm/engine/repoDetection.d.ts +18 -0
  80. package/esm/engine/repoDetection.js +47 -0
  81. package/esm/engine/runProfile.d.ts +18 -0
  82. package/esm/engine/runProfile.js +179 -0
  83. package/esm/engine/spawn.d.ts +13 -0
  84. package/esm/engine/spawn.js +30 -0
  85. package/esm/index.d.ts +10 -0
  86. package/esm/index.js +62 -0
  87. package/esm/profiles/__tests__/client.test.d.ts +1 -0
  88. package/esm/profiles/__tests__/client.test.js +151 -0
  89. package/esm/profiles/client.d.ts +2 -0
  90. package/esm/profiles/client.js +208 -0
  91. package/esm/types.d.ts +194 -0
  92. package/esm/types.js +2 -0
  93. package/package.json +77 -0
package/README.md ADDED
@@ -0,0 +1,59 @@
1
+ # @gooddata/create-pluggable-module
2
+
3
+ Scaffolder for GoodData pluggable applications. Generates a `module/` (the federated app) and `harness/` (a thin host for running it standalone) under your repo's `modules/` directory, with all the wiring required to build, run, and federate the result.
4
+
5
+ ## Usage
6
+
7
+ ```sh
8
+ npm init @gooddata/pluggable-module
9
+ ```
10
+
11
+ `npm init` resolves this package and runs its CLI. You'll be prompted for:
12
+
13
+ - **App name** — must start with `gdc-` (e.g. `gdc-revenue-tracker`)
14
+ - **Title** — the en-US display name shown in the host menu
15
+ - **Scope** — `workspace` (per-workspace app) or `organization` (org-wide app)
16
+ - **Destination path** _(optional)_ — defaults to `./modules/<app-name>/`
17
+
18
+ The scaffolder detects your package manager from the repo's lockfile (`npm`, `yarn`, `pnpm`) and runs `install` once the files are in place. If a `rush.json` is found, it also registers the new packages in it so `rush update` picks them up.
19
+
20
+ ## What you get
21
+
22
+ ```
23
+ modules/<app-name>/
24
+ ├── module/ # The federated pluggable app — Module Federation remote, built with Vite.
25
+ └── harness/ # Standalone host that imports the module directly for fast local dev.
26
+ ```
27
+
28
+ Each side ships its own `package.json`, `tsconfig.json`, `vite.config.ts`, eslint config, and starter source files. SDK dependencies are stamped at the matching `@gooddata/sdk-*` release version (see _Version coupling_ below).
29
+
30
+ Once scaffolded:
31
+
32
+ ```sh
33
+ cd modules/<app-name>/harness
34
+ cp .env.template .env # then fill in TIGER_API_TOKEN
35
+ npm run dev # fast local dev loop — harness imports module source directly
36
+ ```
37
+
38
+ To run the module as a federation remote against the harness (the production-shape dev loop):
39
+
40
+ ```sh
41
+ cd modules/<app-name>/module
42
+ npm run dev # serves remoteEntry.js at https://localhost:<modulePort>/
43
+ ```
44
+
45
+ ## Version coupling
46
+
47
+ This package ships on the same release train as the rest of the GoodData SDK (`versionPolicyName: "sdk"`). Scaffolded `module/package.json` and `harness/package.json` get their `@gooddata/sdk-*` deps stamped with this scaffolder's version, so the templates always align with the SDK release they were generated against. Upgrading a scaffolded app means bumping all `@gooddata/sdk-*` deps in lockstep.
48
+
49
+ ## Programmatic API
50
+
51
+ The CLI is a thin wrapper over an exported engine. Consumers building their own scaffolders can import the engine directly:
52
+
53
+ ```ts
54
+ import { runProfile, type IScaffoldProfile } from "@gooddata/create-pluggable-module";
55
+
56
+ await runProfile(myCustomProfile);
57
+ ```
58
+
59
+ See `src/types.ts` for the `IScaffoldProfile` contract.
@@ -0,0 +1,9 @@
1
+ BACKEND_URL=
2
+ TIGER_API_TOKEN=
3
+
4
+ # Optional: URL of this module's Module Federation remote entry for local development.
5
+ # When set, the harness loads the pluggable app from the federation remote instead of
6
+ # importing the module's source directly via the workspace.
7
+ # Run `npm run dev` in modules/gdc-app-template-name/module and set this to the
8
+ # remoteEntry.js URL (typically https://localhost:{modulePort}/remoteEntry.js).
9
+ # APP_TEMPLATE_REMOTE_URL=
@@ -0,0 +1,10 @@
1
+ node_modules
2
+ dist
3
+ .vite
4
+ rush-logs
5
+ .rush
6
+ .idea
7
+ .vscode
8
+ .DS_Store
9
+ .env
10
+ *.tgz
@@ -0,0 +1,45 @@
1
+ # gdc-app-template-name-harness
2
+
3
+ Standalone development harness for **gdc-app-template-name-module**.
4
+
5
+ This is a template. Run `rush init-pluggable-app` to scaffold a new application from it.
6
+
7
+ ## What this does
8
+
9
+ The harness runs the pluggable application module outside of the full host shell. It provides:
10
+
11
+ - A Vite dev server with backend API proxying
12
+ - Local application registration (the module is lazy-loaded the same way the host would load it)
13
+ - GoodData UI Kit CSS and Indigo font
14
+ - HTTPS support via local certificates (optional)
15
+
16
+ ## Development
17
+
18
+ ```bash
19
+ # Copy environment template and fill in your API token
20
+ cp .env.template .env
21
+
22
+ # Start dev server
23
+ rushx dev
24
+ ```
25
+
26
+ The server starts at `https://localhost:<port>`. Navigate to the appropriate scope URL:
27
+
28
+ - **Organization-scoped apps**: `https://localhost:<port>/organization/<route>`
29
+ - **Workspace-scoped apps**: `https://localhost:<port>/workspace/<workspaceId>/<route>`
30
+
31
+ ## Configuration
32
+
33
+ | Variable | Default | Description |
34
+ | ----------------- | ------------- | -------------------------------------- |
35
+ | `BACKEND_URL` | _(required)_ | GoodData backend to proxy API calls to |
36
+ | `TIGER_API_TOKEN` | _(required)_ | API token for authentication |
37
+ | `PORT` | {harnessPort} | Dev server port |
38
+
39
+ ## Build
40
+
41
+ ```bash
42
+ rushx build
43
+ ```
44
+
45
+ Produces a production bundle in `dist/`.
@@ -0,0 +1,3 @@
1
+ // (C) 2026 GoodData Corporation
2
+
3
+ export { default } from "@gooddata/eslint-config/oxlint-esm-react-vitest";
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>{applicationTemplateTitle} — Harness</title>
7
+ </head>
8
+ <body>
9
+ <noscript>You need to enable JavaScript to run this app.</noscript>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/app.tsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "gdc-app-template-name-harness",
3
+ "version": "11.40.0-alpha.3",
4
+ "private": true,
5
+ "description": "Standalone harness for gdc-app-template-name module",
6
+ "license": "UNLICENSED",
7
+ "author": {
8
+ "name": "Stellar",
9
+ "email": "stellar@gooddata.com"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/gooddata/gdc-ui/",
14
+ "directory": "modules/gdc-app-template-name/harness"
15
+ },
16
+ "type": "module",
17
+ "exports": {
18
+ "./tsconfig.json": "./tsconfig.json"
19
+ },
20
+ "scripts": {
21
+ "_phase:build": "npm run build-check",
22
+ "_phase:validate": "npm run validate",
23
+ "build-check": "tsgo",
24
+ "clean": "../../../../common/scripts/clean-command-state.sh && rm -rf dist *.tgz *.log tsconfig.tsbuildinfo",
25
+ "format-check": "oxfmt --check .",
26
+ "format-write": "oxfmt .",
27
+ "lint": "oxlint . --type-aware --quiet && eslint .",
28
+ "lint-fix": "oxlint . --type-aware --quiet --fix && eslint . --fix",
29
+ "validate": "npm run lint && npm run format-check"
30
+ },
31
+ "dependencies": {
32
+ "@gooddata/sdk-model": "workspace:*",
33
+ "@gooddata/sdk-ui-kit": "workspace:*",
34
+ "@gooddata/sdk-ui-pluggable-host": "workspace:*",
35
+ "gdc-app-template-name-module": "workspace:*",
36
+ "react": "19.1.1",
37
+ "react-dom": "19.1.1",
38
+ "react-intl": "7.1.11",
39
+ "react-router": "7.13.1"
40
+ },
41
+ "devDependencies": {
42
+ "@gooddata/eslint-config": "workspace:*",
43
+ "@gooddata/oxlint-config": "workspace:*",
44
+ "@types/node": "24.12.0",
45
+ "@types/react": "19.1.11",
46
+ "@types/react-dom": "19.1.7",
47
+ "@typescript-eslint/eslint-plugin": "8.58.0",
48
+ "@typescript-eslint/parser": "8.58.0",
49
+ "@typescript/native-preview": "7.0.0-dev.20260202.1",
50
+ "@vitejs/plugin-react": "^5.1.2",
51
+ "eslint": "^9.39.2",
52
+ "eslint-import-resolver-typescript": "4.4.4",
53
+ "eslint-plugin-headers": "1.3.3",
54
+ "eslint-plugin-import-x": "4.16.1",
55
+ "eslint-plugin-jsdoc": "62.1.0",
56
+ "eslint-plugin-no-barrel-files": "1.2.2",
57
+ "eslint-plugin-no-only-tests": "3.3.0",
58
+ "eslint-plugin-react": "7.37.5",
59
+ "eslint-plugin-react-hooks": "5.2.0",
60
+ "eslint-plugin-sonarjs": "3.0.6",
61
+ "jiti": "2.6.1",
62
+ "npm-run-all": "^4.1.5",
63
+ "oxfmt": "0.45.0",
64
+ "oxlint": "^1.43.0",
65
+ "oxlint-tsgolint": "0.11.4",
66
+ "rolldown": "1.0.0-rc.9",
67
+ "typescript": "5.9.3",
68
+ "vite": "8.0.0"
69
+ }
70
+ }
@@ -0,0 +1,81 @@
1
+ // (C) 2026 GoodData Corporation
2
+
3
+ import "@gooddata/sdk-ui-kit/styles/css/main.css";
4
+ import "@gooddata/sdk-ui-kit/styles/css/indigoFont.css";
5
+ import { createRoot } from "react-dom/client";
6
+ import { BrowserRouter } from "react-router";
7
+
8
+ import { type ApplicationScope, type ILocalPluggableApplicationsRegistryV1 } from "@gooddata/sdk-model";
9
+ import {
10
+ Root,
11
+ registerLocalApplicationLoaders,
12
+ registerLocalApplications,
13
+ } from "@gooddata/sdk-ui-pluggable-host";
14
+
15
+ // The scope placeholder is replaced by `rush init-pluggable-app` with the chosen scope.
16
+ const APP_SCOPE = "{applicationTemplateScope}" as ApplicationScope;
17
+
18
+ // Harness-local app registry. Registers only this module so it can be developed and
19
+ // tested in isolation against a real backend without the full host app.
20
+ //
21
+ // Production: load via Module Federation (matches gdc-host-application).
22
+ // The module's MF artifacts are served from the same nginx container under
23
+ // /{applicationTemplateScope}/remotes/gdc-app-template-name/. Relative path keeps
24
+ // the same-origin security guard happy.
25
+ // Dev: fall back to a local binding so the module's source resolves through
26
+ // the workspace graph without a second dev server. Override with
27
+ // APP_TEMPLATE_REMOTE_URL to point at a running module dev server instead.
28
+ const localApplications: ILocalPluggableApplicationsRegistryV1 = {
29
+ apiVersion: "1.0",
30
+ applications: [
31
+ {
32
+ apiVersion: "1.0",
33
+ id: "gdc-app-template-name",
34
+ applicationScope: APP_SCOPE,
35
+ menuOrder: 1,
36
+ title: "{applicationTemplateTitle}",
37
+ ...(PRODUCTION || APP_TEMPLATE_REMOTE_URL
38
+ ? {
39
+ remote: {
40
+ url: PRODUCTION
41
+ ? "/{applicationTemplateScope}/remotes/gdc-app-template-name/remoteEntry.js"
42
+ : (APP_TEMPLATE_REMOTE_URL as string),
43
+ scope: "{applicationTemplateFederationName}",
44
+ module: "./pluggableApp",
45
+ routeBase: "/app-template-route",
46
+ },
47
+ }
48
+ : {
49
+ local: { routeBase: "/app-template-route" },
50
+ }),
51
+ },
52
+ ],
53
+ };
54
+
55
+ registerLocalApplications(localApplications);
56
+ if (!PRODUCTION && !APP_TEMPLATE_REMOTE_URL) {
57
+ registerLocalApplicationLoaders({
58
+ "gdc-app-template-name": () => import("gdc-app-template-name-module"),
59
+ });
60
+ }
61
+
62
+ // When the harness boots at "/", the host runtime's redirect heuristic prefers
63
+ // "/organization" for users with canManageOrganization permission — which lands a
64
+ // workspace-scoped app on the wrong scope (and renders the wrong header chrome).
65
+ // Force the initial URL into the app's chosen scope before Root mounts.
66
+ if (window.location.pathname === "/" || window.location.pathname === "") {
67
+ const scopeRoot = APP_SCOPE === "workspace" ? "/workspace" : "/organization";
68
+ window.history.replaceState({}, "", scopeRoot + window.location.search + window.location.hash);
69
+ }
70
+
71
+ const rootElement = document.getElementById("root");
72
+
73
+ if (!rootElement) {
74
+ throw new Error("Missing #root element in HTML document.");
75
+ }
76
+
77
+ createRoot(rootElement).render(
78
+ <BrowserRouter>
79
+ <Root />
80
+ </BrowserRouter>,
81
+ );
@@ -0,0 +1,70 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "noFallthroughCasesInSwitch": true,
5
+ "noImplicitOverride": true,
6
+ "noPropertyAccessFromIndexSignature": true,
7
+ "exactOptionalPropertyTypes": false,
8
+ "useUnknownInCatchVariables": true,
9
+ "noUncheckedIndexedAccess": false,
10
+ "noUnusedLocals": true,
11
+ "noUnusedParameters": true,
12
+ "allowUnreachableCode": false,
13
+ "allowUnusedLabels": false,
14
+ "noImplicitReturns": true,
15
+ "alwaysStrict": true,
16
+ "forceConsistentCasingInFileNames": true,
17
+ "lib": [
18
+ "es2022",
19
+ "dom",
20
+ "dom.iterable"
21
+ ],
22
+ "target": "es2022",
23
+ "skipLibCheck": true,
24
+ "esModuleInterop": true,
25
+ "jsx": "react-jsx",
26
+ "module": "esnext",
27
+ "moduleResolution": "bundler",
28
+ "noEmit": true,
29
+ "allowJs": true,
30
+ "outDir": "./dist",
31
+ "resolveJsonModule": true,
32
+ "typeRoots": [
33
+ "./node_modules/@types",
34
+ "./typings.d.ts"
35
+ ],
36
+ "paths": {
37
+ "react": [
38
+ "./node_modules/@types/react"
39
+ ]
40
+ },
41
+ "allowSyntheticDefaultImports": true,
42
+ "resolvePackageJsonExports": true,
43
+ "resolvePackageJsonImports": true,
44
+ "useDefineForClassFields": true,
45
+ "noImplicitAny": true,
46
+ "noImplicitThis": true,
47
+ "strictNullChecks": true,
48
+ "strictFunctionTypes": true,
49
+ "strictBindCallApply": true,
50
+ "strictPropertyInitialization": true,
51
+ "strictBuiltinIteratorReturn": true
52
+ },
53
+ "files": [
54
+ "./src/app.tsx",
55
+ "./eslint.config.ts",
56
+ "./typings.d.ts",
57
+ "./vite.config.ts"
58
+ ],
59
+ "include": [
60
+ "src",
61
+ "./*",
62
+ "typings.d.ts"
63
+ ],
64
+ "exclude": [
65
+ "**/node_modules",
66
+ "**/esm",
67
+ "**/temp",
68
+ "**/dist"
69
+ ]
70
+ }
@@ -0,0 +1,13 @@
1
+ // (C) 2026 GoodData Corporation
2
+
3
+ export declare global {
4
+ export const SDK_BACKEND: "tiger";
5
+ export const PRODUCTION: boolean;
6
+ export const TIGER_API_TOKEN: string | undefined;
7
+
8
+ /**
9
+ * URL of this module's Module Federation remote entry. Injected at build time in dev;
10
+ * in production the harness loads the remote from a relative path on the same origin.
11
+ */
12
+ export const APP_TEMPLATE_REMOTE_URL: string | undefined;
13
+ }
@@ -0,0 +1,111 @@
1
+ // (C) 2026 GoodData Corporation
2
+
3
+ import { readFileSync } from "fs";
4
+ import type { ClientRequest } from "http";
5
+ import { join, resolve } from "path";
6
+
7
+ import react from "@vitejs/plugin-react";
8
+ import { type UserConfig, defaultClientConditions, defineConfig, loadEnv } from "vite";
9
+
10
+ // Strip `origin` (Tiger backend rejects cross-origin) and force identity
11
+ // encoding (so the vite proxy can read the response without decompressing).
12
+ function applyProxyRequestHeaders(proxyReq: ClientRequest): void {
13
+ proxyReq.removeHeader("origin");
14
+ proxyReq.setHeader("accept-encoding", "identity");
15
+ }
16
+
17
+ const certDir = join(process.env["HOME"] || process.env["USERPROFILE"] || "", ".gooddata", "certs");
18
+ let httpsConfig: import("vite").ServerOptions["https"] | undefined;
19
+
20
+ try {
21
+ httpsConfig = {
22
+ ca: readFileSync(join(certDir, "rootCA.pem")),
23
+ key: readFileSync(join(certDir, "localhost-key.pem")),
24
+ cert: readFileSync(join(certDir, "localhost-cert.pem")),
25
+ };
26
+ // oxlint-disable-next-line @typescript-eslint/no-unused-vars
27
+ } catch (e) {
28
+ // eslint-disable-next-line no-console
29
+ console.info(`No certificates found in ${certDir}, skipping HTTPS configuration`);
30
+ httpsConfig = undefined;
31
+ }
32
+
33
+ // eslint-disable-next-line no-restricted-exports
34
+ export default defineConfig(({ mode }): UserConfig => {
35
+ const env = loadEnv(mode, process.cwd(), "");
36
+ const isProduction = mode === "production";
37
+ // NOTE: @gooddata/create-pluggable-module's computeDevPort() regex-matches
38
+ // this line shape (`env["PORT"] || "NNNN"`) when scanning existing harnesses
39
+ // to pick a port for the next scaffolded app. If you refactor it, update
40
+ // sdk/tools/create-pluggable-module/src/engine/derived.ts to match.
41
+ const port = parseInt(env["PORT"] || "{harnessPort}");
42
+ const backend = env["BACKEND_URL"];
43
+ if (!isProduction && !backend) {
44
+ throw new Error(
45
+ "BACKEND_URL is required for the dev server. Copy .env.template to .env and set BACKEND_URL " +
46
+ "to the URL of your GoodData Cloud instance (e.g. https://yourorg.gooddata.com).",
47
+ );
48
+ }
49
+
50
+ return {
51
+ root: ".",
52
+ base: isProduction ? "/{applicationTemplateScope}/" : "/",
53
+ publicDir: false,
54
+ define: {
55
+ SDK_BACKEND: JSON.stringify("tiger"),
56
+ TIGER_API_TOKEN: isProduction ? undefined : JSON.stringify(env["TIGER_API_TOKEN"]),
57
+ PRODUCTION: JSON.stringify(isProduction),
58
+ APP_TEMPLATE_REMOTE_URL: isProduction
59
+ ? "undefined"
60
+ : env["APP_TEMPLATE_REMOTE_URL"]
61
+ ? JSON.stringify(env["APP_TEMPLATE_REMOTE_URL"])
62
+ : "undefined",
63
+ "process.env": JSON.stringify({}),
64
+ REACT_APP_SDK_BACKEND: JSON.stringify("TIGER"),
65
+ REACT_APP_API_TOKEN: isProduction ? undefined : JSON.stringify(env["TIGER_API_TOKEN"]),
66
+ REACT_APP_IN_AIO: JSON.stringify(false),
67
+ },
68
+ plugins: [react()],
69
+ resolve: {
70
+ // resolve.conditions REPLACES Vite's defaults rather than extending them
71
+ // (Vite 8's mergeWithDefaultsRecursively assigns arrays directly), so we must
72
+ // spread defaultClientConditions to keep "browser" / "module" / dev-prod
73
+ // resolution. Without "browser", packages like react-textarea-autosize fall
74
+ // through to their node-targeted default export (a stub that ignores
75
+ // minRows/maxRows).
76
+ conditions: [...defaultClientConditions, "source"],
77
+ dedupe: ["react", "react-dom"],
78
+ },
79
+ server: {
80
+ port,
81
+ https: httpsConfig,
82
+ proxy: {
83
+ "/api": {
84
+ changeOrigin: true,
85
+ cookieDomainRewrite: "localhost",
86
+ secure: false,
87
+ target: backend,
88
+ headers: {
89
+ host: backend,
90
+ "X-Requested-With": "XMLHttpRequest",
91
+ },
92
+ configure: (proxy) => {
93
+ proxy.on("proxyReq", applyProxyRequestHeaders);
94
+ },
95
+ },
96
+ },
97
+ fs: {
98
+ allow: ["../../../"],
99
+ },
100
+ },
101
+ build: {
102
+ outDir: "dist",
103
+ sourcemap: !isProduction,
104
+ target: "es2022",
105
+ chunkSizeWarningLimit: 10000,
106
+ rolldownOptions: {
107
+ input: resolve(__dirname, "index.html"),
108
+ },
109
+ },
110
+ };
111
+ });
@@ -0,0 +1,11 @@
1
+ node_modules
2
+ esm
3
+ dist
4
+ *.tgz
5
+ rush-logs
6
+ .rush
7
+ .idea
8
+ .vscode
9
+ .DS_Store
10
+ .env
11
+ .__mf__temp
@@ -0,0 +1,35 @@
1
+ # gdc-app-template-name-module
2
+
3
+ Pluggable application module for **gdc-app-template-name**.
4
+
5
+ This is a template. Run `rush init-pluggable-app` to scaffold a new application from it.
6
+
7
+ ## Structure
8
+
9
+ - `src/pluggableApp.tsx` — mount/unmount lifecycle, forwards `onEvent` and `onTelemetryEvent` callbacks
10
+ - `src/AppProviders.tsx` — thin wrapper around `AppProviders` from `@gooddata/sdk-ui-pluggable-application` that binds the app's translation bundle
11
+ - `src/App.tsx` — main React component, consumes platform context, workspace, and localization
12
+ - `src/translations/` — locale JSON files and async loader (`translations.ts`) using `Record<ILocale, ...>` for compile-time enforcement
13
+
14
+ ## Development
15
+
16
+ ```bash
17
+ # Run tests in watch mode
18
+ rushx dev
19
+
20
+ # Run tests once
21
+ rushx test-once
22
+
23
+ # Build
24
+ rushx build
25
+ ```
26
+
27
+ ## Adding translations
28
+
29
+ 1. Add keys to `src/translations/en-US.json` with `{ "text": "...", "crowdinContext": "..." }` format.
30
+ 2. Copy the new keys to all other locale JSON files (they start as en-US copies; translators update them later).
31
+ 3. The `translations.ts` async loader and `Record<ILocale, ...>` type ensure every locale is covered at build time.
32
+
33
+ ## Localization enforcement
34
+
35
+ The `asyncComponentTranslations` map is typed as `Record<ILocale, ...>`. If a new locale is added to `ILocale` in `@gooddata/sdk-model`, the build will fail until a matching JSON file and map entry are added here.
@@ -0,0 +1,3 @@
1
+ // (C) 2026 GoodData Corporation
2
+
3
+ export { default } from "@gooddata/eslint-config/oxlint-esm-react-vitest";
@@ -0,0 +1,84 @@
1
+ {
2
+ "name": "gdc-app-template-name-module",
3
+ "version": "11.40.0-alpha.3",
4
+ "private": true,
5
+ "description": "{applicationTemplateTitle} pluggable application module",
6
+ "license": "UNLICENSED",
7
+ "author": {
8
+ "name": "Stellar",
9
+ "email": "stellar@gooddata.com"
10
+ },
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/gooddata/gdc-ui/",
14
+ "directory": "modules/gdc-app-template-name/module"
15
+ },
16
+ "type": "module",
17
+ "sideEffects": false,
18
+ "types": "./src/index.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./src/index.ts",
22
+ "source": "./src/index.ts",
23
+ "default": "./src/index.ts"
24
+ },
25
+ "./tsconfig.json": "./tsconfig.json"
26
+ },
27
+ "scripts": {
28
+ "_phase:build": "npm run build-check",
29
+ "_phase:validate": "npm run validate",
30
+ "build-check": "tsgo",
31
+ "clean": "../../../../common/scripts/clean-command-state.sh && rm -rf esm dist *.tgz *.log tsconfig.tsbuildinfo",
32
+ "format-check": "oxfmt --check .",
33
+ "format-write": "oxfmt .",
34
+ "lint": "oxlint . --type-aware --quiet && eslint .",
35
+ "lint-fix": "oxlint . --type-aware --quiet --fix && eslint . --fix",
36
+ "validate": "npm run lint && npm run format-check"
37
+ },
38
+ "dependencies": {
39
+ "@gooddata/sdk-model": "workspace:*",
40
+ "@gooddata/sdk-pluggable-application-model": "workspace:*",
41
+ "@gooddata/sdk-ui": "workspace:*",
42
+ "@gooddata/sdk-ui-pluggable-application": "workspace:*",
43
+ "@gooddata/util": "workspace:*",
44
+ "lodash-es": "^4.17.23",
45
+ "react-intl": "7.1.11"
46
+ },
47
+ "devDependencies": {
48
+ "@gooddata/eslint-config": "workspace:*",
49
+ "@gooddata/oxlint-config": "workspace:*",
50
+ "@module-federation/vite": "^1.8.0",
51
+ "@testing-library/dom": "10.4.1",
52
+ "@testing-library/react": "16.3.0",
53
+ "@types/lodash-es": "^4.17.12",
54
+ "@types/node": "24.12.0",
55
+ "@types/react": "19.1.11",
56
+ "@types/react-dom": "19.1.7",
57
+ "@typescript-eslint/eslint-plugin": "8.58.0",
58
+ "@typescript-eslint/parser": "8.58.0",
59
+ "@typescript/native-preview": "7.0.0-dev.20260202.1",
60
+ "@vitejs/plugin-react": "^5.1.2",
61
+ "eslint": "^9.39.2",
62
+ "eslint-import-resolver-typescript": "4.4.4",
63
+ "eslint-plugin-headers": "1.3.3",
64
+ "eslint-plugin-import-x": "4.16.1",
65
+ "eslint-plugin-jsdoc": "62.1.0",
66
+ "eslint-plugin-no-barrel-files": "1.2.2",
67
+ "eslint-plugin-no-only-tests": "3.3.0",
68
+ "eslint-plugin-react": "7.37.5",
69
+ "eslint-plugin-react-hooks": "5.2.0",
70
+ "eslint-plugin-sonarjs": "3.0.6",
71
+ "happy-dom": "18.0.1",
72
+ "jiti": "2.6.1",
73
+ "npm-run-all": "^4.1.5",
74
+ "oxfmt": "0.45.0",
75
+ "oxlint": "^1.43.0",
76
+ "oxlint-tsgolint": "0.11.4",
77
+ "react": "19.1.1",
78
+ "react-dom": "19.1.1",
79
+ "rolldown": "1.0.0-rc.9",
80
+ "typescript": "5.9.3",
81
+ "vite": "8.0.0",
82
+ "vitest": "4.1.0"
83
+ }
84
+ }