create-nudge-ui 0.1.3 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,7 +11,7 @@ npm create nudge-ui@latest
11
11
 
12
12
  The initializer supports Next.js, Astro, Vite with React, and static HTML. Host
13
13
  frameworks take precedence over their underlying tools, so an Astro project
14
- with React islands receives `@nudge-ui/astro`, not `@nudge-ui/vite-react`.
14
+ with React islands receives `nudge-ui/astro`, not `nudge-ui/vite`.
15
15
 
16
16
  Use an explicit framework when automatic detection is ambiguous:
17
17
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import { stdin, stdout } from "node:process";
4
4
  import { basename, relative } from "node:path";
5
5
  import { parseArguments, helpText } from "./arguments.js";
6
6
  import { planConfiguration } from "./configuration.js";
7
- import { adapterPackage, detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
7
+ import { detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
8
8
  import { formatCommand, installCommand, installPackage } from "./installer.js";
9
9
  import { frameworks } from "./types.js";
10
10
  /** Runs the Nudge UI project initializer. */
@@ -18,8 +18,9 @@ export async function runCli(args = process.argv.slice(2), projectRoot = process
18
18
  const detected = detectFrameworks(projectRoot, manifest);
19
19
  const framework = options.framework ?? await selectFramework(detected);
20
20
  const packageManager = options.packageManager ?? detectPackageManager(projectRoot, manifest);
21
- const packageName = adapterPackage(framework);
22
- const command = installCommand(packageManager, packageName);
21
+ // One package serves every host; the framework only decides which
22
+ // subpath the generated configuration imports.
23
+ const command = installCommand(packageManager, "nudge-ui");
23
24
  const change = planConfiguration(projectRoot, framework);
24
25
  const staticRoot = framework === "standalone" ? detectStaticRoot(projectRoot) : undefined;
25
26
  const serveDirectory = staticRoot ? relative(projectRoot, staticRoot) || "." : ".";
@@ -49,7 +50,7 @@ export async function runCli(args = process.argv.slice(2), projectRoot = process
49
50
  if (framework === "standalone") {
50
51
  stdout.write(`Run \`nudge-ui serve ${serveDirectory}\` to start the instrumented development server.\n`);
51
52
  }
52
- stdout.write(`Nudge UI is configured with ${packageName}.\n`);
53
+ stdout.write(`Nudge UI is configured for ${frameworkDisplayName(framework)}.\n`);
53
54
  }
54
55
  async function selectFramework(detected) {
55
56
  if (detected.length === 1)
@@ -3,7 +3,20 @@ import { join } from "node:path";
3
3
  import ts from "typescript";
4
4
  const nextConfigNames = ["next.config.ts", "next.config.mts", "next.config.mjs", "next.config.js", "next.config.cjs"];
5
5
  const astroConfigNames = ["astro.config.ts", "astro.config.mts", "astro.config.mjs", "astro.config.js"];
6
- const viteConfigNames = ["vite.config.ts", "vite.config.mts", "vite.config.mjs", "vite.config.js"];
6
+ /**
7
+ * Vite's own `DEFAULT_CONFIG_FILES` order. The list is order-sensitive: the
8
+ * first existing file wins for Vite, so it must win here too, or the installer
9
+ * edits a configuration Vite never loads (and creating a new `vite.config.mjs`
10
+ * beside a `vite.config.cjs` would silently shadow it).
11
+ */
12
+ const viteConfigNames = [
13
+ "vite.config.js",
14
+ "vite.config.mjs",
15
+ "vite.config.ts",
16
+ "vite.config.cjs",
17
+ "vite.config.mts",
18
+ "vite.config.cts",
19
+ ];
7
20
  /** Plans an idempotent host-configuration edit without writing to the project. */
8
21
  export function planConfiguration(projectRoot, framework) {
9
22
  if (framework === "standalone")
@@ -22,45 +35,59 @@ export function planConfiguration(projectRoot, framework) {
22
35
  export function configureSource(source, framework, fileName) {
23
36
  const sourceFile = parseSource(source, fileName);
24
37
  if (framework === "astro") {
25
- return configureArrayHost(source, sourceFile, "@nudge-ui/astro", "nudgeUiAstro", "integrations", "nudgeUiAstro()");
38
+ return configureAstroSource(source, sourceFile);
26
39
  }
27
40
  if (framework === "vite-react") {
28
- return configureArrayHost(source, sourceFile, "@nudge-ui/vite-react", "nudgeUi", "plugins", "...nudgeUi()");
41
+ return configureViteSource(source, sourceFile);
29
42
  }
30
43
  return configureNextSource(source, sourceFile);
31
44
  }
32
- function configureArrayHost(source, sourceFile, packageName, importName, propertyName, expression) {
33
- const bindings = importedBindings(sourceFile, packageName, importName);
34
- const callableBindings = new Set([...bindings, importName]);
35
- const configObject = findDefineConfigObject(sourceFile, propertyName);
36
- const matchingProperties = configObject.properties.filter((property) => propertyText(property.name) === propertyName);
37
- if (matchingProperties.length > 1) {
38
- throw new Error(`Could not update ${propertyName}: the configuration contains duplicate ${propertyName} properties.`);
45
+ function configureViteSource(source, sourceFile) {
46
+ const packageName = "nudge-ui/vite";
47
+ const importName = "withNudgeUi";
48
+ const target = findConfigExport(sourceFile, "Vite");
49
+ const bindings = target.commonJs
50
+ ? requiredBindings(sourceFile, packageName, importName)
51
+ : importedBindings(sourceFile, packageName, importName);
52
+ const wrapperBinding = bindings[0] ?? unusedBindingName(sourceFile, importName);
53
+ const callableBindings = new Set([...bindings, wrapperBinding]);
54
+ const edits = [];
55
+ if (!containsCall(target.expression, callableBindings)) {
56
+ const start = target.expression.getStart(sourceFile);
57
+ const end = target.expression.getEnd();
58
+ edits.push({ start, end, text: `${wrapperBinding}(${source.slice(start, end)})` });
39
59
  }
40
- const property = matchingProperties[0];
41
- let configurationEdit;
42
- if (property) {
43
- if (!ts.isPropertyAssignment(property) || !ts.isArrayLiteralExpression(property.initializer)) {
44
- throw new Error(`Could not update ${propertyName}: expected ${propertyName} to be an array.`);
45
- }
46
- if (!containsCall(property.initializer, callableBindings)) {
47
- configurationEdit = insertArrayElement(source, sourceFile, property, expression);
48
- }
60
+ if (bindings.length === 0) {
61
+ const statement = target.commonJs
62
+ ? `const { ${importName}${wrapperBinding === importName ? "" : `: ${wrapperBinding}`} } = require("${packageName}");`
63
+ : `import { ${importName}${wrapperBinding === importName ? "" : ` as ${wrapperBinding}`} } from "${packageName}";`;
64
+ edits.push(importEdit(sourceFile, statement, target.commonJs));
49
65
  }
50
- else {
51
- configurationEdit = insertObjectProperty(source, sourceFile, configObject, propertyName, expression);
66
+ return applyEdits(source, edits);
67
+ }
68
+ function configureAstroSource(source, sourceFile) {
69
+ const target = findDefaultExport(sourceFile, "Astro");
70
+ const bindings = importedBindings(sourceFile, "nudge-ui/astro", "withNudgeUi");
71
+ const wrapperBinding = bindings[0] ?? unusedBindingName(sourceFile, "withNudgeUi");
72
+ const edits = [];
73
+ if (!containsCall(target, new Set(bindings))) {
74
+ const start = target.getStart(sourceFile);
75
+ const end = target.getEnd();
76
+ edits.push({ start, end, text: `${wrapperBinding}(${source.slice(start, end)})` });
52
77
  }
53
- const edits = configurationEdit ? [configurationEdit] : [];
54
78
  if (bindings.length === 0) {
55
- edits.push(importEdit(sourceFile, `import { ${importName} } from "${packageName}";`, false));
79
+ const importedName = wrapperBinding === "withNudgeUi"
80
+ ? "withNudgeUi"
81
+ : `withNudgeUi as ${wrapperBinding}`;
82
+ edits.push(importEdit(sourceFile, `import { ${importedName} } from "nudge-ui/astro";`, false));
56
83
  }
57
84
  return applyEdits(source, edits);
58
85
  }
59
86
  function configureNextSource(source, sourceFile) {
60
- const target = findNextExport(sourceFile);
87
+ const target = findConfigExport(sourceFile, "Next.js");
61
88
  const bindings = target.commonJs
62
- ? requiredBindings(sourceFile, "@nudge-ui/nextjs", "withNudgeUi")
63
- : importedBindings(sourceFile, "@nudge-ui/nextjs", "withNudgeUi");
89
+ ? requiredBindings(sourceFile, "nudge-ui/next", "withNudgeUi")
90
+ : importedBindings(sourceFile, "nudge-ui/next", "withNudgeUi");
64
91
  const callableBindings = new Set([...bindings, "withNudgeUi"]);
65
92
  const edits = [];
66
93
  if (!containsCall(target.expression, callableBindings)) {
@@ -70,34 +97,19 @@ function configureNextSource(source, sourceFile) {
70
97
  }
71
98
  if (bindings.length === 0) {
72
99
  const statement = target.commonJs
73
- ? 'const { withNudgeUi } = require("@nudge-ui/nextjs");'
74
- : 'import { withNudgeUi } from "@nudge-ui/nextjs";';
100
+ ? 'const { withNudgeUi } = require("nudge-ui/next");'
101
+ : 'import { withNudgeUi } from "nudge-ui/next";';
75
102
  edits.push(importEdit(sourceFile, statement, target.commonJs));
76
103
  }
77
104
  return applyEdits(source, edits);
78
105
  }
79
106
  function parseSource(source, fileName) {
80
- const scriptKind = fileName.endsWith(".ts") || fileName.endsWith(".mts")
107
+ const scriptKind = /\.(?:ts|mts|cts)$/.test(fileName)
81
108
  ? ts.ScriptKind.TS
82
109
  : ts.ScriptKind.JS;
83
110
  return ts.createSourceFile(fileName, source, ts.ScriptTarget.Latest, true, scriptKind);
84
111
  }
85
- function findDefineConfigObject(sourceFile, propertyName) {
86
- const candidates = [];
87
- visit(sourceFile, (node) => {
88
- if (ts.isCallExpression(node)
89
- && ts.isIdentifier(node.expression)
90
- && node.expression.text === "defineConfig"
91
- && node.arguments[0]
92
- && ts.isObjectLiteralExpression(node.arguments[0]))
93
- candidates.push(node.arguments[0]);
94
- });
95
- if (candidates.length !== 1) {
96
- throw new Error(`Could not update ${propertyName}: expected exactly one defineConfig({ ... }) call.`);
97
- }
98
- return candidates[0];
99
- }
100
- function findNextExport(sourceFile) {
112
+ function findConfigExport(sourceFile, frameworkName) {
101
113
  const exports = [];
102
114
  for (const statement of sourceFile.statements) {
103
115
  if (ts.isExportAssignment(statement) && !statement.isExportEquals) {
@@ -112,7 +124,16 @@ function findNextExport(sourceFile) {
112
124
  exports.push({ expression: assignment.right, commonJs: true });
113
125
  }
114
126
  if (exports.length !== 1) {
115
- throw new Error("Could not update the Next.js configuration: expected exactly one default or module.exports assignment.");
127
+ throw new Error(`Could not update the ${frameworkName} configuration: expected exactly one default or module.exports assignment.`);
128
+ }
129
+ return exports[0];
130
+ }
131
+ function findDefaultExport(sourceFile, frameworkName) {
132
+ const exports = sourceFile.statements
133
+ .filter((statement) => ts.isExportAssignment(statement) && !statement.isExportEquals)
134
+ .map((statement) => statement.expression);
135
+ if (exports.length !== 1) {
136
+ throw new Error(`Could not update the ${frameworkName} configuration: expected exactly one default export.`);
116
137
  }
117
138
  return exports[0];
118
139
  }
@@ -180,46 +201,23 @@ function containsCall(root, bindingNames) {
180
201
  });
181
202
  return found;
182
203
  }
204
+ function unusedBindingName(sourceFile, preferred) {
205
+ const identifiers = new Set();
206
+ visit(sourceFile, (node) => {
207
+ if (ts.isIdentifier(node))
208
+ identifiers.add(node.text);
209
+ });
210
+ if (!identifiers.has(preferred))
211
+ return preferred;
212
+ let suffix = 2;
213
+ while (identifiers.has(`${preferred}${suffix}`))
214
+ suffix += 1;
215
+ return `${preferred}${suffix}`;
216
+ }
183
217
  function visit(root, visitor) {
184
218
  visitor(root);
185
219
  ts.forEachChild(root, (child) => visit(child, visitor));
186
220
  }
187
- function propertyText(name) {
188
- if (!name)
189
- return undefined;
190
- if (ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name))
191
- return name.text;
192
- if (ts.isComputedPropertyName(name) && ts.isStringLiteral(name.expression))
193
- return name.expression.text;
194
- return undefined;
195
- }
196
- function insertArrayElement(source, sourceFile, property, expression) {
197
- const array = property.initializer;
198
- if (!ts.isArrayLiteralExpression(array))
199
- throw new Error("Expected an array property.");
200
- const start = array.getStart(sourceFile) + 1;
201
- const end = array.getEnd() - 1;
202
- const indentation = lineIndentation(source, property.getStart(sourceFile));
203
- const elementIndentation = `${indentation} `;
204
- const interior = source.slice(start, end);
205
- if (interior.trim() === "") {
206
- return { start, end, text: `\n${elementIndentation}${expression},\n${indentation}` };
207
- }
208
- const text = interior.startsWith("\n")
209
- ? `\n${elementIndentation}${expression},`
210
- : `\n${elementIndentation}${expression},\n${elementIndentation}`;
211
- return { start, end: start, text };
212
- }
213
- function insertObjectProperty(source, sourceFile, object, propertyName, expression) {
214
- const start = object.getStart(sourceFile) + 1;
215
- const indentation = lineIndentation(source, object.getStart(sourceFile));
216
- const propertyIndentation = `${indentation} `;
217
- const empty = object.properties.length === 0;
218
- const text = empty
219
- ? `\n${propertyIndentation}${propertyName}: [${expression}],\n${indentation}`
220
- : `\n${propertyIndentation}${propertyName}: [${expression}],`;
221
- return { start, end: start, text };
222
- }
223
221
  function importEdit(sourceFile, statement, commonJs) {
224
222
  const imports = sourceFile.statements.filter(ts.isImportDeclaration);
225
223
  const lastImport = imports.at(-1);
@@ -235,11 +233,6 @@ function importEdit(sourceFile, statement, commonJs) {
235
233
  const start = firstStatement?.getStart(sourceFile) ?? sourceFile.getEnd();
236
234
  return { start, end: start, text: `${statement}\n` };
237
235
  }
238
- function lineIndentation(source, position) {
239
- const lineStart = source.lastIndexOf("\n", position - 1) + 1;
240
- const match = /^[ \t]*/.exec(source.slice(lineStart, position));
241
- return match?.[0] ?? "";
242
- }
243
236
  function applyEdits(source, edits) {
244
237
  return [...edits]
245
238
  .sort((left, right) => right.start - left.start)
@@ -8,4 +8,3 @@ export declare function detectPackageManager(projectRoot: string, manifest?: Pro
8
8
  /** Finds an HTML document root without recursively scanning dependency or build output. */
9
9
  export declare function detectStaticRoot(projectRoot: string): string | undefined;
10
10
  export declare function frameworkDisplayName(framework: Framework): string;
11
- export declare function adapterPackage(framework: Framework): string;
package/dist/detection.js CHANGED
@@ -57,7 +57,7 @@ export function detectFrameworks(projectRoot, manifest = readProjectManifest(pro
57
57
  return ["vite-react"];
58
58
  if (hasVite)
59
59
  return [];
60
- if (manifest.dependencies.has("@nudge-ui/standalone") || detectStaticRoot(projectRoot))
60
+ if (manifest.dependencies.has("nudge-ui") || detectStaticRoot(projectRoot))
61
61
  return ["standalone"];
62
62
  return [];
63
63
  }
@@ -107,15 +107,6 @@ export function frameworkDisplayName(framework) {
107
107
  return "Vite with React";
108
108
  return "static HTML";
109
109
  }
110
- export function adapterPackage(framework) {
111
- if (framework === "nextjs")
112
- return "@nudge-ui/nextjs";
113
- if (framework === "astro")
114
- return "@nudge-ui/astro";
115
- if (framework === "vite-react")
116
- return "@nudge-ui/vite-react";
117
- return "@nudge-ui/standalone";
118
- }
119
110
  function hasAnyFile(projectRoot, names) {
120
111
  return names.some((name) => existsSync(join(projectRoot, name)));
121
112
  }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { parseArguments, helpText, type CliOptions } from "./arguments.js";
2
2
  export { configureSource, planConfiguration } from "./configuration.js";
3
- export { adapterPackage, detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
3
+ export { detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
4
4
  export { formatCommand, installCommand, type InstallCommand } from "./installer.js";
5
5
  export { frameworks, packageManagers, type Framework, type PackageManager } from "./types.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { parseArguments, helpText } from "./arguments.js";
2
2
  export { configureSource, planConfiguration } from "./configuration.js";
3
- export { adapterPackage, detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
3
+ export { detectFrameworks, detectPackageManager, detectStaticRoot, frameworkDisplayName, readProjectManifest, } from "./detection.js";
4
4
  export { formatCommand, installCommand } from "./installer.js";
5
5
  export { frameworks, packageManagers } from "./types.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nudge-ui",
3
- "version": "0.1.3",
3
+ "version": "0.2.0",
4
4
  "description": "Framework-detecting installer for Nudge UI.",
5
5
  "license": "MIT",
6
6
  "repository": {