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 +1 -1
- package/dist/cli.js +5 -4
- package/dist/configuration.js +80 -87
- package/dist/detection.d.ts +0 -1
- package/dist/detection.js +1 -10
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
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
|
|
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 {
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
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)
|
package/dist/configuration.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
38
|
+
return configureAstroSource(source, sourceFile);
|
|
26
39
|
}
|
|
27
40
|
if (framework === "vite-react") {
|
|
28
|
-
return
|
|
41
|
+
return configureViteSource(source, sourceFile);
|
|
29
42
|
}
|
|
30
43
|
return configureNextSource(source, sourceFile);
|
|
31
44
|
}
|
|
32
|
-
function
|
|
33
|
-
const
|
|
34
|
-
const
|
|
35
|
-
const
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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 =
|
|
87
|
+
const target = findConfigExport(sourceFile, "Next.js");
|
|
61
88
|
const bindings = target.commonJs
|
|
62
|
-
? requiredBindings(sourceFile, "
|
|
63
|
-
: importedBindings(sourceFile, "
|
|
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("
|
|
74
|
-
: 'import { withNudgeUi } from "
|
|
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 =
|
|
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
|
|
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(
|
|
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)
|
package/dist/detection.d.ts
CHANGED
|
@@ -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("
|
|
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 {
|
|
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 {
|
|
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";
|