@vendure-platform/create 0.2.1 → 0.2.3-nightly.202605260835

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 (132) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/README.md +11 -0
  3. package/dist/add-to-existing.d.ts +80 -0
  4. package/dist/add-to-existing.js +539 -0
  5. package/dist/add-to-existing.js.map +1 -0
  6. package/dist/assets/demo-data/assets/branding/logo.svg +4 -0
  7. package/dist/assets/demo-data/assets/images/absorbent-pad.webp +0 -0
  8. package/dist/assets/demo-data/assets/images/adjustable-wrench.webp +0 -0
  9. package/dist/assets/demo-data/assets/images/anchor-bolt.webp +0 -0
  10. package/dist/assets/demo-data/assets/images/angle-grinder.webp +0 -0
  11. package/dist/assets/demo-data/assets/images/barrier-tape.webp +0 -0
  12. package/dist/assets/demo-data/assets/images/bench-grinder.webp +0 -0
  13. package/dist/assets/demo-data/assets/images/bracket.webp +0 -0
  14. package/dist/assets/demo-data/assets/images/broom.webp +0 -0
  15. package/dist/assets/demo-data/assets/images/bucket.webp +0 -0
  16. package/dist/assets/demo-data/assets/images/cable-gland.webp +0 -0
  17. package/dist/assets/demo-data/assets/images/cable-tie.webp +0 -0
  18. package/dist/assets/demo-data/assets/images/cable-tray.webp +0 -0
  19. package/dist/assets/demo-data/assets/images/cable.webp +0 -0
  20. package/dist/assets/demo-data/assets/images/chimney-base-kit.webp +0 -0
  21. package/dist/assets/demo-data/assets/images/chimney-cowl-bird-guard.webp +0 -0
  22. package/dist/assets/demo-data/assets/images/chimney-cowl-open.webp +0 -0
  23. package/dist/assets/demo-data/assets/images/chimney-storm-collar.webp +0 -0
  24. package/dist/assets/demo-data/assets/images/chimney-wall-bracket.webp +0 -0
  25. package/dist/assets/demo-data/assets/images/circular-saw.webp +0 -0
  26. package/dist/assets/demo-data/assets/images/cleaning-spray.webp +0 -0
  27. package/dist/assets/demo-data/assets/images/conduit.webp +0 -0
  28. package/dist/assets/demo-data/assets/images/cordless-drill.webp +0 -0
  29. package/dist/assets/demo-data/assets/images/drill-bits.webp +0 -0
  30. package/dist/assets/demo-data/assets/images/dust-mask.webp +0 -0
  31. package/dist/assets/demo-data/assets/images/ear-defenders.webp +0 -0
  32. package/dist/assets/demo-data/assets/images/ear-plugs.webp +0 -0
  33. package/dist/assets/demo-data/assets/images/extension-lead.webp +0 -0
  34. package/dist/assets/demo-data/assets/images/eye-wash-station.webp +0 -0
  35. package/dist/assets/demo-data/assets/images/face-shield.webp +0 -0
  36. package/dist/assets/demo-data/assets/images/file-set.webp +0 -0
  37. package/dist/assets/demo-data/assets/images/first-aid-kit.webp +0 -0
  38. package/dist/assets/demo-data/assets/images/floor-sign.webp +0 -0
  39. package/dist/assets/demo-data/assets/images/flue-bend-45.webp +0 -0
  40. package/dist/assets/demo-data/assets/images/flue-bend-90.webp +0 -0
  41. package/dist/assets/demo-data/assets/images/flue-section-1000mm.webp +0 -0
  42. package/dist/assets/demo-data/assets/images/flue-section-250mm.webp +0 -0
  43. package/dist/assets/demo-data/assets/images/flue-section-500mm.webp +0 -0
  44. package/dist/assets/demo-data/assets/images/fuse.webp +0 -0
  45. package/dist/assets/demo-data/assets/images/glove-dispenser.webp +0 -0
  46. package/dist/assets/demo-data/assets/images/grinding-disc.webp +0 -0
  47. package/dist/assets/demo-data/assets/images/hacksaw.webp +0 -0
  48. package/dist/assets/demo-data/assets/images/hammer.webp +0 -0
  49. package/dist/assets/demo-data/assets/images/hand-sanitiser.webp +0 -0
  50. package/dist/assets/demo-data/assets/images/head-torch.webp +0 -0
  51. package/dist/assets/demo-data/assets/images/heat-gun.webp +0 -0
  52. package/dist/assets/demo-data/assets/images/hex-bolt.webp +0 -0
  53. package/dist/assets/demo-data/assets/images/hex-key-set.webp +0 -0
  54. package/dist/assets/demo-data/assets/images/hex-nut.webp +0 -0
  55. package/dist/assets/demo-data/assets/images/hi-vis-jacket.webp +0 -0
  56. package/dist/assets/demo-data/assets/images/hi-vis-vest.webp +0 -0
  57. package/dist/assets/demo-data/assets/images/hose-clamp.webp +0 -0
  58. package/dist/assets/demo-data/assets/images/impact-driver.webp +0 -0
  59. package/dist/assets/demo-data/assets/images/jigsaw.webp +0 -0
  60. package/dist/assets/demo-data/assets/images/junction-box.webp +0 -0
  61. package/dist/assets/demo-data/assets/images/knee-pads.webp +0 -0
  62. package/dist/assets/demo-data/assets/images/machine-screw.webp +0 -0
  63. package/dist/assets/demo-data/assets/images/made-to-order-configurable-chimney.webp +0 -0
  64. package/dist/assets/demo-data/assets/images/mallet.webp +0 -0
  65. package/dist/assets/demo-data/assets/images/mcb.webp +0 -0
  66. package/dist/assets/demo-data/assets/images/mop.webp +0 -0
  67. package/dist/assets/demo-data/assets/images/paper-towels.webp +0 -0
  68. package/dist/assets/demo-data/assets/images/pipe-wrench.webp +0 -0
  69. package/dist/assets/demo-data/assets/images/pliers-set.webp +0 -0
  70. package/dist/assets/demo-data/assets/images/pry-bar.webp +0 -0
  71. package/dist/assets/demo-data/assets/images/rcd.webp +0 -0
  72. package/dist/assets/demo-data/assets/images/reciprocating-saw.webp +0 -0
  73. package/dist/assets/demo-data/assets/images/rivet.webp +0 -0
  74. package/dist/assets/demo-data/assets/images/roof-flashing-high-pitch.webp +0 -0
  75. package/dist/assets/demo-data/assets/images/roof-flashing-low-pitch.webp +0 -0
  76. package/dist/assets/demo-data/assets/images/safety-boots.webp +0 -0
  77. package/dist/assets/demo-data/assets/images/safety-glasses.webp +0 -0
  78. package/dist/assets/demo-data/assets/images/safety-harness.webp +0 -0
  79. package/dist/assets/demo-data/assets/images/safety-helmet.webp +0 -0
  80. package/dist/assets/demo-data/assets/images/safety-sign.webp +0 -0
  81. package/dist/assets/demo-data/assets/images/sanding-disc.webp +0 -0
  82. package/dist/assets/demo-data/assets/images/saw-blade.webp +0 -0
  83. package/dist/assets/demo-data/assets/images/screwdriver-set.webp +0 -0
  84. package/dist/assets/demo-data/assets/images/screwdriver.webp +0 -0
  85. package/dist/assets/demo-data/assets/images/self-tapping-screw.webp +0 -0
  86. package/dist/assets/demo-data/assets/images/socket-set.webp +0 -0
  87. package/dist/assets/demo-data/assets/images/socket.webp +0 -0
  88. package/dist/assets/demo-data/assets/images/spanner-set.webp +0 -0
  89. package/dist/assets/demo-data/assets/images/spanner.webp +0 -0
  90. package/dist/assets/demo-data/assets/images/spill-kit.webp +0 -0
  91. package/dist/assets/demo-data/assets/images/spirit-level.webp +0 -0
  92. package/dist/assets/demo-data/assets/images/switch.webp +0 -0
  93. package/dist/assets/demo-data/assets/images/tape-measure.webp +0 -0
  94. package/dist/assets/demo-data/assets/images/terminal-block.webp +0 -0
  95. package/dist/assets/demo-data/assets/images/threaded-rod.webp +0 -0
  96. package/dist/assets/demo-data/assets/images/torque-wrench.webp +0 -0
  97. package/dist/assets/demo-data/assets/images/utility-knife.webp +0 -0
  98. package/dist/assets/demo-data/assets/images/wall-pass-sleeve.webp +0 -0
  99. package/dist/assets/demo-data/assets/images/wall-plug.webp +0 -0
  100. package/dist/assets/demo-data/assets/images/washer.webp +0 -0
  101. package/dist/assets/demo-data/assets/images/waste-bin.webp +0 -0
  102. package/dist/assets/demo-data/assets/images/wire-connector.webp +0 -0
  103. package/dist/assets/demo-data/assets/images/wire-cutters.webp +0 -0
  104. package/dist/assets/demo-data/assets/images/wood-screw.webp +0 -0
  105. package/dist/assets/demo-data/assets/images/work-gloves.webp +0 -0
  106. package/dist/assets/demo-data/assets/images/work-light.webp +0 -0
  107. package/dist/assets/demo-data/assets/initial-data.json +667 -0
  108. package/dist/assets/demo-data/assets/products.csv +779 -0
  109. package/dist/assets/storefront/package.json +1 -1
  110. package/dist/assets/templates/bunfig.toml.hbs +17 -0
  111. package/dist/assets/templates/env.hbs +5 -2
  112. package/dist/assets/templates/gitignore.hbs +6 -0
  113. package/dist/assets/templates/npmrc.hbs +2 -0
  114. package/dist/bin/run.js +27 -0
  115. package/dist/bin/run.js.map +1 -1
  116. package/dist/cli.js +34 -1
  117. package/dist/cli.js.map +1 -1
  118. package/dist/scaffold-shared.js +15 -5
  119. package/dist/scaffold-shared.js.map +1 -1
  120. package/dist/scaffolder.js +7 -2
  121. package/dist/scaffolder.js.map +1 -1
  122. package/dist/sort-package-json.d.ts +12 -0
  123. package/dist/sort-package-json.js +26 -0
  124. package/dist/sort-package-json.js.map +1 -0
  125. package/dist/storefront-copier.d.ts +12 -0
  126. package/dist/storefront-copier.js +34 -7
  127. package/dist/storefront-copier.js.map +1 -1
  128. package/dist/template-renderer.d.ts +31 -0
  129. package/dist/template-renderer.js +42 -1
  130. package/dist/template-renderer.js.map +1 -1
  131. package/dist/types.d.ts +32 -0
  132. package/package.json +4 -4
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ ## 0.2.2 (2026-05-22)
2
+
3
+ ### 🚀 Features
4
+
5
+ - publish CLI publicly with two-token onboarding flow ([#322](https://github.com/vendurehq/vendure-platform/pull/322))
6
+ - Add 'add to existing project' onboarding flow ([#318](https://github.com/vendurehq/vendure-platform/pull/318))
7
+
1
8
  ## 0.2.1 (2026-05-20)
2
9
 
3
10
  _Initial release._
package/README.md CHANGED
@@ -23,6 +23,8 @@ npx @vendure-platform/create my-project \
23
23
  |------|-------------|---------|
24
24
  | `--plugins <list>` | Comma-separated optional plugin keys | (interactive prompt) |
25
25
  | `--company <name>` | Company name for branding | (derived from project name) |
26
+ | `--registry-token <token>` | Vendure private-registry npm auth token (baked into `.npmrc`) | (interactive prompt) |
27
+ | `--license-token <token>` | Vendure Platform server-side license token (written to `.env`) | (interactive prompt) |
26
28
  | `--no-storefront` | Skip the Next.js storefront | included |
27
29
  | `--github` | Create a private GitHub repo via `gh` CLI | no |
28
30
  | `--github-org <org>` | GitHub organization for the repo (only used with `--github`) | `vendurehq` |
@@ -32,6 +34,15 @@ npx @vendure-platform/create my-project \
32
34
 
33
35
  When `--plugins` and `--company` are both provided, the CLI runs in non-interactive mode.
34
36
 
37
+ ## Tokens: registry vs license
38
+
39
+ The Vendure Enterprise Portal issues **two distinct tokens** with different purposes — keep them straight:
40
+
41
+ - **Registry token** — npm auth token for the private registry. Lets `${pm} install` download `@vendure-platform/*` packages. The CLI writes it into the project `.npmrc` as `_authToken`. `.npmrc` is gitignored.
42
+ - **License token** — checked at server boot by the License Guard plugin. The CLI writes it into `.env` as `VENDURE_PLATFORM_LICENSE_TOKEN`.
43
+
44
+ They are not interchangeable. Pass each via its dedicated flag (`--registry-token`, `--license-token`) or paste each at its own interactive prompt. Either can be skipped (blank input) if you want to add it later.
45
+
35
46
  ## Plugin Tiers
36
47
 
37
48
  Plugins fall into three tiers (mirroring `@vendure-platform/plugin-meta`):
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Options for the `add` subcommand. Mirrors the create CLI's `ScaffoldOptions`
3
+ * but minimal — we only need the plugin selection. Everything else is derived
4
+ * from the project we're patching.
5
+ */
6
+ export interface AddToExistingOptions {
7
+ /** Directory of the existing Vendure project. Defaults to cwd. */
8
+ projectDir: string;
9
+ /** Plugin keys to include (optional/requires-config). Core plugins are always added. */
10
+ selectedPlugins: string[];
11
+ /** Print actions without writing files. */
12
+ dryRun: boolean;
13
+ /**
14
+ * Vendure private-registry npm auth token. When supplied, written as
15
+ * `_authToken` into the project `.npmrc` (literal value) so
16
+ * `${pm} install` can pull `@vendure-platform/*` packages without any
17
+ * pre-existing `~/.npmrc` setup. The project `.gitignore` is updated to
18
+ * ignore `.npmrc` so the literal token never gets committed.
19
+ *
20
+ * Distinct from `licenseToken`. When omitted, only the scope mapping is
21
+ * written and the user is expected to have `~/.npmrc` set up, or to
22
+ * re-run with `--registry-token` later.
23
+ */
24
+ registryToken?: string;
25
+ /**
26
+ * Vendure Platform server-side license token. When supplied, written as
27
+ * `VENDURE_PLATFORM_LICENSE_TOKEN` into `.env` (filling an empty
28
+ * existing value, never overwriting a non-empty one). Read at boot by
29
+ * the License Guard plugin.
30
+ *
31
+ * Distinct from `registryToken`.
32
+ */
33
+ licenseToken?: string;
34
+ }
35
+ interface DetectedProject {
36
+ projectDir: string;
37
+ /** Path to `package.json` of the project we'll patch. */
38
+ packageJsonPath: string;
39
+ /** Resolved path to `vendure-config.ts`, or `null` if we can't find one. */
40
+ vendureConfigPath: string | null;
41
+ /** Whether `@vendure/core` was found in `package.json` dependencies. */
42
+ hasVendureCore: boolean;
43
+ /** Resolved path to `.env`, may not exist yet. */
44
+ envPath: string;
45
+ /** Resolved path to `.npmrc`, may not exist yet. */
46
+ npmrcPath: string;
47
+ /** Resolved path to `.gitignore`, may not exist yet. */
48
+ gitignorePath: string;
49
+ }
50
+ /**
51
+ * Detect the layout of an existing Vendure project.
52
+ *
53
+ * We look for `@vendure/core` in `package.json` and a `vendure-config.ts` file
54
+ * somewhere under `src/`. We don't enforce a specific layout — if we can't
55
+ * find the config we still write the installation guide, just to the root.
56
+ */
57
+ export declare function detectExistingProject(projectDir: string): Promise<DetectedProject>;
58
+ /**
59
+ * Apply the safe-to-modify changes:
60
+ * 1. Add `@vendure-platform/platform` and any selected requires-config
61
+ * plugin packages to `package.json` dependencies.
62
+ * 2. Append missing env vars to `.env` (idempotent — never overwrite values).
63
+ * 3. Ensure `.npmrc` declares the `@vendure-platform` scope.
64
+ * 4. Write a `vendure-platform-installation-guide.md` in the project root
65
+ * containing the exact import + spread to paste into `vendure-config.ts`.
66
+ *
67
+ * Returns a checklist of next steps to print at the end.
68
+ */
69
+ export declare function applyAddToExisting(detected: DetectedProject, options: AddToExistingOptions): Promise<string[]>;
70
+ export declare function runAddToExistingCli(flags: {
71
+ plugins?: string;
72
+ dryRun?: boolean;
73
+ registryToken?: string;
74
+ licenseToken?: string;
75
+ }): Promise<AddToExistingOptions>;
76
+ /**
77
+ * Top-level entry point — wired up from `bin/run.ts`.
78
+ */
79
+ export declare function addToExisting(options: AddToExistingOptions): Promise<void>;
80
+ export {};
@@ -0,0 +1,539 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.detectExistingProject = detectExistingProject;
37
+ exports.applyAddToExisting = applyAddToExisting;
38
+ exports.runAddToExistingCli = runAddToExistingCli;
39
+ exports.addToExisting = addToExisting;
40
+ const fs = __importStar(require("fs-extra"));
41
+ const path = __importStar(require("path"));
42
+ const p = __importStar(require("@clack/prompts"));
43
+ const plugin_registry_1 = require("./plugin-registry");
44
+ const registry_1 = require("./registry");
45
+ const template_renderer_1 = require("./template-renderer");
46
+ const INSTALLATION_GUIDE_FILENAME = 'vendure-platform-installation-guide.md';
47
+ /**
48
+ * Detect the layout of an existing Vendure project.
49
+ *
50
+ * We look for `@vendure/core` in `package.json` and a `vendure-config.ts` file
51
+ * somewhere under `src/`. We don't enforce a specific layout — if we can't
52
+ * find the config we still write the installation guide, just to the root.
53
+ */
54
+ async function detectExistingProject(projectDir) {
55
+ var _a, _b;
56
+ const packageJsonPath = path.join(projectDir, 'package.json');
57
+ if (!(await fs.pathExists(packageJsonPath))) {
58
+ throw new Error(`No package.json found at ${packageJsonPath}. Run this from your Vendure project root.`);
59
+ }
60
+ const pkg = await fs.readJson(packageJsonPath);
61
+ const deps = { ...((_a = pkg.dependencies) !== null && _a !== void 0 ? _a : {}), ...((_b = pkg.devDependencies) !== null && _b !== void 0 ? _b : {}) };
62
+ const hasVendureCore = '@vendure/core' in deps;
63
+ // Search a few likely locations for vendure-config.ts. Customers vary —
64
+ // some use src/, some use server/src/. We try the common spots; if we
65
+ // can't find one we surface that to the user but keep going.
66
+ const configCandidates = [
67
+ path.join(projectDir, 'src', 'vendure-config.ts'),
68
+ path.join(projectDir, 'server', 'src', 'vendure-config.ts'),
69
+ path.join(projectDir, 'src', 'vendure-config.js'),
70
+ ];
71
+ let vendureConfigPath = null;
72
+ for (const candidate of configCandidates) {
73
+ if (await fs.pathExists(candidate)) {
74
+ vendureConfigPath = candidate;
75
+ break;
76
+ }
77
+ }
78
+ return {
79
+ projectDir,
80
+ packageJsonPath,
81
+ vendureConfigPath,
82
+ hasVendureCore,
83
+ envPath: path.join(projectDir, '.env'),
84
+ npmrcPath: path.join(projectDir, '.npmrc'),
85
+ gitignorePath: path.join(projectDir, '.gitignore'),
86
+ };
87
+ }
88
+ /**
89
+ * Apply the safe-to-modify changes:
90
+ * 1. Add `@vendure-platform/platform` and any selected requires-config
91
+ * plugin packages to `package.json` dependencies.
92
+ * 2. Append missing env vars to `.env` (idempotent — never overwrite values).
93
+ * 3. Ensure `.npmrc` declares the `@vendure-platform` scope.
94
+ * 4. Write a `vendure-platform-installation-guide.md` in the project root
95
+ * containing the exact import + spread to paste into `vendure-config.ts`.
96
+ *
97
+ * Returns a checklist of next steps to print at the end.
98
+ */
99
+ async function applyAddToExisting(detected, options) {
100
+ const { selectedPlugins, dryRun, registryToken, licenseToken } = options;
101
+ const resolvedKeys = (0, plugin_registry_1.resolveActivePluginKeys)(selectedPlugins);
102
+ const resolvedPlugins = resolvedKeys.map((k) => (0, plugin_registry_1.getPlugin)(k));
103
+ const versionSpec = (0, template_renderer_1.resolvePluginVersionSpec)();
104
+ const checklist = [];
105
+ // 1. package.json — add `@vendure-platform/platform` AND each resolved
106
+ // plugin's package as a direct dep. Listing only `platform` would
107
+ // work on npm/bun (which hoist transitives) but break under
108
+ // pnpm strict mode, and would also leave the user's snippet
109
+ // imports like `import { SqlAnalyticsStrategy } from
110
+ // '@vendure-platform/advanced-search'` looking at an undeclared
111
+ // dependency. This matches what the fresh-scaffold flow writes.
112
+ //
113
+ // We intentionally do NOT add `@vendure-platform/data-populator`,
114
+ // `integration`, or `scenario-default` here — those are
115
+ // scaffold-specific (used by the generated populate.ts boot code)
116
+ // and irrelevant to an existing project the user is patching.
117
+ const pluginPackageNames = resolvedPlugins.map((p) => p.packageName);
118
+ const addedDeps = await patchPackageJson(detected.packageJsonPath, versionSpec, pluginPackageNames, dryRun);
119
+ if (addedDeps.length > 0) {
120
+ const summary = addedDeps.length === 1
121
+ ? addedDeps[0]
122
+ : `${addedDeps.length} packages (${addedDeps[0]}, …)`;
123
+ checklist.push(`Added ${summary} at ${versionSpec} to package.json — run \`npm install\` (or your package manager equivalent).`);
124
+ }
125
+ // 2. .env — append missing env vars from selected plugins. If we have a
126
+ // license token, seed it as the default value for VENDURE_PLATFORM_LICENSE_TOKEN
127
+ // so the user doesn't have to paste it twice.
128
+ const envAdditions = collectEnvAdditions(resolvedPlugins, licenseToken);
129
+ const appendedCount = await appendMissingEnvVars(detected.envPath, envAdditions, dryRun);
130
+ if (appendedCount > 0) {
131
+ checklist.push(`Appended ${appendedCount} env var(s) to .env — fill in real values before booting.`);
132
+ }
133
+ // 2a. Recovery path: if the user previously ran `create add` without a
134
+ // token (leaving `VENDURE_PLATFORM_LICENSE_TOKEN=` empty in .env)
135
+ // and is now re-running with a token, fill that empty value in.
136
+ // Never overwrite a non-empty value — that's still the user's
137
+ // intentional setting.
138
+ if (licenseToken) {
139
+ const filled = await fillEmptyLicenseTokenInEnv(detected.envPath, licenseToken, dryRun);
140
+ if (filled) {
141
+ checklist.push(`Filled in the empty VENDURE_PLATFORM_LICENSE_TOKEN value in .env.`);
142
+ }
143
+ }
144
+ // 3. .npmrc — declare the @vendure-platform scope, and (if a registry
145
+ // token was provided) the `_authToken` line so `${pm} install` works
146
+ // without any `~/.npmrc` setup. The token is the literal value, not
147
+ // env-var indirection — npm cannot read `.env` during install, so
148
+ // indirection would only work when the user manually exports the env
149
+ // var. The project `.gitignore` is updated below to keep this file
150
+ // untracked.
151
+ const npmrcResult = await ensureNpmrcScopeAndAuth(detected.npmrcPath, registryToken, dryRun);
152
+ if (npmrcResult.scopeAdded || npmrcResult.authAdded) {
153
+ const bits = [];
154
+ if (npmrcResult.scopeAdded)
155
+ bits.push('scope');
156
+ if (npmrcResult.authAdded)
157
+ bits.push('auth token');
158
+ checklist.push(`Updated .npmrc (${bits.join(' + ')}).`);
159
+ }
160
+ // 3a. .gitignore — make sure .npmrc never gets committed. We do this
161
+ // unconditionally (even without a token) so future runs that add a
162
+ // token to the existing scope-only .npmrc don't accidentally commit
163
+ // it. Without a token in play the entry is harmless either way.
164
+ const gitignoreAdded = await ensureGitignoreEntry(detected.gitignorePath, '.npmrc', dryRun);
165
+ if (gitignoreAdded) {
166
+ checklist.push(`Added .npmrc to .gitignore so any future registry token never gets committed.`);
167
+ }
168
+ // 4. vendure-platform-installation-guide.md — generate paste-in
169
+ // instructions. Always written to the project root (not next to the
170
+ // config) so it's easy to find regardless of project layout.
171
+ const guide = renderInstallationGuide(resolvedPlugins, resolvedKeys);
172
+ const guidePath = path.join(detected.projectDir, INSTALLATION_GUIDE_FILENAME);
173
+ if (!dryRun) {
174
+ await fs.writeFile(guidePath, guide);
175
+ }
176
+ checklist.push(`Wrote ${path.relative(detected.projectDir, guidePath)} — follow it to wire up your vendure-config.ts.`);
177
+ // 5. Downstream reminders (not automated)
178
+ // Bootstrap-import swap is the gate: without it, every platform plugin fails
179
+ // at startup with LICENSE_GUARD_NONCE_MISMATCH. Surface it as its own line so
180
+ // it can't be missed by anyone who only skims the next-steps printout.
181
+ checklist.push(`Swap \`import { bootstrap } from '@vendure/core'\` for \`'@vendure-platform/platform'\` in your server entry file (and \`bootstrapWorker\` if you have a worker). See the installation guide's "Bootstrap import" section.`);
182
+ checklist.push('Generate and run a migration for the new platform entities.');
183
+ checklist.push('Rebuild the dashboard if you serve a pre-built artefact.');
184
+ checklist.push('Grant new permissions to existing admin roles via Settings → Roles.');
185
+ return checklist;
186
+ }
187
+ /**
188
+ * Add `@vendure-platform/platform` plus each resolved plugin package to
189
+ * `package.json` dependencies (idempotent — already-present packages are
190
+ * left at their existing version, never downgraded). Returns the list of
191
+ * packages we actually added so the caller can report a useful checklist
192
+ * line and skip the message entirely when nothing changed.
193
+ */
194
+ async function patchPackageJson(packageJsonPath, versionSpec, pluginPackageNames, dryRun) {
195
+ var _a;
196
+ const pkg = await fs.readJson(packageJsonPath);
197
+ (_a = pkg.dependencies) !== null && _a !== void 0 ? _a : (pkg.dependencies = {});
198
+ // Sort so the checklist message is deterministic and the platform
199
+ // meta-package always lands first (it's the "headline" dep).
200
+ const allPackages = ['@vendure-platform/platform', ...pluginPackageNames]
201
+ .filter((name, i, arr) => arr.indexOf(name) === i);
202
+ const added = [];
203
+ for (const name of allPackages) {
204
+ if (!pkg.dependencies[name]) {
205
+ pkg.dependencies[name] = versionSpec;
206
+ added.push(name);
207
+ }
208
+ }
209
+ if (added.length > 0 && !dryRun) {
210
+ await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
211
+ }
212
+ return added;
213
+ }
214
+ function collectEnvAdditions(plugins, licenseToken) {
215
+ // Always include the license guard vars even if the plugin isn't selected
216
+ // — they're required for boot.
217
+ const additions = [
218
+ { name: 'VENDURE_PLATFORM_LICENSE_TOKEN', defaultValue: licenseToken !== null && licenseToken !== void 0 ? licenseToken : '' },
219
+ { name: 'VENDURE_PLATFORM_LICENSE_GUARD_LOCAL_DEV', defaultValue: '1' },
220
+ ];
221
+ for (const pl of plugins) {
222
+ for (const v of pl.envVars) {
223
+ if (!additions.some((a) => a.name === v.name)) {
224
+ additions.push(v);
225
+ }
226
+ }
227
+ }
228
+ return additions;
229
+ }
230
+ /**
231
+ * Find a `VENDURE_PLATFORM_LICENSE_TOKEN=` line with an empty value and
232
+ * fill it in with `token`. Used to recover from a previous `create add` run
233
+ * that didn't have a token to hand.
234
+ *
235
+ * - `VENDURE_PLATFORM_LICENSE_TOKEN=` → updated to `=<token>`
236
+ * - `VENDURE_PLATFORM_LICENSE_TOKEN=preexisting`→ left alone
237
+ * - `# VENDURE_PLATFORM_LICENSE_TOKEN=` → left alone (commented out)
238
+ *
239
+ * Returns `true` if the file was modified.
240
+ */
241
+ async function fillEmptyLicenseTokenInEnv(envPath, token, dryRun) {
242
+ if (!(await fs.pathExists(envPath)))
243
+ return false;
244
+ const existing = await fs.readFile(envPath, 'utf8');
245
+ const lines = existing.split('\n');
246
+ let modified = false;
247
+ const TOKEN_KEY = 'VENDURE_PLATFORM_LICENSE_TOKEN';
248
+ for (let i = 0; i < lines.length; i++) {
249
+ const trimmed = lines[i].trim();
250
+ if (trimmed.startsWith('#'))
251
+ continue;
252
+ if (trimmed === `${TOKEN_KEY}=`) {
253
+ lines[i] = `${TOKEN_KEY}=${token}`;
254
+ modified = true;
255
+ // Only fill the first occurrence — dotenv reads top-down and any
256
+ // subsequent line would be a duplicate the user authored deliberately.
257
+ break;
258
+ }
259
+ }
260
+ if (modified && !dryRun) {
261
+ await fs.writeFile(envPath, lines.join('\n'));
262
+ }
263
+ return modified;
264
+ }
265
+ /**
266
+ * Append any env vars from `additions` that aren't already declared in
267
+ * `.env`. Idempotent — never overwrites or duplicates an existing key.
268
+ * Returns the count of keys actually appended, so callers can decide
269
+ * whether to surface a "we did something" message.
270
+ */
271
+ async function appendMissingEnvVars(envPath, additions, dryRun) {
272
+ let existing = '';
273
+ if (await fs.pathExists(envPath)) {
274
+ existing = await fs.readFile(envPath, 'utf8');
275
+ }
276
+ const existingKeys = new Set(existing
277
+ .split('\n')
278
+ .map((line) => { var _a; return (_a = line.split('=')[0]) === null || _a === void 0 ? void 0 : _a.trim(); })
279
+ .filter((k) => Boolean(k) && !k.startsWith('#')));
280
+ const toAdd = additions.filter((a) => !existingKeys.has(a.name));
281
+ if (toAdd.length === 0)
282
+ return 0;
283
+ const header = '\n# Added by @vendure-platform/create add\n';
284
+ const block = toAdd.map((a) => `${a.name}=${a.defaultValue}`).join('\n') + '\n';
285
+ if (!dryRun) {
286
+ const trailing = existing.endsWith('\n') || existing === '' ? '' : '\n';
287
+ await fs.writeFile(envPath, existing + trailing + header + block);
288
+ }
289
+ return toAdd.length;
290
+ }
291
+ /**
292
+ * Ensure the project-level `.npmrc` declares both the `@vendure-platform`
293
+ * scope and (when a registry token is supplied) a registry-scoped
294
+ * `_authToken` line. The registry token is the npm auth token from the
295
+ * Enterprise Portal — distinct from the server-side license token, which
296
+ * lives in `.env` and is handled by the env-writing path.
297
+ *
298
+ * The auth line uses the **literal token value**, not env-var indirection.
299
+ * npm reads `.npmrc` at install time and only resolves `${VAR}` against the
300
+ * process environment — it doesn't load `.env` files. Env-var indirection
301
+ * would therefore only work when the user has manually exported the var,
302
+ * which defeats the point of bundling the token at scaffold time. Project
303
+ * `.gitignore` is updated separately to keep this file untracked.
304
+ *
305
+ * Idempotent: lines that already exist are left alone.
306
+ */
307
+ async function ensureNpmrcScopeAndAuth(npmrcPath, registryToken, dryRun) {
308
+ const registryUrl = (0, registry_1.getRegistryUrl)();
309
+ const registryHost = (0, registry_1.getRegistryHostname)();
310
+ const scopeLine = `@vendure-platform:registry=${registryUrl}`;
311
+ // `npm login` writes the auth-token line with a leading `//` and a
312
+ // trailing slash before `:_authToken`. We match that convention so a
313
+ // subsequent `npm login` / `npm logout` rewrite cleanly.
314
+ const authLineKey = `//${registryHost}/:_authToken=`;
315
+ let existing = '';
316
+ if (await fs.pathExists(npmrcPath)) {
317
+ existing = await fs.readFile(npmrcPath, 'utf8');
318
+ }
319
+ const hasScope = existing.includes('@vendure-platform:registry=');
320
+ const hasAuth = existing.includes(authLineKey);
321
+ const linesToAdd = [];
322
+ if (!hasScope)
323
+ linesToAdd.push(scopeLine);
324
+ if (registryToken && !hasAuth)
325
+ linesToAdd.push(`${authLineKey}${registryToken}`);
326
+ if (linesToAdd.length === 0) {
327
+ return { scopeAdded: false, authAdded: false };
328
+ }
329
+ const trailing = existing === '' || existing.endsWith('\n') ? '' : '\n';
330
+ if (!dryRun) {
331
+ await fs.writeFile(npmrcPath, existing + trailing + linesToAdd.join('\n') + '\n');
332
+ }
333
+ return {
334
+ scopeAdded: !hasScope,
335
+ authAdded: Boolean(registryToken) && !hasAuth,
336
+ };
337
+ }
338
+ /**
339
+ * Ensure `entry` appears as its own line in `.gitignore`. Skips if the entry
340
+ * (or a less-specific glob that would match it) is already present. Creates
341
+ * the file when missing.
342
+ */
343
+ async function ensureGitignoreEntry(gitignorePath, entry, dryRun) {
344
+ let existing = '';
345
+ if (await fs.pathExists(gitignorePath)) {
346
+ existing = await fs.readFile(gitignorePath, 'utf8');
347
+ }
348
+ const lines = existing.split('\n').map((l) => l.trim());
349
+ if (lines.includes(entry) || lines.includes(`/${entry}`)) {
350
+ return false;
351
+ }
352
+ const trailing = existing === '' || existing.endsWith('\n') ? '' : '\n';
353
+ if (!dryRun) {
354
+ await fs.writeFile(gitignorePath, existing + trailing + entry + '\n');
355
+ }
356
+ return true;
357
+ }
358
+ function renderInstallationGuide(plugins, resolvedKeys) {
359
+ const imports = [`import { VendurePlatform } from '@vendure-platform/platform';`];
360
+ const optionsBlocks = [];
361
+ for (const pl of plugins) {
362
+ if (pl.platformImports)
363
+ imports.push(...pl.platformImports);
364
+ if (pl.platformOptionsBlock) {
365
+ optionsBlocks.push(` ${pl.platformOptionsBlock},`);
366
+ }
367
+ }
368
+ // Plugins that are optional but the user didn't pick → emit `key: false`
369
+ const disabledKeys = plugin_registry_1.pluginRegistry
370
+ .filter((pl) => pl.tier === 'optional' && !resolvedKeys.includes(pl.key))
371
+ .map((pl) => pl.key);
372
+ for (const key of disabledKeys) {
373
+ const platformKey = (0, plugin_registry_1.getPlugin)(key).platformKey;
374
+ optionsBlocks.push(` ${platformKey}: false,`);
375
+ }
376
+ const importsText = imports.join('\n');
377
+ const optionsText = optionsBlocks.length === 0
378
+ ? ' // All optional plugins enabled with default config'
379
+ : optionsBlocks.join('\n');
380
+ return `# Vendure Platform installation guide
381
+
382
+ Paste these changes into your existing project. Step 1 is the most important —
383
+ without it, every Vendure Platform plugin refuses to start with
384
+ \`LICENSE_GUARD_NONCE_MISMATCH\`.
385
+
386
+ ## 1. Bootstrap import (server entry point)
387
+
388
+ In your server entry file (typically \`src/index.ts\` or wherever you call
389
+ \`bootstrap()\`), swap the bootstrap import from \`@vendure/core\` to
390
+ \`@vendure-platform/platform\`:
391
+
392
+ \`\`\`ts
393
+ // before
394
+ import { bootstrap } from '@vendure/core';
395
+
396
+ // after
397
+ import { bootstrap } from '@vendure-platform/platform';
398
+ \`\`\`
399
+
400
+ If you also run a separate worker process, swap \`bootstrapWorker\` too:
401
+
402
+ \`\`\`ts
403
+ import { bootstrapWorker } from '@vendure-platform/platform';
404
+ \`\`\`
405
+
406
+ The \`@vendure-platform/platform\` bootstrap is a drop-in wrapper around
407
+ \`@vendure/core\`'s \`bootstrap\` that primes the runtime license guard before
408
+ Nest builds the DI container. Without this swap, plugins like \`OrgHierarchyPlugin\`
409
+ fail at startup with \`LICENSE_GUARD_NONCE_MISMATCH\`.
410
+
411
+ ## 2. Imports in vendure-config.ts
412
+
413
+ Add to the top of the file (after your existing imports):
414
+
415
+ \`\`\`ts
416
+ ${importsText}
417
+ \`\`\`
418
+
419
+ ## 3. Plugins array
420
+
421
+ Spread \`VendurePlatform.init({...})\` into the \`plugins\` array — typically
422
+ between \`DefaultJobQueuePlugin\` and \`DashboardPlugin\`:
423
+
424
+ \`\`\`ts
425
+ ...VendurePlatform.init({
426
+ ${optionsText}
427
+ }),
428
+ \`\`\`
429
+
430
+ ## 4. Order process
431
+
432
+ If \`approvalWorkflows\` is enabled (default), VendurePlatform will append
433
+ \`approvalOrderProcess\` to your order process chain and override
434
+ \`arrangingPaymentRequiresContents\` and \`checkAllItemsBeforeCancel\`. If you
435
+ have custom order process handlers they are preserved; the flags above are
436
+ not.
437
+
438
+ See https://docs.vendure.io/current/platform/getting-started/introduction for the full reference.
439
+ `;
440
+ }
441
+ async function runAddToExistingCli(flags) {
442
+ var _a;
443
+ const projectDir = process.cwd();
444
+ const detected = await detectExistingProject(projectDir);
445
+ if (!detected.hasVendureCore) {
446
+ p.log.warn('No @vendure/core dependency found in package.json. Continuing anyway — make sure this is the right directory.');
447
+ }
448
+ if (!detected.vendureConfigPath) {
449
+ p.log.warn('No vendure-config.ts found under src/ or server/src/. The installation guide will still be written to the project root.');
450
+ }
451
+ else {
452
+ p.log.info(`Detected vendure-config.ts at ${path.relative(projectDir, detected.vendureConfigPath)}`);
453
+ }
454
+ const hasIntro = flags.plugins === undefined ||
455
+ flags.registryToken === undefined ||
456
+ flags.licenseToken === undefined;
457
+ if (hasIntro) {
458
+ p.intro('Add Vendure Platform to existing project');
459
+ }
460
+ let selectedPlugins;
461
+ if (flags.plugins !== undefined) {
462
+ selectedPlugins = flags.plugins
463
+ .split(',')
464
+ .map((k) => k.trim())
465
+ .filter(Boolean);
466
+ }
467
+ else {
468
+ const pluginOptions = plugin_registry_1.pluginRegistry
469
+ .filter((pl) => pl.tier === 'optional' || pl.tier === 'requires-config')
470
+ .sort((a, b) => {
471
+ if (a.category !== b.category)
472
+ return a.category.localeCompare(b.category);
473
+ return a.displayName.localeCompare(b.displayName);
474
+ })
475
+ .map((pl) => ({
476
+ label: `${pl.displayName} (${pl.category})`,
477
+ value: pl.key,
478
+ hint: pl.tier === 'requires-config' ? 'needs configuration' : undefined,
479
+ }));
480
+ const initialValues = plugin_registry_1.pluginRegistry.filter((pl) => pl.tier === 'optional').map((pl) => pl.key);
481
+ const answer = await p.multiselect({
482
+ message: 'Optional plugins to include (core plugins are always installed):',
483
+ options: pluginOptions,
484
+ initialValues,
485
+ required: false,
486
+ });
487
+ if (typeof answer === 'symbol') {
488
+ p.cancel('Cancelled.');
489
+ process.exit(0);
490
+ }
491
+ selectedPlugins = answer;
492
+ }
493
+ // Two separate prompts: registry token (for .npmrc — needed by `${pm}
494
+ // install`) and license token (for .env — read at server boot). The
495
+ // Enterprise Portal issues these as two distinct values with different
496
+ // rotation policies, so we never collapse them.
497
+ const registryToken = await promptForOptionalToken(flags.registryToken, 'Vendure private-registry npm auth token (from https://enterprise.vendure.io). Used by your package manager (npm/bun/pnpm) at install time. Leave blank to skip:');
498
+ const licenseToken = await promptForOptionalToken(flags.licenseToken, 'Vendure Platform license token (server-side, written to .env). Leave blank to skip:');
499
+ return {
500
+ projectDir,
501
+ selectedPlugins,
502
+ dryRun: (_a = flags.dryRun) !== null && _a !== void 0 ? _a : false,
503
+ registryToken,
504
+ licenseToken,
505
+ };
506
+ }
507
+ /**
508
+ * Pre-resolve from a flag if given, otherwise interactively prompt with a
509
+ * masked password input. Empty / whitespace-only answers normalise to
510
+ * `undefined` so downstream "if a token was supplied" checks read cleanly.
511
+ */
512
+ async function promptForOptionalToken(flagValue, message) {
513
+ const fromFlag = flagValue === null || flagValue === void 0 ? void 0 : flagValue.trim();
514
+ if (fromFlag !== undefined) {
515
+ return fromFlag === '' ? undefined : fromFlag;
516
+ }
517
+ const answer = await p.password({ message });
518
+ if (typeof answer === 'symbol') {
519
+ p.cancel('Cancelled.');
520
+ process.exit(0);
521
+ }
522
+ const trimmed = answer.trim();
523
+ return trimmed === '' ? undefined : trimmed;
524
+ }
525
+ /**
526
+ * Top-level entry point — wired up from `bin/run.ts`.
527
+ */
528
+ async function addToExisting(options) {
529
+ const detected = await detectExistingProject(options.projectDir);
530
+ const alwaysOn = plugin_registry_1.ALWAYS_ACTIVE_KEYS.map((k) => (0, plugin_registry_1.getPlugin)(k).displayName);
531
+ if (alwaysOn.length > 0) {
532
+ p.log.info(`Always-on core plugins: ${alwaysOn.join(', ')}`);
533
+ }
534
+ const checklist = await applyAddToExisting(detected, options);
535
+ const numbered = checklist.map((line, i) => `${i + 1}. ${line}`).join('\n');
536
+ p.note(numbered, options.dryRun ? 'Dry run — would do:' : 'Next steps');
537
+ p.outro(options.dryRun ? 'Dry run complete — no files written.' : 'Done.');
538
+ }
539
+ //# sourceMappingURL=add-to-existing.js.map