@dx-labs/template-designer-setup 0.1.2

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/CHANGELOG.md +20 -0
  2. package/README.md +44 -0
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +136 -0
  5. package/dist/index.js.map +1 -0
  6. package/dist/steps/ensureNpmrc.d.ts +2 -0
  7. package/dist/steps/ensureNpmrc.js +127 -0
  8. package/dist/steps/ensureNpmrc.js.map +1 -0
  9. package/dist/steps/installPackages.d.ts +2 -0
  10. package/dist/steps/installPackages.js +23 -0
  11. package/dist/steps/installPackages.js.map +1 -0
  12. package/dist/steps/patchAppConfig.d.ts +2 -0
  13. package/dist/steps/patchAppConfig.js +42 -0
  14. package/dist/steps/patchAppConfig.js.map +1 -0
  15. package/dist/steps/patchAppTsx.d.ts +2 -0
  16. package/dist/steps/patchAppTsx.js +63 -0
  17. package/dist/steps/patchAppTsx.js.map +1 -0
  18. package/dist/steps/patchBackendIndex.d.ts +2 -0
  19. package/dist/steps/patchBackendIndex.js +89 -0
  20. package/dist/steps/patchBackendIndex.js.map +1 -0
  21. package/dist/steps/patchRootTsx.d.ts +2 -0
  22. package/dist/steps/patchRootTsx.js +72 -0
  23. package/dist/steps/patchRootTsx.js.map +1 -0
  24. package/dist/types.d.ts +10 -0
  25. package/dist/types.js +3 -0
  26. package/dist/types.js.map +1 -0
  27. package/dist/utils/ast.d.ts +9 -0
  28. package/dist/utils/ast.js +65 -0
  29. package/dist/utils/ast.js.map +1 -0
  30. package/dist/utils/exec.d.ts +7 -0
  31. package/dist/utils/exec.js +22 -0
  32. package/dist/utils/exec.js.map +1 -0
  33. package/dist/utils/fs.d.ts +8 -0
  34. package/dist/utils/fs.js +48 -0
  35. package/dist/utils/fs.js.map +1 -0
  36. package/dist/utils/log.d.ts +7 -0
  37. package/dist/utils/log.js +47 -0
  38. package/dist/utils/log.js.map +1 -0
  39. package/dist/utils/yaml.d.ts +4 -0
  40. package/dist/utils/yaml.js +20 -0
  41. package/dist/utils/yaml.js.map +1 -0
  42. package/package.json +69 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ ## [0.1.2](https://github.com/dx-labs-com/template-designer-pro-setup/compare/template-designer-pro-setup-v0.1.1...template-designer-pro-setup-v0.1.2) (2026-02-03)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * release package ([53ba47f](https://github.com/dx-labs-com/template-designer-pro-setup/commit/53ba47f204eedbc40cb698860208969592ccd670))
7
+
8
+ ## [0.1.1](https://github.com/dx-labs-com/template-designer-pro-setup/compare/template-designer-pro-setup-v0.1.0...template-designer-pro-setup-v0.1.1) (2026-02-03)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * release package ([7a2211f](https://github.com/dx-labs-com/template-designer-pro-setup/commit/7a2211f1074716c59d43a4741fcf5302a85af2d7))
14
+
15
+ # [0.1.0](https://github.com/dx-labs-com/template-designer-pro-setup/compare/template-designer-pro-setup-v0.0.1...template-designer-pro-setup-v0.1.0) (2026-02-03)
16
+
17
+
18
+ ### Features
19
+
20
+ * publish template-designer-pro-setup ([f716d0f](https://github.com/dx-labs-com/template-designer-pro-setup/commit/f716d0f6972873ebfac3a68ee47d76abf175ccab))
package/README.md ADDED
@@ -0,0 +1,44 @@
1
+ # Template Designer Setup CLI
2
+
3
+ CLI that installs and wires the Template Designer frontend and License Box backend plugins into a Backstage instance. It can be published and used via `npx @dx-labs/template-designer-setup`.
4
+
5
+ ## Build
6
+
7
+ - From repo root: `yarn install`
8
+ - Build the CLI: `yarn workspace @dx-labs/template-designer-setup build`
9
+
10
+ ## Usage
11
+
12
+ Run from the Backstage repo root (must contain `packages/app`, `packages/backend`, and `app-config.yaml`).
13
+
14
+ - Dry run (no writes): `node npx/dist/index.js --dryRun --licenseId LIC-12345`
15
+ - Full run with prompts for missing values: `npx @dx-labs/template-designer-setup --licenseId LIC-12345`
16
+ - Run against a different Backstage path: `npx @dx-labs/template-designer-setup --backstageDir ../my-backstage --licenseId LIC-12345`
17
+ - Provide registry details explicitly: `npx @dx-labs/template-designer-setup --licenseId LIC-12345 --npmToken TOKEN --registryUrl https://registry.npmjs.org --scopes @tduniec,@dx-labs`
18
+ - Skip final install: `npx @dx-labs/template-designer-setup --licenseId LIC-12345 --skipInstall`
19
+
20
+ Flags:
21
+
22
+ - `--licenseId <value>` (required, prompts if missing)
23
+ - `--npmToken <value>` (optional, prompts if missing)
24
+ - `--registryUrl <url>` defaults to `https://registry.npmjs.org`
25
+ - `--scopes <@scope1,@scope2>` defaults to `@tduniec,@dx-labs`
26
+ - `--backstageDir <path>` to target a Backstage repo (default: current working directory)
27
+ - `--dryRun` shows planned actions only
28
+ - `--skipInstall` skips `yarn install` after patching
29
+
30
+ ## What it does
31
+
32
+ 1. Installs `@dx-labs/plugin-template-designer-pro` in `packages/app`
33
+ 2. Installs `@dx-labs/plugin-license-box` in `packages/backend`
34
+ 3. Adds Template Designer route to `packages/app/src/App.tsx` using ts-morph (idempotent)
35
+ 4. Adds sidebar entry with `TemplateDesignerIcon` to `packages/app/src/components/Root/Root.tsx` (idempotent)
36
+ 5. Writes `dxLicenseBox.license.key` to `app-config.local.yaml` when present, otherwise `app-config.yaml`
37
+ 6. Configures `.npmrc` with the provided scopes, registry, and auth token (token is masked in logs)
38
+ 7. Runs `yarn install` unless `--skipInstall` is passed
39
+
40
+ ## Publish (optional)
41
+
42
+ - Bump the version in `npx/package.json` if needed
43
+ - Build: `yarn workspace @dx-labs/template-designer-setup build`
44
+ - Publish from `npx/`: `cd npx && npm publish --access public`
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,136 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ const commander_1 = require("commander");
8
+ const path_1 = __importDefault(require("path"));
9
+ const prompts_1 = __importDefault(require("prompts"));
10
+ const ensureNpmrc_1 = require("./steps/ensureNpmrc");
11
+ const installPackages_1 = require("./steps/installPackages");
12
+ const patchAppConfig_1 = require("./steps/patchAppConfig");
13
+ const patchAppTsx_1 = require("./steps/patchAppTsx");
14
+ const patchRootTsx_1 = require("./steps/patchRootTsx");
15
+ const patchBackendIndex_1 = require("./steps/patchBackendIndex");
16
+ const exec_1 = require("./utils/exec");
17
+ const fs_1 = require("./utils/fs");
18
+ const log_1 = require("./utils/log");
19
+ const DEFAULT_SCOPES = ["@tduniec", "@dx-labs"];
20
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org";
21
+ async function main() {
22
+ const program = new commander_1.Command();
23
+ program
24
+ .name("template-designer-setup")
25
+ .description("Install and configure Template Designer and License Box plugins for Backstage")
26
+ .option("--backstageDir <path>", "Path to Backstage repo root (defaults to current working directory)")
27
+ .option("--licenseId <id>", "License Box license identifier")
28
+ .option("--npmToken <token>", "Token for accessing the private npm registry")
29
+ .option("--registryUrl <url>", "Registry URL", DEFAULT_REGISTRY)
30
+ .option("--scopes <scopes>", "Comma-separated scopes to configure", (value) => value, DEFAULT_SCOPES.join(","))
31
+ .option("--dryRun", "Show actions without writing files or running commands", false)
32
+ .option("--skipInstall", "Skip running yarn install at the end", false)
33
+ .parse(process.argv);
34
+ const options = program.opts();
35
+ try {
36
+ const root = options.backstageDir
37
+ ? path_1.default.resolve(process.cwd(), options.backstageDir)
38
+ : process.cwd();
39
+ const scopes = parseScopes(options.scopes);
40
+ const dryRun = Boolean(options.dryRun);
41
+ const skipInstall = Boolean(options.skipInstall);
42
+ await validateBackstageRoot(root);
43
+ const configPath = (await (0, fs_1.fileExists)(path_1.default.join(root, "app-config.local.yaml")))
44
+ ? path_1.default.join(root, "app-config.local.yaml")
45
+ : path_1.default.join(root, "app-config.yaml");
46
+ const licenseId = await resolveLicenseId(options.licenseId);
47
+ const npmToken = await resolveNpmToken(options.npmToken);
48
+ const context = {
49
+ root,
50
+ dryRun,
51
+ skipInstall,
52
+ licenseId,
53
+ npmToken,
54
+ registryUrl: options.registryUrl ?? DEFAULT_REGISTRY,
55
+ scopes,
56
+ configPath,
57
+ };
58
+ await (0, ensureNpmrc_1.ensureNpmrc)(context);
59
+ await (0, installPackages_1.installPackages)(context);
60
+ await (0, patchAppTsx_1.patchAppTsx)(context);
61
+ await (0, patchRootTsx_1.patchRootTsx)(context);
62
+ await (0, patchBackendIndex_1.patchBackendIndex)(context);
63
+ await (0, patchAppConfig_1.patchAppConfig)(context);
64
+ if (!skipInstall) {
65
+ await runYarnInstall(context);
66
+ }
67
+ else {
68
+ (0, log_1.info)("Skipping yarn install (--skipInstall provided)");
69
+ }
70
+ (0, log_1.success)("Template Designer setup completed");
71
+ (0, log_1.info)("Next: start Backstage to verify → yarn start");
72
+ }
73
+ catch (err) {
74
+ const message = err instanceof Error ? err.message : String(err);
75
+ (0, log_1.error)(message);
76
+ process.exitCode = 1;
77
+ }
78
+ }
79
+ async function validateBackstageRoot(root) {
80
+ const requiredPaths = [
81
+ path_1.default.join(root, "packages", "app"),
82
+ path_1.default.join(root, "packages", "backend"),
83
+ path_1.default.join(root, "app-config.yaml"),
84
+ ];
85
+ for (const required of requiredPaths) {
86
+ if (!(await (0, fs_1.fileExists)(required))) {
87
+ throw new Error(`This tool must be run from a Backstage repository root. Missing: ${path_1.default.relative(root, required)}`);
88
+ }
89
+ }
90
+ }
91
+ function parseScopes(scopes) {
92
+ if (!scopes)
93
+ return DEFAULT_SCOPES;
94
+ return scopes
95
+ .split(",")
96
+ .map((scope) => scope.trim())
97
+ .filter((scope) => scope.length > 0);
98
+ }
99
+ async function resolveLicenseId(provided) {
100
+ if (provided)
101
+ return provided;
102
+ const validateLicenseId = (value) => value.trim().length > 0 ? true : "licenseId is required";
103
+ const response = (await (0, prompts_1.default)({
104
+ type: "text",
105
+ name: "licenseId",
106
+ message: "Enter the License Box licenseId",
107
+ validate: validateLicenseId,
108
+ }));
109
+ const licenseIdRaw = response.licenseId;
110
+ const licenseId = typeof licenseIdRaw === "string" ? licenseIdRaw.trim() : "";
111
+ if (!licenseId) {
112
+ throw new Error("licenseId is required");
113
+ }
114
+ return licenseId;
115
+ }
116
+ async function resolveNpmToken(provided) {
117
+ if (provided)
118
+ return provided;
119
+ const response = (await (0, prompts_1.default)({
120
+ type: "password",
121
+ name: "npmToken",
122
+ message: "Enter npm token for the private registry (optional)",
123
+ }));
124
+ const tokenRaw = response.npmToken;
125
+ const token = typeof tokenRaw === "string" ? tokenRaw.trim() : "";
126
+ return token.length > 0 ? token : undefined;
127
+ }
128
+ async function runYarnInstall(context) {
129
+ await (0, exec_1.runCommand)("yarn", ["install"], { cwd: context.root, dryRun: context.dryRun });
130
+ if (context.dryRun) {
131
+ return;
132
+ }
133
+ (0, log_1.success)("yarn install completed");
134
+ }
135
+ void main();
136
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport path from \"path\";\nimport prompts from \"prompts\";\nimport { CliContext } from \"./types\";\nimport { ensureNpmrc } from \"./steps/ensureNpmrc\";\nimport { installPackages } from \"./steps/installPackages\";\nimport { patchAppConfig } from \"./steps/patchAppConfig\";\nimport { patchAppTsx } from \"./steps/patchAppTsx\";\nimport { patchRootTsx } from \"./steps/patchRootTsx\";\nimport { patchBackendIndex } from \"./steps/patchBackendIndex\";\nimport { runCommand } from \"./utils/exec\";\nimport { fileExists } from \"./utils/fs\";\nimport { error, info, success } from \"./utils/log\";\n\nconst DEFAULT_SCOPES = [\"@tduniec\", \"@dx-labs\"];\nconst DEFAULT_REGISTRY = \"https://registry.npmjs.org\";\n\nasync function main() {\n const program = new Command();\n\n program\n .name(\"template-designer-setup\")\n .description(\"Install and configure Template Designer and License Box plugins for Backstage\")\n .option(\n \"--backstageDir <path>\",\n \"Path to Backstage repo root (defaults to current working directory)\",\n )\n .option(\"--licenseId <id>\", \"License Box license identifier\")\n .option(\"--npmToken <token>\", \"Token for accessing the private npm registry\")\n .option(\"--registryUrl <url>\", \"Registry URL\", DEFAULT_REGISTRY)\n .option(\n \"--scopes <scopes>\",\n \"Comma-separated scopes to configure\",\n (value: string) => value,\n DEFAULT_SCOPES.join(\",\"),\n )\n .option(\"--dryRun\", \"Show actions without writing files or running commands\", false)\n .option(\"--skipInstall\", \"Skip running yarn install at the end\", false)\n .parse(process.argv);\n\n const options = program.opts<{\n licenseId?: string;\n npmToken?: string;\n registryUrl?: string;\n scopes?: string;\n dryRun?: boolean;\n skipInstall?: boolean;\n backstageDir?: string;\n }>();\n\n try {\n const root = options.backstageDir\n ? path.resolve(process.cwd(), options.backstageDir)\n : process.cwd();\n const scopes = parseScopes(options.scopes);\n const dryRun = Boolean(options.dryRun);\n const skipInstall = Boolean(options.skipInstall);\n\n await validateBackstageRoot(root);\n\n const configPath = (await fileExists(path.join(root, \"app-config.local.yaml\")))\n ? path.join(root, \"app-config.local.yaml\")\n : path.join(root, \"app-config.yaml\");\n\n const licenseId = await resolveLicenseId(options.licenseId);\n const npmToken = await resolveNpmToken(options.npmToken);\n\n const context: CliContext = {\n root,\n dryRun,\n skipInstall,\n licenseId,\n npmToken,\n registryUrl: options.registryUrl ?? DEFAULT_REGISTRY,\n scopes,\n configPath,\n };\n\n await ensureNpmrc(context);\n await installPackages(context);\n await patchAppTsx(context);\n await patchRootTsx(context);\n await patchBackendIndex(context);\n await patchAppConfig(context);\n\n if (!skipInstall) {\n await runYarnInstall(context);\n } else {\n info(\"Skipping yarn install (--skipInstall provided)\");\n }\n\n success(\"Template Designer setup completed\");\n info(\"Next: start Backstage to verify → yarn start\");\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n error(message);\n process.exitCode = 1;\n }\n}\n\nasync function validateBackstageRoot(root: string) {\n const requiredPaths = [\n path.join(root, \"packages\", \"app\"),\n path.join(root, \"packages\", \"backend\"),\n path.join(root, \"app-config.yaml\"),\n ];\n\n for (const required of requiredPaths) {\n if (!(await fileExists(required))) {\n throw new Error(\n `This tool must be run from a Backstage repository root. Missing: ${path.relative(\n root,\n required,\n )}`,\n );\n }\n }\n}\n\nfunction parseScopes(scopes?: string): string[] {\n if (!scopes) return DEFAULT_SCOPES;\n return scopes\n .split(\",\")\n .map((scope) => scope.trim())\n .filter((scope) => scope.length > 0);\n}\n\nasync function resolveLicenseId(provided?: string): Promise<string> {\n if (provided) return provided;\n\n const validateLicenseId = (value: string) =>\n value.trim().length > 0 ? true : \"licenseId is required\";\n\n const response = (await prompts({\n type: \"text\",\n name: \"licenseId\",\n message: \"Enter the License Box licenseId\",\n validate: validateLicenseId,\n })) as { licenseId?: string };\n\n const licenseIdRaw: unknown = (response as { licenseId?: unknown }).licenseId;\n const licenseId = typeof licenseIdRaw === \"string\" ? licenseIdRaw.trim() : \"\";\n if (!licenseId) {\n throw new Error(\"licenseId is required\");\n }\n\n return licenseId;\n}\n\nasync function resolveNpmToken(provided?: string): Promise<string | undefined> {\n if (provided) return provided;\n\n const response = (await prompts({\n type: \"password\",\n name: \"npmToken\",\n message: \"Enter npm token for the private registry (optional)\",\n })) as { npmToken?: string };\n\n const tokenRaw: unknown = (response as { npmToken?: unknown }).npmToken;\n const token = typeof tokenRaw === \"string\" ? tokenRaw.trim() : \"\";\n return token.length > 0 ? token : undefined;\n}\n\nasync function runYarnInstall(context: CliContext) {\n await runCommand(\"yarn\", [\"install\"], { cwd: context.root, dryRun: context.dryRun });\n if (context.dryRun) {\n return;\n }\n success(\"yarn install completed\");\n}\n\nvoid main();\n"]}
@@ -0,0 +1,2 @@
1
+ import { CliContext } from "../types";
2
+ export declare function ensureNpmrc(context: CliContext): Promise<void>;
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ensureNpmrc = ensureNpmrc;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = require("../utils/fs");
9
+ const log_1 = require("../utils/log");
10
+ const yaml_1 = __importDefault(require("yaml"));
11
+ const DEFAULT_REGISTRY = "https://registry.npmjs.org";
12
+ async function ensureNpmrc(context) {
13
+ const npmrcPath = path_1.default.join(context.root, ".npmrc");
14
+ const registryUrl = context.registryUrl || DEFAULT_REGISTRY;
15
+ const scopes = context.scopes;
16
+ const npmToken = context.npmToken;
17
+ (0, log_1.step)("Ensuring .npmrc is configured");
18
+ const existing = (await (0, fs_1.readFileIfExists)(npmrcPath)) ?? "";
19
+ const lines = existing.split(/\r?\n/).filter((line) => line.trim().length > 0);
20
+ const additions = [];
21
+ scopes.forEach((scope) => {
22
+ const trimmedScope = scope.trim();
23
+ if (!trimmedScope.startsWith("@"))
24
+ return;
25
+ const scopeLine = `${trimmedScope}:registry=${registryUrl}`;
26
+ const alreadyPresent = lines.some((line) => line.startsWith(`${trimmedScope}:registry=`));
27
+ if (!alreadyPresent) {
28
+ additions.push(scopeLine);
29
+ }
30
+ });
31
+ if (npmToken) {
32
+ const registryHost = toRegistryHost(registryUrl);
33
+ const tokenLine = `//${registryHost}:_authToken=${npmToken}`;
34
+ const hasToken = lines.some((line) => line.startsWith(`//${registryHost}:_authToken=`));
35
+ if (!hasToken) {
36
+ additions.push(tokenLine);
37
+ }
38
+ }
39
+ if (additions.length === 0) {
40
+ if (context.dryRun) {
41
+ (0, log_1.dryRun)(".npmrc already contains required registry settings");
42
+ }
43
+ else {
44
+ (0, log_1.success)(".npmrc already contains required registry settings");
45
+ }
46
+ return;
47
+ }
48
+ const nextContent = buildContent(existing, additions);
49
+ if (context.dryRun) {
50
+ (0, log_1.dryRun)(`Would update .npmrc with scopes ${scopes.join(", ")}${npmToken ? " and auth token" : ""}`);
51
+ return;
52
+ }
53
+ await (0, fs_1.writeFileSafely)(npmrcPath, nextContent);
54
+ (0, log_1.success)(`.npmrc updated (${additions.length} entr${additions.length === 1 ? "y" : "ies"} added${npmToken ? `, token ${(0, log_1.mask)(npmToken)}` : ""})`);
55
+ await ensureYarnrcYml(context, registryUrl, scopes, npmToken);
56
+ }
57
+ function toRegistryHost(registryUrl) {
58
+ try {
59
+ const url = new URL(registryUrl);
60
+ const path = url.pathname.endsWith("/") ? url.pathname.slice(1) : `${url.pathname.slice(1)}/`;
61
+ return `${url.host}/${path}`;
62
+ }
63
+ catch {
64
+ return registryUrl.replace(/^https?:\/\//, "").replace(/\/?$/, "/");
65
+ }
66
+ }
67
+ function buildContent(existing, additions) {
68
+ let content = existing.trimEnd();
69
+ if (content.length > 0 && !content.endsWith("\n")) {
70
+ content += "\n";
71
+ }
72
+ else if (content.length > 0) {
73
+ // keep as is
74
+ }
75
+ content += additions.join("\n");
76
+ if (!content.endsWith("\n")) {
77
+ content += "\n";
78
+ }
79
+ return content;
80
+ }
81
+ async function ensureYarnrcYml(context, registryUrl, scopes, npmToken) {
82
+ const yarnrcPath = path_1.default.join(context.root, ".yarnrc.yml");
83
+ const existingContent = (await (0, fs_1.readFileIfExists)(yarnrcPath)) ?? "";
84
+ let doc = existingContent
85
+ ? yaml_1.default.parse(existingContent)
86
+ : {};
87
+ if (!doc || typeof doc !== "object")
88
+ doc = {};
89
+ const npmScopes = doc.npmScopes ?? {};
90
+ doc.npmScopes = npmScopes;
91
+ let changed = false;
92
+ scopes
93
+ .map((s) => s.replace(/^@/, ""))
94
+ .filter((s) => s.length > 0)
95
+ .forEach((scope) => {
96
+ const current = npmScopes[scope] || {};
97
+ const desired = {
98
+ npmRegistryServer: registryUrl,
99
+ };
100
+ if (npmToken) {
101
+ desired.npmAuthToken = npmToken;
102
+ desired.npmAlwaysAuth = true;
103
+ }
104
+ const next = { ...current, ...desired };
105
+ if (JSON.stringify(current) !== JSON.stringify(next)) {
106
+ npmScopes[scope] = next;
107
+ changed = true;
108
+ }
109
+ });
110
+ if (!changed) {
111
+ if (context.dryRun) {
112
+ (0, log_1.dryRun)(".yarnrc.yml already contains required scope registry settings");
113
+ }
114
+ else {
115
+ (0, log_1.success)(".yarnrc.yml already contains required scope registry settings");
116
+ }
117
+ return;
118
+ }
119
+ const yamlOut = yaml_1.default.stringify(doc).trimEnd() + "\n";
120
+ if (context.dryRun) {
121
+ (0, log_1.dryRun)("Would update .yarnrc.yml with scope registry/token configuration");
122
+ return;
123
+ }
124
+ await (0, fs_1.writeFileSafely)(yarnrcPath, yamlOut);
125
+ (0, log_1.success)(`.yarnrc.yml updated for scopes ${scopes.join(", ")}${npmToken ? ` (token ${(0, log_1.mask)(npmToken)})` : ""}`);
126
+ }
127
+ //# sourceMappingURL=ensureNpmrc.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ensureNpmrc.js","sourceRoot":"","sources":["../../src/steps/ensureNpmrc.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"path\";\nimport { CliContext } from \"../types\";\nimport { readFileIfExists, writeFileSafely } from \"../utils/fs\";\nimport { dryRun, mask, success, step } from \"../utils/log\";\nimport YAML from \"yaml\";\n\nconst DEFAULT_REGISTRY = \"https://registry.npmjs.org\";\n\nexport async function ensureNpmrc(context: CliContext) {\n const npmrcPath = path.join(context.root, \".npmrc\");\n const registryUrl = context.registryUrl || DEFAULT_REGISTRY;\n const scopes = context.scopes;\n const npmToken = context.npmToken;\n\n step(\"Ensuring .npmrc is configured\");\n const existing = (await readFileIfExists(npmrcPath)) ?? \"\";\n const lines = existing.split(/\\r?\\n/).filter((line) => line.trim().length > 0);\n\n const additions: string[] = [];\n\n scopes.forEach((scope) => {\n const trimmedScope = scope.trim();\n if (!trimmedScope.startsWith(\"@\")) return;\n const scopeLine = `${trimmedScope}:registry=${registryUrl}`;\n const alreadyPresent = lines.some((line) => line.startsWith(`${trimmedScope}:registry=`));\n if (!alreadyPresent) {\n additions.push(scopeLine);\n }\n });\n\n if (npmToken) {\n const registryHost = toRegistryHost(registryUrl);\n const tokenLine = `//${registryHost}:_authToken=${npmToken}`;\n const hasToken = lines.some((line) => line.startsWith(`//${registryHost}:_authToken=`));\n if (!hasToken) {\n additions.push(tokenLine);\n }\n }\n\n if (additions.length === 0) {\n if (context.dryRun) {\n dryRun(\".npmrc already contains required registry settings\");\n } else {\n success(\".npmrc already contains required registry settings\");\n }\n return;\n }\n\n const nextContent = buildContent(existing, additions);\n\n if (context.dryRun) {\n dryRun(\n `Would update .npmrc with scopes ${scopes.join(\", \")}${npmToken ? \" and auth token\" : \"\"}`,\n );\n return;\n }\n\n await writeFileSafely(npmrcPath, nextContent);\n success(\n `.npmrc updated (${additions.length} entr${additions.length === 1 ? \"y\" : \"ies\"} added${\n npmToken ? `, token ${mask(npmToken)}` : \"\"\n })`,\n );\n\n await ensureYarnrcYml(context, registryUrl, scopes, npmToken);\n}\n\nfunction toRegistryHost(registryUrl: string): string {\n try {\n const url = new URL(registryUrl);\n const path = url.pathname.endsWith(\"/\") ? url.pathname.slice(1) : `${url.pathname.slice(1)}/`;\n return `${url.host}/${path}`;\n } catch {\n return registryUrl.replace(/^https?:\\/\\//, \"\").replace(/\\/?$/, \"/\");\n }\n}\n\nfunction buildContent(existing: string, additions: string[]): string {\n let content = existing.trimEnd();\n if (content.length > 0 && !content.endsWith(\"\\n\")) {\n content += \"\\n\";\n } else if (content.length > 0) {\n // keep as is\n }\n content += additions.join(\"\\n\");\n if (!content.endsWith(\"\\n\")) {\n content += \"\\n\";\n }\n return content;\n}\n\ntype YarnScopeConfig = {\n npmRegistryServer: string;\n npmAuthToken?: string;\n npmAlwaysAuth?: boolean;\n};\n\nasync function ensureYarnrcYml(\n context: CliContext,\n registryUrl: string,\n scopes: string[],\n npmToken?: string,\n) {\n const yarnrcPath = path.join(context.root, \".yarnrc.yml\");\n const existingContent = (await readFileIfExists(yarnrcPath)) ?? \"\";\n let doc: Record<string, unknown> = existingContent\n ? (YAML.parse(existingContent) as Record<string, unknown>)\n : {};\n if (!doc || typeof doc !== \"object\") doc = {};\n\n const npmScopes = (doc.npmScopes as Record<string, YarnScopeConfig>) ?? {};\n doc.npmScopes = npmScopes;\n\n let changed = false;\n\n scopes\n .map((s) => s.replace(/^@/, \"\"))\n .filter((s) => s.length > 0)\n .forEach((scope) => {\n const current = npmScopes[scope] || {};\n const desired: YarnScopeConfig = {\n npmRegistryServer: registryUrl,\n };\n if (npmToken) {\n desired.npmAuthToken = npmToken;\n desired.npmAlwaysAuth = true;\n }\n\n const next = { ...current, ...desired };\n if (JSON.stringify(current) !== JSON.stringify(next)) {\n npmScopes[scope] = next;\n changed = true;\n }\n });\n\n if (!changed) {\n if (context.dryRun) {\n dryRun(\".yarnrc.yml already contains required scope registry settings\");\n } else {\n success(\".yarnrc.yml already contains required scope registry settings\");\n }\n return;\n }\n\n const yamlOut = YAML.stringify(doc).trimEnd() + \"\\n\";\n if (context.dryRun) {\n dryRun(\"Would update .yarnrc.yml with scope registry/token configuration\");\n return;\n }\n\n await writeFileSafely(yarnrcPath, yamlOut);\n success(\n `.yarnrc.yml updated for scopes ${scopes.join(\", \")}${npmToken ? ` (token ${mask(npmToken)})` : \"\"}`,\n );\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { CliContext } from "../types";
2
+ export declare function installPackages(context: CliContext): Promise<void>;
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.installPackages = installPackages;
7
+ const path_1 = __importDefault(require("path"));
8
+ const exec_1 = require("../utils/exec");
9
+ const log_1 = require("../utils/log");
10
+ async function installPackages(context) {
11
+ const appDir = path_1.default.join(context.root, "packages", "app");
12
+ const backendDir = path_1.default.join(context.root, "packages", "backend");
13
+ (0, log_1.step)("Installing frontend plugin @dx-labs/plugin-template-designer-pro");
14
+ await (0, exec_1.runCommand)("yarn", ["--cwd", appDir, "add", "@dx-labs/plugin-template-designer-pro"], {
15
+ dryRun: context.dryRun,
16
+ });
17
+ (0, log_1.step)("Installing backend plugin @dx-labs/plugin-license-box");
18
+ await (0, exec_1.runCommand)("yarn", ["--cwd", backendDir, "add", "@dx-labs/plugin-license-box"], {
19
+ dryRun: context.dryRun,
20
+ });
21
+ (0, log_1.success)("Dependencies ensured");
22
+ }
23
+ //# sourceMappingURL=installPackages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"installPackages.js","sourceRoot":"","sources":["../../src/steps/installPackages.ts"],"names":[],"mappings":";;;;;AAKA,0CAeC;AApBD,gDAAwB;AAExB,wCAA2C;AAC3C,sCAA6C;AAEtC,KAAK,UAAU,eAAe,CAAC,OAAmB;IACvD,MAAM,MAAM,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,CAAC,CAAC;IAC1D,MAAM,UAAU,GAAG,cAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;IAElE,IAAA,UAAI,EAAC,kEAAkE,CAAC,CAAC;IACzE,MAAM,IAAA,iBAAU,EAAC,MAAM,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,uCAAuC,CAAC,EAAE;QAC1F,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,IAAA,UAAI,EAAC,uDAAuD,CAAC,CAAC;IAC9D,MAAM,IAAA,iBAAU,EAAC,MAAM,EAAE,CAAC,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,6BAA6B,CAAC,EAAE;QACpF,MAAM,EAAE,OAAO,CAAC,MAAM;KACvB,CAAC,CAAC;IAEH,IAAA,aAAO,EAAC,sBAAsB,CAAC,CAAC;AAClC,CAAC","sourcesContent":["import path from \"path\";\nimport { CliContext } from \"../types\";\nimport { runCommand } from \"../utils/exec\";\nimport { success, step } from \"../utils/log\";\n\nexport async function installPackages(context: CliContext) {\n const appDir = path.join(context.root, \"packages\", \"app\");\n const backendDir = path.join(context.root, \"packages\", \"backend\");\n\n step(\"Installing frontend plugin @dx-labs/plugin-template-designer-pro\");\n await runCommand(\"yarn\", [\"--cwd\", appDir, \"add\", \"@dx-labs/plugin-template-designer-pro\"], {\n dryRun: context.dryRun,\n });\n\n step(\"Installing backend plugin @dx-labs/plugin-license-box\");\n await runCommand(\"yarn\", [\"--cwd\", backendDir, \"add\", \"@dx-labs/plugin-license-box\"], {\n dryRun: context.dryRun,\n });\n\n success(\"Dependencies ensured\");\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { CliContext } from "../types";
2
+ export declare function patchAppConfig(context: CliContext): Promise<void>;
@@ -0,0 +1,42 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.patchAppConfig = patchAppConfig;
7
+ const path_1 = __importDefault(require("path"));
8
+ const yaml_1 = __importDefault(require("yaml"));
9
+ const fs_1 = require("../utils/fs");
10
+ const log_1 = require("../utils/log");
11
+ const yaml_2 = require("../utils/yaml");
12
+ async function patchAppConfig(context) {
13
+ const targetConfig = context.configPath;
14
+ if (!(await (0, fs_1.fileExists)(targetConfig))) {
15
+ throw new Error(`Config file not found at ${targetConfig}`);
16
+ }
17
+ (0, log_1.step)(`Updating ${path_1.default.relative(context.root, targetConfig)} with license key`);
18
+ const existing = (await (0, yaml_2.readYamlFile)(targetConfig)) ?? {};
19
+ const before = yaml_1.default.stringify(existing);
20
+ const updated = JSON.parse(JSON.stringify(existing));
21
+ const dxLicenseBox = updated.dxLicenseBox ?? {};
22
+ const license = dxLicenseBox.license ?? {};
23
+ license.key = context.licenseId;
24
+ dxLicenseBox.license = license;
25
+ updated.dxLicenseBox = dxLicenseBox;
26
+ const after = yaml_1.default.stringify(updated);
27
+ const changed = before !== after;
28
+ if (context.dryRun) {
29
+ (0, log_1.dryRun)(changed
30
+ ? `Would write license key to ${path_1.default.relative(context.root, targetConfig)}`
31
+ : `No changes needed in ${path_1.default.relative(context.root, targetConfig)}`);
32
+ return;
33
+ }
34
+ if (changed) {
35
+ await (0, yaml_2.writeYamlFile)(targetConfig, updated);
36
+ (0, log_1.success)(`Wrote license key to ${path_1.default.relative(context.root, targetConfig)}`);
37
+ }
38
+ else {
39
+ (0, log_1.success)(`License key already present in ${path_1.default.relative(context.root, targetConfig)}`);
40
+ }
41
+ }
42
+ //# sourceMappingURL=patchAppConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patchAppConfig.js","sourceRoot":"","sources":["../../src/steps/patchAppConfig.ts"],"names":[],"mappings":";;;;;AAOA,wCAqCC;AA5CD,gDAAwB;AACxB,gDAAwB;AAExB,oCAAyC;AACzC,sCAAqD;AACrD,wCAA4D;AAErD,KAAK,UAAU,cAAc,CAAC,OAAmB;IACtD,MAAM,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAExC,IAAI,CAAC,CAAC,MAAM,IAAA,eAAU,EAAC,YAAY,CAAC,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,4BAA4B,YAAY,EAAE,CAAC,CAAC;IAC9D,CAAC;IAED,IAAA,UAAI,EAAC,YAAY,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,mBAAmB,CAAC,CAAC;IAE/E,MAAM,QAAQ,GAAG,CAAC,MAAM,IAAA,mBAAY,EAA0B,YAAY,CAAC,CAAC,IAAI,EAAE,CAAC;IACnF,MAAM,MAAM,GAAG,cAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAA4B,CAAC;IAEhF,MAAM,YAAY,GAAI,OAAO,CAAC,YAAwC,IAAI,EAAE,CAAC;IAC7E,MAAM,OAAO,GAAI,YAAY,CAAC,OAAmC,IAAI,EAAE,CAAC;IACxE,OAAO,CAAC,GAAG,GAAG,OAAO,CAAC,SAAS,CAAC;IAChC,YAAY,CAAC,OAAO,GAAG,OAAO,CAAC;IAC/B,OAAO,CAAC,YAAY,GAAG,YAAY,CAAC;IAEpC,MAAM,KAAK,GAAG,cAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,OAAO,GAAG,MAAM,KAAK,KAAK,CAAC;IAEjC,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,IAAA,YAAM,EACJ,OAAO;YACL,CAAC,CAAC,8BAA8B,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE;YAC3E,CAAC,CAAC,wBAAwB,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CACxE,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,OAAO,EAAE,CAAC;QACZ,MAAM,IAAA,oBAAa,EAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC3C,IAAA,aAAO,EAAC,wBAAwB,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;SAAM,CAAC;QACN,IAAA,aAAO,EAAC,kCAAkC,cAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,EAAE,YAAY,CAAC,EAAE,CAAC,CAAC;IACzF,CAAC;AACH,CAAC","sourcesContent":["import path from \"path\";\nimport YAML from \"yaml\";\nimport { CliContext } from \"../types\";\nimport { fileExists } from \"../utils/fs\";\nimport { dryRun, success, step } from \"../utils/log\";\nimport { readYamlFile, writeYamlFile } from \"../utils/yaml\";\n\nexport async function patchAppConfig(context: CliContext) {\n const targetConfig = context.configPath;\n\n if (!(await fileExists(targetConfig))) {\n throw new Error(`Config file not found at ${targetConfig}`);\n }\n\n step(`Updating ${path.relative(context.root, targetConfig)} with license key`);\n\n const existing = (await readYamlFile<Record<string, unknown>>(targetConfig)) ?? {};\n const before = YAML.stringify(existing);\n const updated = JSON.parse(JSON.stringify(existing)) as Record<string, unknown>;\n\n const dxLicenseBox = (updated.dxLicenseBox as Record<string, unknown>) ?? {};\n const license = (dxLicenseBox.license as Record<string, unknown>) ?? {};\n license.key = context.licenseId;\n dxLicenseBox.license = license;\n updated.dxLicenseBox = dxLicenseBox;\n\n const after = YAML.stringify(updated);\n const changed = before !== after;\n\n if (context.dryRun) {\n dryRun(\n changed\n ? `Would write license key to ${path.relative(context.root, targetConfig)}`\n : `No changes needed in ${path.relative(context.root, targetConfig)}`,\n );\n return;\n }\n\n if (changed) {\n await writeYamlFile(targetConfig, updated);\n success(`Wrote license key to ${path.relative(context.root, targetConfig)}`);\n } else {\n success(`License key already present in ${path.relative(context.root, targetConfig)}`);\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { CliContext } from "../types";
2
+ export declare function patchAppTsx(context: CliContext): Promise<void>;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.patchAppTsx = patchAppTsx;
7
+ const path_1 = __importDefault(require("path"));
8
+ const fs_1 = require("../utils/fs");
9
+ const ast_1 = require("../utils/ast");
10
+ const log_1 = require("../utils/log");
11
+ const ts_morph_1 = require("ts-morph");
12
+ const ROUTE_PATH = "/template-designer";
13
+ const ROUTE_SNIPPET = `<Route path="${ROUTE_PATH}" element={<TemplateDesignerProPage />} />`;
14
+ const IMPORT_NAME = "TemplateDesignerProPage";
15
+ const IMPORT_SOURCE = "@dx-labs/plugin-template-designer-pro";
16
+ async function patchAppTsx(context) {
17
+ const appTsxPath = path_1.default.join(context.root, "packages", "app", "src", "App.tsx");
18
+ if (!(await (0, fs_1.fileExists)(appTsxPath))) {
19
+ throw new Error(`Could not find App.tsx at ${appTsxPath}`);
20
+ }
21
+ (0, log_1.step)("Patching packages/app/src/App.tsx");
22
+ const { sourceFile } = (0, ast_1.loadSourceFile)(appTsxPath);
23
+ const importsChanged = (0, ast_1.ensureNamedImport)(sourceFile, IMPORT_NAME, IMPORT_SOURCE);
24
+ const flatRoutes = (0, ast_1.findJsxElements)(sourceFile, "FlatRoutes");
25
+ const targetFlatRoutes = flatRoutes.find((el) => el.getDescendants().some((child) => child.getKindName().includes("Route"))) || flatRoutes[0];
26
+ if (!targetFlatRoutes) {
27
+ throw new Error("Could not locate <FlatRoutes> in App.tsx. Please add the route manually and rerun.");
28
+ }
29
+ const descendantRoutes = [
30
+ ...targetFlatRoutes.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxElement),
31
+ ...targetFlatRoutes.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxSelfClosingElement),
32
+ ].filter((node) => {
33
+ const tagName = node.getKind() === ts_morph_1.SyntaxKind.JsxElement
34
+ ? node.getOpeningElement().getTagNameNode().getText()
35
+ : node.getTagNameNode().getText();
36
+ return tagName === "Route";
37
+ });
38
+ const existingRoute = descendantRoutes.some((route) => (0, ast_1.hasJsxAttribute)(route, "path", (value) => value === ROUTE_PATH));
39
+ let routeAdded = false;
40
+ if (!existingRoute) {
41
+ const closing = targetFlatRoutes.getClosingElement();
42
+ if (!closing) {
43
+ throw new Error("FlatRoutes element is not well-formed (missing closing tag).");
44
+ }
45
+ const insertion = `\n ${ROUTE_SNIPPET}\n `;
46
+ sourceFile.insertText(closing.getStart(), insertion);
47
+ routeAdded = true;
48
+ }
49
+ if (context.dryRun) {
50
+ (0, log_1.dryRun)(importsChanged || routeAdded
51
+ ? "Would update App.tsx with Template Designer import and route"
52
+ : "No changes needed in App.tsx");
53
+ return;
54
+ }
55
+ if (importsChanged || routeAdded) {
56
+ await sourceFile.save();
57
+ (0, log_1.success)("Updated App.tsx with Template Designer route");
58
+ }
59
+ else {
60
+ (0, log_1.success)("App.tsx already has Template Designer route");
61
+ }
62
+ }
63
+ //# sourceMappingURL=patchAppTsx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patchAppTsx.js","sourceRoot":"","sources":["../../src/steps/patchAppTsx.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"path\";\nimport { CliContext } from \"../types\";\nimport { fileExists } from \"../utils/fs\";\nimport { findJsxElements, hasJsxAttribute, ensureNamedImport, loadSourceFile } from \"../utils/ast\";\nimport { dryRun, success, step } from \"../utils/log\";\nimport { SyntaxKind, JsxElement, JsxSelfClosingElement } from \"ts-morph\";\n\nconst ROUTE_PATH = \"/template-designer\";\nconst ROUTE_SNIPPET = `<Route path=\"${ROUTE_PATH}\" element={<TemplateDesignerProPage />} />`;\nconst IMPORT_NAME = \"TemplateDesignerProPage\";\nconst IMPORT_SOURCE = \"@dx-labs/plugin-template-designer-pro\";\n\nexport async function patchAppTsx(context: CliContext) {\n const appTsxPath = path.join(context.root, \"packages\", \"app\", \"src\", \"App.tsx\");\n if (!(await fileExists(appTsxPath))) {\n throw new Error(`Could not find App.tsx at ${appTsxPath}`);\n }\n\n step(\"Patching packages/app/src/App.tsx\");\n const { sourceFile } = loadSourceFile(appTsxPath);\n\n const importsChanged = ensureNamedImport(sourceFile, IMPORT_NAME, IMPORT_SOURCE);\n const flatRoutes = findJsxElements(sourceFile, \"FlatRoutes\");\n\n const targetFlatRoutes =\n flatRoutes.find((el) =>\n el.getDescendants().some((child) => child.getKindName().includes(\"Route\")),\n ) || flatRoutes[0];\n\n if (!targetFlatRoutes) {\n throw new Error(\n \"Could not locate <FlatRoutes> in App.tsx. Please add the route manually and rerun.\",\n );\n }\n\n const descendantRoutes: Array<JsxElement | JsxSelfClosingElement> = [\n ...targetFlatRoutes.getDescendantsOfKind(SyntaxKind.JsxElement),\n ...targetFlatRoutes.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),\n ].filter((node) => {\n const tagName =\n node.getKind() === SyntaxKind.JsxElement\n ? (node as JsxElement).getOpeningElement().getTagNameNode().getText()\n : (node as JsxSelfClosingElement).getTagNameNode().getText();\n return tagName === \"Route\";\n });\n\n const existingRoute = descendantRoutes.some((route) =>\n hasJsxAttribute(route, \"path\", (value) => value === ROUTE_PATH),\n );\n\n let routeAdded = false;\n if (!existingRoute) {\n const closing = targetFlatRoutes.getClosingElement();\n if (!closing) {\n throw new Error(\"FlatRoutes element is not well-formed (missing closing tag).\");\n }\n const insertion = `\\n ${ROUTE_SNIPPET}\\n `;\n sourceFile.insertText(closing.getStart(), insertion);\n routeAdded = true;\n }\n\n if (context.dryRun) {\n dryRun(\n importsChanged || routeAdded\n ? \"Would update App.tsx with Template Designer import and route\"\n : \"No changes needed in App.tsx\",\n );\n return;\n }\n\n if (importsChanged || routeAdded) {\n await sourceFile.save();\n success(\"Updated App.tsx with Template Designer route\");\n } else {\n success(\"App.tsx already has Template Designer route\");\n }\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { CliContext } from "../types";
2
+ export declare function patchBackendIndex(context: CliContext): Promise<void>;
@@ -0,0 +1,89 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.patchBackendIndex = patchBackendIndex;
7
+ const path_1 = __importDefault(require("path"));
8
+ const ts_morph_1 = require("ts-morph");
9
+ const fs_1 = require("../utils/fs");
10
+ const ast_1 = require("../utils/ast");
11
+ const log_1 = require("../utils/log");
12
+ const DYNAMIC_IMPORT = "@dx-labs/plugin-license-box";
13
+ async function patchBackendIndex(context) {
14
+ const backendIndexPath = path_1.default.join(context.root, "packages", "backend", "src", "index.ts");
15
+ if (!(await (0, fs_1.fileExists)(backendIndexPath))) {
16
+ throw new Error(`Could not find backend index at ${backendIndexPath}`);
17
+ }
18
+ (0, log_1.step)("Patching packages/backend/src/index.ts");
19
+ const { sourceFile } = (0, ast_1.loadSourceFile)(backendIndexPath);
20
+ const backendAdds = sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression).filter((call) => {
21
+ const expr = call.getExpression();
22
+ return expr.getText() === "backend.add";
23
+ });
24
+ const alreadyPresent = backendAdds.some((call) => call.getText().includes(DYNAMIC_IMPORT));
25
+ if (alreadyPresent) {
26
+ if (context.dryRun) {
27
+ (0, log_1.dryRun)("Backend already adds plugin-license-box");
28
+ }
29
+ else {
30
+ (0, log_1.success)("Backend already adds plugin-license-box");
31
+ }
32
+ return;
33
+ }
34
+ const insertionTarget = backendAdds[backendAdds.length - 1];
35
+ const insertionText = `backend.add(import('${DYNAMIC_IMPORT}'));`;
36
+ const startCall = sourceFile
37
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)
38
+ .find((call) => call.getExpression().getText() === "backend.start");
39
+ const targetStatement = startCall?.getFirstAncestorByKind(ts_morph_1.SyntaxKind.ExpressionStatement);
40
+ if (!targetStatement) {
41
+ // Fallback: use previous logic
42
+ if (!insertionTarget) {
43
+ const appendText = `\nbackend.add(import('${DYNAMIC_IMPORT}'));\n`;
44
+ if (context.dryRun) {
45
+ (0, log_1.dryRun)("Would append backend.add(import('@dx-labs/plugin-license-box')) to backend index");
46
+ return;
47
+ }
48
+ sourceFile.addStatements(appendText);
49
+ }
50
+ else {
51
+ const stmt = insertionTarget.getFirstAncestorByKind(ts_morph_1.SyntaxKind.ExpressionStatement);
52
+ const block = stmt?.getParentIfKind(ts_morph_1.SyntaxKind.Block);
53
+ if (context.dryRun) {
54
+ (0, log_1.dryRun)("Would insert backend.add(import('@dx-labs/plugin-license-box'))");
55
+ return;
56
+ }
57
+ if (stmt && block) {
58
+ const statements = block.getStatements();
59
+ const idx = statements.indexOf(stmt);
60
+ block.insertStatements(idx + 1, insertionText);
61
+ }
62
+ else {
63
+ sourceFile.addStatements(`\n${insertionText}\n`);
64
+ }
65
+ }
66
+ }
67
+ else {
68
+ // Insert before backend.start();
69
+ if (context.dryRun) {
70
+ (0, log_1.dryRun)("Would insert backend.add(import('@dx-labs/plugin-license-box')) before backend.start()");
71
+ return;
72
+ }
73
+ const block = targetStatement.getParentIfKind(ts_morph_1.SyntaxKind.Block);
74
+ if (block) {
75
+ const statements = block.getStatements();
76
+ const idx = statements.indexOf(targetStatement);
77
+ block.insertStatements(idx, `${insertionText}\n`);
78
+ }
79
+ else {
80
+ // As a fallback, insert text directly before the start call statement
81
+ sourceFile.insertText(targetStatement.getStart(), `${insertionText}\n`);
82
+ }
83
+ }
84
+ if (context.dryRun)
85
+ return;
86
+ await sourceFile.save();
87
+ (0, log_1.success)("Added backend.add(import('@dx-labs/plugin-license-box')) to backend");
88
+ }
89
+ //# sourceMappingURL=patchBackendIndex.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patchBackendIndex.js","sourceRoot":"","sources":["../../src/steps/patchBackendIndex.ts"],"names":[],"mappings":"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","sourcesContent":["import path from \"path\";\nimport { SyntaxKind } from \"ts-morph\";\nimport { CliContext } from \"../types\";\nimport { fileExists } from \"../utils/fs\";\nimport { loadSourceFile } from \"../utils/ast\";\nimport { dryRun, success, step } from \"../utils/log\";\n\nconst DYNAMIC_IMPORT = \"@dx-labs/plugin-license-box\";\n\nexport async function patchBackendIndex(context: CliContext) {\n const backendIndexPath = path.join(context.root, \"packages\", \"backend\", \"src\", \"index.ts\");\n if (!(await fileExists(backendIndexPath))) {\n throw new Error(`Could not find backend index at ${backendIndexPath}`);\n }\n\n step(\"Patching packages/backend/src/index.ts\");\n const { sourceFile } = loadSourceFile(backendIndexPath);\n\n const backendAdds = sourceFile.getDescendantsOfKind(SyntaxKind.CallExpression).filter((call) => {\n const expr = call.getExpression();\n return expr.getText() === \"backend.add\";\n });\n\n const alreadyPresent = backendAdds.some((call) => call.getText().includes(DYNAMIC_IMPORT));\n\n if (alreadyPresent) {\n if (context.dryRun) {\n dryRun(\"Backend already adds plugin-license-box\");\n } else {\n success(\"Backend already adds plugin-license-box\");\n }\n return;\n }\n\n const insertionTarget = backendAdds[backendAdds.length - 1];\n const insertionText = `backend.add(import('${DYNAMIC_IMPORT}'));`;\n\n const startCall = sourceFile\n .getDescendantsOfKind(SyntaxKind.CallExpression)\n .find((call) => call.getExpression().getText() === \"backend.start\");\n\n const targetStatement = startCall?.getFirstAncestorByKind(SyntaxKind.ExpressionStatement);\n\n if (!targetStatement) {\n // Fallback: use previous logic\n if (!insertionTarget) {\n const appendText = `\\nbackend.add(import('${DYNAMIC_IMPORT}'));\\n`;\n if (context.dryRun) {\n dryRun(\"Would append backend.add(import('@dx-labs/plugin-license-box')) to backend index\");\n return;\n }\n sourceFile.addStatements(appendText);\n } else {\n const stmt = insertionTarget.getFirstAncestorByKind(SyntaxKind.ExpressionStatement);\n const block = stmt?.getParentIfKind(SyntaxKind.Block);\n\n if (context.dryRun) {\n dryRun(\"Would insert backend.add(import('@dx-labs/plugin-license-box'))\");\n return;\n }\n\n if (stmt && block) {\n const statements = block.getStatements();\n const idx = statements.indexOf(stmt);\n block.insertStatements(idx + 1, insertionText);\n } else {\n sourceFile.addStatements(`\\n${insertionText}\\n`);\n }\n }\n } else {\n // Insert before backend.start();\n if (context.dryRun) {\n dryRun(\n \"Would insert backend.add(import('@dx-labs/plugin-license-box')) before backend.start()\",\n );\n return;\n }\n const block = targetStatement.getParentIfKind(SyntaxKind.Block);\n if (block) {\n const statements = block.getStatements();\n const idx = statements.indexOf(targetStatement);\n block.insertStatements(idx, `${insertionText}\\n`);\n } else {\n // As a fallback, insert text directly before the start call statement\n sourceFile.insertText(targetStatement.getStart(), `${insertionText}\\n`);\n }\n }\n\n if (context.dryRun) return;\n\n await sourceFile.save();\n success(\"Added backend.add(import('@dx-labs/plugin-license-box')) to backend\");\n}\n"]}
@@ -0,0 +1,2 @@
1
+ import { CliContext } from "../types";
2
+ export declare function patchRootTsx(context: CliContext): Promise<void>;
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.patchRootTsx = patchRootTsx;
7
+ const path_1 = __importDefault(require("path"));
8
+ const ts_morph_1 = require("ts-morph");
9
+ const fs_1 = require("../utils/fs");
10
+ const ast_1 = require("../utils/ast");
11
+ const log_1 = require("../utils/log");
12
+ const ITEM_SNIPPET = '<SidebarItem icon={TemplateDesignerIcon} to="template-designer" text="Template Designer" />';
13
+ const ICON_IMPORT = "TemplateDesignerIcon";
14
+ const ICON_SOURCE = "@dx-labs/plugin-template-designer-pro";
15
+ const SIDEBAR_ITEM_IMPORT = "SidebarItem";
16
+ const SIDEBAR_ITEM_SOURCE = "@backstage/core-components";
17
+ async function patchRootTsx(context) {
18
+ const rootTsxPath = path_1.default.join(context.root, "packages", "app", "src", "components", "Root", "Root.tsx");
19
+ if (!(await (0, fs_1.fileExists)(rootTsxPath))) {
20
+ throw new Error(`Could not find Root.tsx at ${rootTsxPath}`);
21
+ }
22
+ (0, log_1.step)("Patching packages/app/src/components/Root/Root.tsx");
23
+ const { sourceFile } = (0, ast_1.loadSourceFile)(rootTsxPath);
24
+ const sidebarItemImportChanged = (0, ast_1.ensureNamedImport)(sourceFile, SIDEBAR_ITEM_IMPORT, SIDEBAR_ITEM_SOURCE);
25
+ const iconImportChanged = (0, ast_1.ensureNamedImport)(sourceFile, ICON_IMPORT, ICON_SOURCE);
26
+ const importsChanged = sidebarItemImportChanged || iconImportChanged;
27
+ const sidebarItems = [
28
+ ...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxElement),
29
+ ...sourceFile.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxSelfClosingElement),
30
+ ].filter((node) => {
31
+ const tagName = node.getKind() === ts_morph_1.SyntaxKind.JsxElement
32
+ ? node.getOpeningElement().getTagNameNode().getText()
33
+ : node.getTagNameNode().getText();
34
+ return tagName === "SidebarItem";
35
+ });
36
+ const existingItem = sidebarItems.some((item) => (0, ast_1.hasJsxAttribute)(item, "to", (value) => value.includes("template-designer")) ||
37
+ (0, ast_1.hasJsxAttribute)(item, "text", (value) => value.toLowerCase().includes("template designer")));
38
+ let itemAdded = false;
39
+ if (!existingItem) {
40
+ const sidebarGroups = (0, ast_1.findJsxElements)(sourceFile, "SidebarGroup");
41
+ const menuGroup = sidebarGroups.find((group) => (0, ast_1.hasJsxAttribute)(group, "label", (value) => value.toLowerCase().includes("menu"))) ||
42
+ sidebarGroups.find((group) => group.getJsxChildren().some((child) => child.getText().includes("SidebarItem"))) ||
43
+ sidebarGroups[0];
44
+ const sidebar = (0, ast_1.findJsxElements)(sourceFile, "Sidebar")[0];
45
+ const targetContainer = menuGroup || sidebar;
46
+ if (!targetContainer) {
47
+ throw new Error("Could not find a Sidebar or SidebarGroup in Root.tsx. Add the sidebar item manually and rerun.");
48
+ }
49
+ const closing = targetContainer.getClosingElement();
50
+ if (!closing) {
51
+ throw new Error("Sidebar container is not well-formed (missing closing tag).");
52
+ }
53
+ const baseIndent = sourceFile.getIndentationText() ?? " ";
54
+ const insertion = `\n${baseIndent}${ITEM_SNIPPET}\n`;
55
+ sourceFile.insertText(closing.getStart(), insertion);
56
+ itemAdded = true;
57
+ }
58
+ if (context.dryRun) {
59
+ (0, log_1.dryRun)(importsChanged || itemAdded
60
+ ? "Would update Root.tsx with Template Designer sidebar link"
61
+ : "No changes needed in Root.tsx");
62
+ return;
63
+ }
64
+ if (importsChanged || itemAdded) {
65
+ await sourceFile.save();
66
+ (0, log_1.success)("Updated Root.tsx with Template Designer sidebar item");
67
+ }
68
+ else {
69
+ (0, log_1.success)("Root.tsx already has Template Designer sidebar item");
70
+ }
71
+ }
72
+ //# sourceMappingURL=patchRootTsx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patchRootTsx.js","sourceRoot":"","sources":["../../src/steps/patchRootTsx.ts"],"names":[],"mappings":";;;;;AAcA,oCAyFC;AAvGD,gDAAwB;AACxB,uCAAyE;AAEzE,oCAAyC;AACzC,sCAAmG;AACnG,sCAAqD;AAErD,MAAM,YAAY,GAChB,6FAA6F,CAAC;AAChG,MAAM,WAAW,GAAG,sBAAsB,CAAC;AAC3C,MAAM,WAAW,GAAG,uCAAuC,CAAC;AAC5D,MAAM,mBAAmB,GAAG,aAAa,CAAC;AAC1C,MAAM,mBAAmB,GAAG,4BAA4B,CAAC;AAElD,KAAK,UAAU,YAAY,CAAC,OAAmB;IACpD,MAAM,WAAW,GAAG,cAAI,CAAC,IAAI,CAC3B,OAAO,CAAC,IAAI,EACZ,UAAU,EACV,KAAK,EACL,KAAK,EACL,YAAY,EACZ,MAAM,EACN,UAAU,CACX,CAAC;IACF,IAAI,CAAC,CAAC,MAAM,IAAA,eAAU,EAAC,WAAW,CAAC,CAAC,EAAE,CAAC;QACrC,MAAM,IAAI,KAAK,CAAC,8BAA8B,WAAW,EAAE,CAAC,CAAC;IAC/D,CAAC;IAED,IAAA,UAAI,EAAC,oDAAoD,CAAC,CAAC;IAC3D,MAAM,EAAE,UAAU,EAAE,GAAG,IAAA,oBAAc,EAAC,WAAW,CAAC,CAAC;IAEnD,MAAM,wBAAwB,GAAG,IAAA,uBAAiB,EAChD,UAAU,EACV,mBAAmB,EACnB,mBAAmB,CACpB,CAAC;IACF,MAAM,iBAAiB,GAAG,IAAA,uBAAiB,EAAC,UAAU,EAAE,WAAW,EAAE,WAAW,CAAC,CAAC;IAClF,MAAM,cAAc,GAAG,wBAAwB,IAAI,iBAAiB,CAAC;IAErE,MAAM,YAAY,GAA8C;QAC9D,GAAG,UAAU,CAAC,oBAAoB,CAAC,qBAAU,CAAC,UAAU,CAAC;QACzD,GAAG,UAAU,CAAC,oBAAoB,CAAC,qBAAU,CAAC,qBAAqB,CAAC;KACrE,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE;QAChB,MAAM,OAAO,GACX,IAAI,CAAC,OAAO,EAAE,KAAK,qBAAU,CAAC,UAAU;YACtC,CAAC,CAAE,IAAmB,CAAC,iBAAiB,EAAE,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE;YACrE,CAAC,CAAE,IAA8B,CAAC,cAAc,EAAE,CAAC,OAAO,EAAE,CAAC;QACjE,OAAO,OAAO,KAAK,aAAa,CAAC;IACnC,CAAC,CAAC,CAAC;IAEH,MAAM,YAAY,GAAG,YAAY,CAAC,IAAI,CACpC,CAAC,IAAI,EAAE,EAAE,CACP,IAAA,qBAAe,EAAC,IAAI,EAAE,IAAI,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC;QAC3E,IAAA,qBAAe,EAAC,IAAI,EAAE,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,mBAAmB,CAAC,CAAC,CAC9F,CAAC;IAEF,IAAI,SAAS,GAAG,KAAK,CAAC;IACtB,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,aAAa,GAAG,IAAA,qBAAe,EAAC,UAAU,EAAE,cAAc,CAAC,CAAC;QAClE,MAAM,SAAS,GACb,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,IAAA,qBAAe,EAAC,KAAK,EAAE,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CACjF;YACD,aAAa,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAC3B,KAAK,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,QAAQ,CAAC,aAAa,CAAC,CAAC,CAChF;YACD,aAAa,CAAC,CAAC,CAAC,CAAC;QAEnB,MAAM,OAAO,GAAG,IAAA,qBAAe,EAAC,UAAU,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC;QAE1D,MAAM,eAAe,GAAG,SAAS,IAAI,OAAO,CAAC;QAE7C,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CACb,gGAAgG,CACjG,CAAC;QACJ,CAAC;QAED,MAAM,OAAO,GAAG,eAAe,CAAC,iBAAiB,EAAE,CAAC;QACpD,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,6DAA6D,CAAC,CAAC;QACjF,CAAC;QACD,MAAM,UAAU,GAAG,UAAU,CAAC,kBAAkB,EAAE,IAAI,IAAI,CAAC;QAC3D,MAAM,SAAS,GAAG,KAAK,UAAU,GAAG,YAAY,IAAI,CAAC;QACrD,UAAU,CAAC,UAAU,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,SAAS,CAAC,CAAC;QACrD,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,IAAA,YAAM,EACJ,cAAc,IAAI,SAAS;YACzB,CAAC,CAAC,2DAA2D;YAC7D,CAAC,CAAC,+BAA+B,CACpC,CAAC;QACF,OAAO;IACT,CAAC;IAED,IAAI,cAAc,IAAI,SAAS,EAAE,CAAC;QAChC,MAAM,UAAU,CAAC,IAAI,EAAE,CAAC;QACxB,IAAA,aAAO,EAAC,sDAAsD,CAAC,CAAC;IAClE,CAAC;SAAM,CAAC;QACN,IAAA,aAAO,EAAC,qDAAqD,CAAC,CAAC;IACjE,CAAC;AACH,CAAC","sourcesContent":["import path from \"path\";\nimport { SyntaxKind, JsxElement, JsxSelfClosingElement } from \"ts-morph\";\nimport { CliContext } from \"../types\";\nimport { fileExists } from \"../utils/fs\";\nimport { ensureNamedImport, findJsxElements, hasJsxAttribute, loadSourceFile } from \"../utils/ast\";\nimport { dryRun, success, step } from \"../utils/log\";\n\nconst ITEM_SNIPPET =\n '<SidebarItem icon={TemplateDesignerIcon} to=\"template-designer\" text=\"Template Designer\" />';\nconst ICON_IMPORT = \"TemplateDesignerIcon\";\nconst ICON_SOURCE = \"@dx-labs/plugin-template-designer-pro\";\nconst SIDEBAR_ITEM_IMPORT = \"SidebarItem\";\nconst SIDEBAR_ITEM_SOURCE = \"@backstage/core-components\";\n\nexport async function patchRootTsx(context: CliContext) {\n const rootTsxPath = path.join(\n context.root,\n \"packages\",\n \"app\",\n \"src\",\n \"components\",\n \"Root\",\n \"Root.tsx\",\n );\n if (!(await fileExists(rootTsxPath))) {\n throw new Error(`Could not find Root.tsx at ${rootTsxPath}`);\n }\n\n step(\"Patching packages/app/src/components/Root/Root.tsx\");\n const { sourceFile } = loadSourceFile(rootTsxPath);\n\n const sidebarItemImportChanged = ensureNamedImport(\n sourceFile,\n SIDEBAR_ITEM_IMPORT,\n SIDEBAR_ITEM_SOURCE,\n );\n const iconImportChanged = ensureNamedImport(sourceFile, ICON_IMPORT, ICON_SOURCE);\n const importsChanged = sidebarItemImportChanged || iconImportChanged;\n\n const sidebarItems: Array<JsxElement | JsxSelfClosingElement> = [\n ...sourceFile.getDescendantsOfKind(SyntaxKind.JsxElement),\n ...sourceFile.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement),\n ].filter((node) => {\n const tagName =\n node.getKind() === SyntaxKind.JsxElement\n ? (node as JsxElement).getOpeningElement().getTagNameNode().getText()\n : (node as JsxSelfClosingElement).getTagNameNode().getText();\n return tagName === \"SidebarItem\";\n });\n\n const existingItem = sidebarItems.some(\n (item) =>\n hasJsxAttribute(item, \"to\", (value) => value.includes(\"template-designer\")) ||\n hasJsxAttribute(item, \"text\", (value) => value.toLowerCase().includes(\"template designer\")),\n );\n\n let itemAdded = false;\n if (!existingItem) {\n const sidebarGroups = findJsxElements(sourceFile, \"SidebarGroup\");\n const menuGroup =\n sidebarGroups.find((group) =>\n hasJsxAttribute(group, \"label\", (value) => value.toLowerCase().includes(\"menu\")),\n ) ||\n sidebarGroups.find((group) =>\n group.getJsxChildren().some((child) => child.getText().includes(\"SidebarItem\")),\n ) ||\n sidebarGroups[0];\n\n const sidebar = findJsxElements(sourceFile, \"Sidebar\")[0];\n\n const targetContainer = menuGroup || sidebar;\n\n if (!targetContainer) {\n throw new Error(\n \"Could not find a Sidebar or SidebarGroup in Root.tsx. Add the sidebar item manually and rerun.\",\n );\n }\n\n const closing = targetContainer.getClosingElement();\n if (!closing) {\n throw new Error(\"Sidebar container is not well-formed (missing closing tag).\");\n }\n const baseIndent = sourceFile.getIndentationText() ?? \" \";\n const insertion = `\\n${baseIndent}${ITEM_SNIPPET}\\n`;\n sourceFile.insertText(closing.getStart(), insertion);\n itemAdded = true;\n }\n\n if (context.dryRun) {\n dryRun(\n importsChanged || itemAdded\n ? \"Would update Root.tsx with Template Designer sidebar link\"\n : \"No changes needed in Root.tsx\",\n );\n return;\n }\n\n if (importsChanged || itemAdded) {\n await sourceFile.save();\n success(\"Updated Root.tsx with Template Designer sidebar item\");\n } else {\n success(\"Root.tsx already has Template Designer sidebar item\");\n }\n}\n"]}
@@ -0,0 +1,10 @@
1
+ export type CliContext = {
2
+ root: string;
3
+ dryRun: boolean;
4
+ skipInstall: boolean;
5
+ licenseId: string;
6
+ npmToken?: string;
7
+ registryUrl: string;
8
+ scopes: string[];
9
+ configPath: string;
10
+ };
package/dist/types.js ADDED
@@ -0,0 +1,3 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"","sourcesContent":["export type CliContext = {\n root: string;\n dryRun: boolean;\n skipInstall: boolean;\n licenseId: string;\n npmToken?: string;\n registryUrl: string;\n scopes: string[];\n configPath: string;\n};\n"]}
@@ -0,0 +1,9 @@
1
+ import { JsxElement, JsxSelfClosingElement, Project, SourceFile } from "ts-morph";
2
+ export declare function loadSourceFile(filePath: string): {
3
+ project: Project;
4
+ sourceFile: SourceFile;
5
+ };
6
+ export declare function ensureNamedImport(sourceFile: SourceFile, imported: string, moduleSpecifier: string): boolean;
7
+ export declare function findJsxElements(sourceFile: SourceFile, tagName: string): JsxElement[];
8
+ export declare function findJsxSelfClosingElements(sourceFile: SourceFile, tagName: string): JsxSelfClosingElement[];
9
+ export declare function hasJsxAttribute(element: JsxElement | JsxSelfClosingElement, attributeName: string, predicate?: (value: string) => boolean): boolean;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadSourceFile = loadSourceFile;
4
+ exports.ensureNamedImport = ensureNamedImport;
5
+ exports.findJsxElements = findJsxElements;
6
+ exports.findJsxSelfClosingElements = findJsxSelfClosingElements;
7
+ exports.hasJsxAttribute = hasJsxAttribute;
8
+ const ts_morph_1 = require("ts-morph");
9
+ function loadSourceFile(filePath) {
10
+ const project = new ts_morph_1.Project({
11
+ manipulationSettings: {
12
+ quoteKind: ts_morph_1.QuoteKind.Double,
13
+ useTrailingCommas: true,
14
+ indentationText: ts_morph_1.IndentationText.TwoSpaces,
15
+ },
16
+ });
17
+ const sourceFile = project.addSourceFileAtPath(filePath);
18
+ return { project, sourceFile };
19
+ }
20
+ function ensureNamedImport(sourceFile, imported, moduleSpecifier) {
21
+ const existing = sourceFile.getImportDeclaration((d) => d.getModuleSpecifierValue() === moduleSpecifier);
22
+ if (existing) {
23
+ const hasImport = existing.getNamedImports().some((i) => i.getName() === imported);
24
+ if (hasImport)
25
+ return false;
26
+ existing.addNamedImport(imported);
27
+ return true;
28
+ }
29
+ sourceFile.addImportDeclaration({
30
+ namedImports: [imported],
31
+ moduleSpecifier,
32
+ });
33
+ return true;
34
+ }
35
+ function findJsxElements(sourceFile, tagName) {
36
+ return sourceFile
37
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxElement)
38
+ .filter((el) => el.getOpeningElement().getTagNameNode().getText() === tagName);
39
+ }
40
+ function findJsxSelfClosingElements(sourceFile, tagName) {
41
+ return sourceFile
42
+ .getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxSelfClosingElement)
43
+ .filter((el) => el.getTagNameNode().getText() === tagName);
44
+ }
45
+ function hasJsxAttribute(element, attributeName, predicate) {
46
+ const attributes = ts_morph_1.Node.isJsxElement(element)
47
+ ? element.getOpeningElement().getAttributes()
48
+ : element.getAttributes();
49
+ for (const attr of attributes) {
50
+ if (!ts_morph_1.Node.isJsxAttribute(attr))
51
+ continue;
52
+ const jsxAttr = attr;
53
+ const attrName = jsxAttr.getNameNode().getText();
54
+ if (attrName !== attributeName)
55
+ continue;
56
+ const init = jsxAttr.getInitializer();
57
+ if (!init)
58
+ return predicate ? predicate("") : true;
59
+ const text = init.getText().replace(/['"`{}]/g, "");
60
+ if (!predicate || predicate(text))
61
+ return true;
62
+ }
63
+ return false;
64
+ }
65
+ //# sourceMappingURL=ast.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/utils/ast.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n JsxElement,\n JsxSelfClosingElement,\n Project,\n QuoteKind,\n SourceFile,\n SyntaxKind,\n IndentationText,\n Node,\n JsxAttributeLike,\n JsxAttribute,\n} from \"ts-morph\";\n\nexport function loadSourceFile(filePath: string): { project: Project; sourceFile: SourceFile } {\n const project = new Project({\n manipulationSettings: {\n quoteKind: QuoteKind.Double,\n useTrailingCommas: true,\n indentationText: IndentationText.TwoSpaces,\n },\n });\n const sourceFile = project.addSourceFileAtPath(filePath);\n return { project, sourceFile };\n}\n\nexport function ensureNamedImport(\n sourceFile: SourceFile,\n imported: string,\n moduleSpecifier: string,\n): boolean {\n const existing = sourceFile.getImportDeclaration(\n (d) => d.getModuleSpecifierValue() === moduleSpecifier,\n );\n if (existing) {\n const hasImport = existing.getNamedImports().some((i) => i.getName() === imported);\n if (hasImport) return false;\n existing.addNamedImport(imported);\n return true;\n }\n\n sourceFile.addImportDeclaration({\n namedImports: [imported],\n moduleSpecifier,\n });\n return true;\n}\n\nexport function findJsxElements(sourceFile: SourceFile, tagName: string): JsxElement[] {\n return sourceFile\n .getDescendantsOfKind(SyntaxKind.JsxElement)\n .filter((el) => el.getOpeningElement().getTagNameNode().getText() === tagName);\n}\n\nexport function findJsxSelfClosingElements(\n sourceFile: SourceFile,\n tagName: string,\n): JsxSelfClosingElement[] {\n return sourceFile\n .getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)\n .filter((el) => el.getTagNameNode().getText() === tagName);\n}\n\nexport function hasJsxAttribute(\n element: JsxElement | JsxSelfClosingElement,\n attributeName: string,\n predicate?: (value: string) => boolean,\n): boolean {\n const attributes: JsxAttributeLike[] = Node.isJsxElement(element)\n ? element.getOpeningElement().getAttributes()\n : element.getAttributes();\n\n for (const attr of attributes) {\n if (!Node.isJsxAttribute(attr)) continue;\n const jsxAttr: JsxAttribute = attr;\n const attrName = jsxAttr.getNameNode().getText();\n if (attrName !== attributeName) continue;\n const init = jsxAttr.getInitializer();\n if (!init) return predicate ? predicate(\"\") : true;\n const text = init.getText().replace(/['\"`{}]/g, \"\");\n if (!predicate || predicate(text)) return true;\n }\n return false;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ type ExecOptions = {
2
+ cwd?: string;
3
+ dryRun?: boolean;
4
+ env?: Record<string, string | undefined>;
5
+ };
6
+ export declare function runCommand(command: string, args: string[], options?: ExecOptions): Promise<void>;
7
+ export {};
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runCommand = runCommand;
7
+ const execa_1 = __importDefault(require("execa"));
8
+ const log_1 = require("./log");
9
+ async function runCommand(command, args, options = {}) {
10
+ const display = `${command} ${args.join(" ")}`.trim();
11
+ if (options.dryRun) {
12
+ (0, log_1.dryRun)(`Would run: ${display}`);
13
+ return;
14
+ }
15
+ (0, log_1.info)(`Running: ${display}`);
16
+ await (0, execa_1.default)(command, args, {
17
+ cwd: options.cwd,
18
+ env: options.env,
19
+ stdio: "inherit",
20
+ });
21
+ }
22
+ //# sourceMappingURL=exec.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"exec.js","sourceRoot":"","sources":["../../src/utils/exec.ts"],"names":[],"mappings":";;;;;AASA,gCAiBC;AA1BD,kDAA0B;AAC1B,+BAAqC;AAQ9B,KAAK,UAAU,UAAU,CAC9B,OAAe,EACf,IAAc,EACd,UAAuB,EAAE;IAEzB,MAAM,OAAO,GAAG,GAAG,OAAO,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;IACtD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,IAAA,YAAM,EAAC,cAAc,OAAO,EAAE,CAAC,CAAC;QAChC,OAAO;IACT,CAAC;IAED,IAAA,UAAI,EAAC,YAAY,OAAO,EAAE,CAAC,CAAC;IAC5B,MAAM,IAAA,eAAK,EAAC,OAAO,EAAE,IAAI,EAAE;QACzB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,GAAG,EAAE,OAAO,CAAC,GAAG;QAChB,KAAK,EAAE,SAAS;KACjB,CAAC,CAAC;AACL,CAAC","sourcesContent":["import execa from \"execa\";\nimport { dryRun, info } from \"./log\";\n\ntype ExecOptions = {\n cwd?: string;\n dryRun?: boolean;\n env?: Record<string, string | undefined>;\n};\n\nexport async function runCommand(\n command: string,\n args: string[],\n options: ExecOptions = {},\n): Promise<void> {\n const display = `${command} ${args.join(\" \")}`.trim();\n if (options.dryRun) {\n dryRun(`Would run: ${display}`);\n return;\n }\n\n info(`Running: ${display}`);\n await execa(command, args, {\n cwd: options.cwd,\n env: options.env,\n stdio: \"inherit\",\n });\n}\n"]}
@@ -0,0 +1,8 @@
1
+ export declare function fileExists(filePath: string): Promise<boolean>;
2
+ export declare function readFileIfExists(filePath: string): Promise<string | undefined>;
3
+ export declare function writeFileSafely(filePath: string, content: string, options?: {
4
+ dryRun?: boolean;
5
+ }): Promise<boolean>;
6
+ export declare function ensureFile(filePath: string, content: string, options?: {
7
+ dryRun?: boolean;
8
+ }): Promise<boolean>;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.fileExists = fileExists;
7
+ exports.readFileIfExists = readFileIfExists;
8
+ exports.writeFileSafely = writeFileSafely;
9
+ exports.ensureFile = ensureFile;
10
+ const fs_1 = __importDefault(require("fs"));
11
+ const path_1 = __importDefault(require("path"));
12
+ async function fileExists(filePath) {
13
+ return fs_1.default.promises
14
+ .access(filePath, fs_1.default.constants.F_OK)
15
+ .then(() => true)
16
+ .catch(() => false);
17
+ }
18
+ async function readFileIfExists(filePath) {
19
+ if (!(await fileExists(filePath)))
20
+ return undefined;
21
+ return fs_1.default.promises.readFile(filePath, "utf8");
22
+ }
23
+ async function writeFileSafely(filePath, content, options = {}) {
24
+ const dir = path_1.default.dirname(filePath);
25
+ await fs_1.default.promises.mkdir(dir, { recursive: true });
26
+ const existing = await readFileIfExists(filePath);
27
+ if (existing !== undefined && existing === content) {
28
+ return false;
29
+ }
30
+ if (options.dryRun) {
31
+ return true;
32
+ }
33
+ await fs_1.default.promises.writeFile(filePath, content, "utf8");
34
+ return true;
35
+ }
36
+ async function ensureFile(filePath, content, options = {}) {
37
+ if (await fileExists(filePath)) {
38
+ return false;
39
+ }
40
+ if (options.dryRun) {
41
+ return true;
42
+ }
43
+ const dir = path_1.default.dirname(filePath);
44
+ await fs_1.default.promises.mkdir(dir, { recursive: true });
45
+ await fs_1.default.promises.writeFile(filePath, content, "utf8");
46
+ return true;
47
+ }
48
+ //# sourceMappingURL=fs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fs.js","sourceRoot":"","sources":["../../src/utils/fs.ts"],"names":[],"mappings":";;;;;AAGA,gCAKC;AAED,4CAGC;AAED,0CAmBC;AAED,gCAeC;AAnDD,4CAAoB;AACpB,gDAAwB;AAEjB,KAAK,UAAU,UAAU,CAAC,QAAgB;IAC/C,OAAO,YAAE,CAAC,QAAQ;SACf,MAAM,CAAC,QAAQ,EAAE,YAAE,CAAC,SAAS,CAAC,IAAI,CAAC;SACnC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC;SAChB,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;AACxB,CAAC;AAEM,KAAK,UAAU,gBAAgB,CAAC,QAAgB;IACrD,IAAI,CAAC,CAAC,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACpD,OAAO,YAAE,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;AAChD,CAAC;AAEM,KAAK,UAAU,eAAe,CACnC,QAAgB,EAChB,OAAe,EACf,UAAgC,EAAE;IAElC,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,YAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAElD,MAAM,QAAQ,GAAG,MAAM,gBAAgB,CAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QACnD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,YAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACvD,OAAO,IAAI,CAAC;AACd,CAAC;AAEM,KAAK,UAAU,UAAU,CAC9B,QAAgB,EAChB,OAAe,EACf,UAAgC,EAAE;IAElC,IAAI,MAAM,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC/B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;QACnB,OAAO,IAAI,CAAC;IACd,CAAC;IACD,MAAM,GAAG,GAAG,cAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IACnC,MAAM,YAAE,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,YAAE,CAAC,QAAQ,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IACvD,OAAO,IAAI,CAAC;AACd,CAAC","sourcesContent":["import fs from \"fs\";\nimport path from \"path\";\n\nexport async function fileExists(filePath: string): Promise<boolean> {\n return fs.promises\n .access(filePath, fs.constants.F_OK)\n .then(() => true)\n .catch(() => false);\n}\n\nexport async function readFileIfExists(filePath: string): Promise<string | undefined> {\n if (!(await fileExists(filePath))) return undefined;\n return fs.promises.readFile(filePath, \"utf8\");\n}\n\nexport async function writeFileSafely(\n filePath: string,\n content: string,\n options: { dryRun?: boolean } = {},\n): Promise<boolean> {\n const dir = path.dirname(filePath);\n await fs.promises.mkdir(dir, { recursive: true });\n\n const existing = await readFileIfExists(filePath);\n if (existing !== undefined && existing === content) {\n return false;\n }\n\n if (options.dryRun) {\n return true;\n }\n\n await fs.promises.writeFile(filePath, content, \"utf8\");\n return true;\n}\n\nexport async function ensureFile(\n filePath: string,\n content: string,\n options: { dryRun?: boolean } = {},\n): Promise<boolean> {\n if (await fileExists(filePath)) {\n return false;\n }\n if (options.dryRun) {\n return true;\n }\n const dir = path.dirname(filePath);\n await fs.promises.mkdir(dir, { recursive: true });\n await fs.promises.writeFile(filePath, content, \"utf8\");\n return true;\n}\n"]}
@@ -0,0 +1,7 @@
1
+ export declare function step(message: string): void;
2
+ export declare function success(message: string): void;
3
+ export declare function warn(message: string): void;
4
+ export declare function info(message: string): void;
5
+ export declare function dryRun(message: string): void;
6
+ export declare function error(message: string): void;
7
+ export declare function mask(value: string | undefined): string;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.step = step;
7
+ exports.success = success;
8
+ exports.warn = warn;
9
+ exports.info = info;
10
+ exports.dryRun = dryRun;
11
+ exports.error = error;
12
+ exports.mask = mask;
13
+ const chalk_1 = __importDefault(require("chalk"));
14
+ const prefixes = {
15
+ step: chalk_1.default.cyan("==>"),
16
+ success: chalk_1.default.green("[ok]"),
17
+ warn: chalk_1.default.yellow("[warn]"),
18
+ error: chalk_1.default.red("[err]"),
19
+ info: chalk_1.default.blue("[info]"),
20
+ dryRun: chalk_1.default.magenta("[dry-run]"),
21
+ };
22
+ function step(message) {
23
+ console.log(`${prefixes.step} ${message}`);
24
+ }
25
+ function success(message) {
26
+ console.log(`${prefixes.success} ${message}`);
27
+ }
28
+ function warn(message) {
29
+ console.log(`${prefixes.warn} ${message}`);
30
+ }
31
+ function info(message) {
32
+ console.log(`${prefixes.info} ${message}`);
33
+ }
34
+ function dryRun(message) {
35
+ console.log(`${prefixes.dryRun} ${message}`);
36
+ }
37
+ function error(message) {
38
+ console.error(`${prefixes.error} ${message}`);
39
+ }
40
+ function mask(value) {
41
+ if (!value)
42
+ return "";
43
+ if (value.length <= 6)
44
+ return "*".repeat(value.length);
45
+ return `${value.slice(0, 2)}***${value.slice(-2)}`;
46
+ }
47
+ //# sourceMappingURL=log.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/utils/log.ts"],"names":[],"mappings":";;;;;AAWA,oBAEC;AAED,0BAEC;AAED,oBAEC;AAED,oBAEC;AAED,wBAEC;AAED,sBAEC;AAED,oBAIC;AAvCD,kDAA0B;AAE1B,MAAM,QAAQ,GAAG;IACf,IAAI,EAAE,eAAK,CAAC,IAAI,CAAC,KAAK,CAAC;IACvB,OAAO,EAAE,eAAK,CAAC,KAAK,CAAC,MAAM,CAAC;IAC5B,IAAI,EAAE,eAAK,CAAC,MAAM,CAAC,QAAQ,CAAC;IAC5B,KAAK,EAAE,eAAK,CAAC,GAAG,CAAC,OAAO,CAAC;IACzB,IAAI,EAAE,eAAK,CAAC,IAAI,CAAC,QAAQ,CAAC;IAC1B,MAAM,EAAE,eAAK,CAAC,OAAO,CAAC,WAAW,CAAC;CACnC,CAAC;AAEF,SAAgB,IAAI,CAAC,OAAe;IAClC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,SAAgB,OAAO,CAAC,OAAe;IACrC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,OAAO,IAAI,OAAO,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,SAAgB,IAAI,CAAC,OAAe;IAClC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,SAAgB,IAAI,CAAC,OAAe;IAClC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC,CAAC;AAC7C,CAAC;AAED,SAAgB,MAAM,CAAC,OAAe;IACpC,OAAO,CAAC,GAAG,CAAC,GAAG,QAAQ,CAAC,MAAM,IAAI,OAAO,EAAE,CAAC,CAAC;AAC/C,CAAC;AAED,SAAgB,KAAK,CAAC,OAAe;IACnC,OAAO,CAAC,KAAK,CAAC,GAAG,QAAQ,CAAC,KAAK,IAAI,OAAO,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,SAAgB,IAAI,CAAC,KAAyB;IAC5C,IAAI,CAAC,KAAK;QAAE,OAAO,EAAE,CAAC;IACtB,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC;QAAE,OAAO,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IACvD,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AACrD,CAAC","sourcesContent":["import chalk from \"chalk\";\n\nconst prefixes = {\n step: chalk.cyan(\"==>\"),\n success: chalk.green(\"[ok]\"),\n warn: chalk.yellow(\"[warn]\"),\n error: chalk.red(\"[err]\"),\n info: chalk.blue(\"[info]\"),\n dryRun: chalk.magenta(\"[dry-run]\"),\n};\n\nexport function step(message: string) {\n console.log(`${prefixes.step} ${message}`);\n}\n\nexport function success(message: string) {\n console.log(`${prefixes.success} ${message}`);\n}\n\nexport function warn(message: string) {\n console.log(`${prefixes.warn} ${message}`);\n}\n\nexport function info(message: string) {\n console.log(`${prefixes.info} ${message}`);\n}\n\nexport function dryRun(message: string) {\n console.log(`${prefixes.dryRun} ${message}`);\n}\n\nexport function error(message: string) {\n console.error(`${prefixes.error} ${message}`);\n}\n\nexport function mask(value: string | undefined): string {\n if (!value) return \"\";\n if (value.length <= 6) return \"*\".repeat(value.length);\n return `${value.slice(0, 2)}***${value.slice(-2)}`;\n}\n"]}
@@ -0,0 +1,4 @@
1
+ export declare function readYamlFile<T = unknown>(filePath: string): Promise<T | undefined>;
2
+ export declare function writeYamlFile(filePath: string, data: unknown, options?: {
3
+ dryRun?: boolean;
4
+ }): Promise<boolean>;
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.readYamlFile = readYamlFile;
7
+ exports.writeYamlFile = writeYamlFile;
8
+ const yaml_1 = __importDefault(require("yaml"));
9
+ const fs_1 = require("./fs");
10
+ async function readYamlFile(filePath) {
11
+ const contents = await (0, fs_1.readFileIfExists)(filePath);
12
+ if (!contents)
13
+ return undefined;
14
+ return yaml_1.default.parse(contents);
15
+ }
16
+ async function writeYamlFile(filePath, data, options = {}) {
17
+ const content = `${yaml_1.default.stringify(data, { indent: 2 })}`.trimEnd() + "\n";
18
+ return (0, fs_1.writeFileSafely)(filePath, content, { dryRun: options.dryRun });
19
+ }
20
+ //# sourceMappingURL=yaml.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"yaml.js","sourceRoot":"","sources":["../../src/utils/yaml.ts"],"names":[],"mappings":";;;;;AAGA,oCAIC;AAED,sCAOC;AAhBD,gDAAwB;AACxB,6BAAyD;AAElD,KAAK,UAAU,YAAY,CAAc,QAAgB;IAC9D,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAgB,EAAC,QAAQ,CAAC,CAAC;IAClD,IAAI,CAAC,QAAQ;QAAE,OAAO,SAAS,CAAC;IAChC,OAAO,cAAI,CAAC,KAAK,CAAC,QAAQ,CAAM,CAAC;AACnC,CAAC;AAEM,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,IAAa,EACb,UAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,GAAG,cAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC;IAC1E,OAAO,IAAA,oBAAe,EAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;AACxE,CAAC","sourcesContent":["import YAML from \"yaml\";\nimport { readFileIfExists, writeFileSafely } from \"./fs\";\n\nexport async function readYamlFile<T = unknown>(filePath: string): Promise<T | undefined> {\n const contents = await readFileIfExists(filePath);\n if (!contents) return undefined;\n return YAML.parse(contents) as T;\n}\n\nexport async function writeYamlFile(\n filePath: string,\n data: unknown,\n options: { dryRun?: boolean } = {},\n): Promise<boolean> {\n const content = `${YAML.stringify(data, { indent: 2 })}`.trimEnd() + \"\\n\";\n return writeFileSafely(filePath, content, { dryRun: options.dryRun });\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@dx-labs/template-designer-setup",
3
+ "version": "0.1.2",
4
+ "description": "CLI to install and wire Template Designer and License Box plugins into a Backstage instance",
5
+ "license": "MIT",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "bin": "dist/index.js",
9
+ "scripts": {
10
+ "build": "tsc -p tsconfig.json",
11
+ "lint": "eslint .",
12
+ "format": "prettier -w .",
13
+ "format:check": "prettier -c .",
14
+ "clean": "rm -rf dist",
15
+ "start": "node dist/index.js",
16
+ "validate:fix": "yarn tsc && yarn lint && yarn format",
17
+ "set-version": "node scripts/set-version.js",
18
+ "prepare": "husky"
19
+ },
20
+ "dependencies": {
21
+ "chalk": "^4.1.2",
22
+ "commander": "^11.1.0",
23
+ "execa": "^5.1.1",
24
+ "prompts": "^2.4.2",
25
+ "ts-morph": "^22.0.0",
26
+ "yaml": "^2.5.0"
27
+ },
28
+ "devDependencies": {
29
+ "@commitlint/cli": "^20.4.1",
30
+ "@commitlint/config-conventional": "^20.1.0",
31
+ "@semantic-release/changelog": "^6.0.3",
32
+ "@semantic-release/commit-analyzer": "^13.0.1",
33
+ "@semantic-release/exec": "^7.1.0",
34
+ "@semantic-release/git": "^10.0.1",
35
+ "@semantic-release/github": "^12.0.1",
36
+ "@semantic-release/npm": "^13.1.1",
37
+ "@semantic-release/release-notes-generator": "^14.1.0",
38
+ "@types/node": "^20.14.11",
39
+ "@types/prompts": "^2.4.9",
40
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
41
+ "@typescript-eslint/parser": "^7.18.0",
42
+ "eslint": "^8.57.1",
43
+ "eslint-config-prettier": "^9.1.0",
44
+ "husky": "^9.1.7",
45
+ "lint-staged": "^16.2.6",
46
+ "prettier": "^3.3.3",
47
+ "typescript": "~5.8.0"
48
+ },
49
+ "engines": {
50
+ "node": ">=18"
51
+ },
52
+ "files": [
53
+ "dist"
54
+ ],
55
+ "sideEffects": false,
56
+ "exports": {
57
+ ".": "./dist/index.js",
58
+ "./package.json": "./package.json"
59
+ },
60
+ "publishConfig": {
61
+ "access": "public"
62
+ },
63
+ "lint-staged": {
64
+ "*.{js,ts,tsx,jsx}": "eslint --fix",
65
+ "*.{json,md,yml,yaml}": "prettier --write"
66
+ },
67
+ "packageManager": "yarn@4.1.1",
68
+ "repository": {}
69
+ }