create-wp-typia 0.1.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.
Files changed (90) hide show
  1. package/README.md +33 -0
  2. package/dist/cli.js +87837 -0
  3. package/dist/highlights-eq9cgrbb.scm +604 -0
  4. package/dist/highlights-ghv9g403.scm +205 -0
  5. package/dist/highlights-hk7bwhj4.scm +284 -0
  6. package/dist/highlights-r812a2qc.scm +150 -0
  7. package/dist/highlights-x6tmsnaa.scm +115 -0
  8. package/dist/injections-73j83es3.scm +27 -0
  9. package/dist/tree-sitter-javascript-nd0q4pe9.wasm +0 -0
  10. package/dist/tree-sitter-markdown-411r6y9b.wasm +0 -0
  11. package/dist/tree-sitter-markdown_inline-j5349f42.wasm +0 -0
  12. package/dist/tree-sitter-typescript-zxjzwt75.wasm +0 -0
  13. package/dist/tree-sitter-zig-e78zbjpm.wasm +0 -0
  14. package/lib/entry.js +29 -0
  15. package/lib/node-cli.js +326 -0
  16. package/lib/package-managers.d.ts +29 -0
  17. package/lib/package-managers.js +170 -0
  18. package/lib/scaffold.d.ts +64 -0
  19. package/lib/scaffold.js +565 -0
  20. package/lib/template-registry.d.ts +18 -0
  21. package/lib/template-registry.js +58 -0
  22. package/package.json +64 -0
  23. package/src/cli.ts +329 -0
  24. package/templates/advanced/README.md.mustache +70 -0
  25. package/templates/advanced/block.json.mustache +42 -0
  26. package/templates/advanced/index.js +21 -0
  27. package/templates/advanced/package.json.mustache +48 -0
  28. package/templates/advanced/scripts/generate-migrations.ts.mustache +267 -0
  29. package/templates/advanced/scripts/lib/typia-metadata-core.ts +806 -0
  30. package/templates/advanced/scripts/migration-cli.ts.mustache +260 -0
  31. package/templates/advanced/scripts/sync-types-to-block-json.ts.mustache +25 -0
  32. package/templates/advanced/src/admin/migration-dashboard.tsx.mustache +450 -0
  33. package/templates/advanced/src/components/ErrorBoundary.tsx.mustache +47 -0
  34. package/templates/advanced/src/deprecated.ts.mustache +184 -0
  35. package/templates/advanced/src/edit.tsx.mustache +93 -0
  36. package/templates/advanced/src/hooks/useDebounce.ts.mustache +20 -0
  37. package/templates/advanced/src/hooks/useLocalStorage.ts.mustache +31 -0
  38. package/templates/advanced/src/hooks.ts.mustache +56 -0
  39. package/templates/advanced/src/index.tsx.mustache +16 -0
  40. package/templates/advanced/src/migration-detector.ts.mustache +417 -0
  41. package/templates/advanced/src/migrations/index.ts.mustache +361 -0
  42. package/templates/advanced/src/save.tsx.mustache +40 -0
  43. package/templates/advanced/src/style.scss.mustache +84 -0
  44. package/templates/advanced/src/types/versions.ts.mustache +108 -0
  45. package/templates/advanced/src/types.ts.mustache +45 -0
  46. package/templates/advanced/src/utils/classnames.ts.mustache +51 -0
  47. package/templates/advanced/src/utils/debounce.ts.mustache +37 -0
  48. package/templates/advanced/src/utils/index.ts.mustache +7 -0
  49. package/templates/advanced/src/utils/uuid.ts.mustache +17 -0
  50. package/templates/advanced/src/validators.ts.mustache +39 -0
  51. package/templates/advanced/src/view.ts.mustache +59 -0
  52. package/templates/advanced/tsconfig.json.mustache +9 -0
  53. package/templates/advanced/webpack.config.js.mustache +85 -0
  54. package/templates/basic/package.json.mustache +39 -0
  55. package/templates/basic/scripts/lib/typia-metadata-core.ts +806 -0
  56. package/templates/basic/scripts/sync-types-to-block-json.ts +25 -0
  57. package/templates/basic/src/block.json +51 -0
  58. package/templates/basic/src/edit.tsx +85 -0
  59. package/templates/basic/src/hooks.ts +75 -0
  60. package/templates/basic/src/index.tsx +37 -0
  61. package/templates/basic/src/save.tsx +27 -0
  62. package/templates/basic/src/style.scss +42 -0
  63. package/templates/basic/src/types.ts +47 -0
  64. package/templates/basic/src/validators.ts +39 -0
  65. package/templates/basic/tsconfig.json +20 -0
  66. package/templates/basic/webpack.config.js +85 -0
  67. package/templates/full/package.json.mustache +40 -0
  68. package/templates/full/scripts/lib/typia-metadata-core.ts +806 -0
  69. package/templates/full/scripts/sync-types-to-block-json.ts.mustache +25 -0
  70. package/templates/full/src/block.json.mustache +121 -0
  71. package/templates/full/src/edit.tsx.mustache +300 -0
  72. package/templates/full/src/editor.scss.mustache +251 -0
  73. package/templates/full/src/hooks.ts.mustache +140 -0
  74. package/templates/full/src/index.tsx.mustache +27 -0
  75. package/templates/full/src/save.tsx.mustache +39 -0
  76. package/templates/full/src/style.scss.mustache +224 -0
  77. package/templates/full/src/types.ts.mustache +34 -0
  78. package/templates/full/src/validators.ts.mustache +84 -0
  79. package/templates/full/tsconfig.json.mustache +9 -0
  80. package/templates/full/webpack.config.js.mustache +85 -0
  81. package/templates/interactivity/package.json.mustache +41 -0
  82. package/templates/interactivity/scripts/lib/typia-metadata-core.ts +806 -0
  83. package/templates/interactivity/scripts/sync-types-to-block-json.ts.mustache +25 -0
  84. package/templates/interactivity/src/block.json.mustache +75 -0
  85. package/templates/interactivity/src/edit.tsx.mustache +206 -0
  86. package/templates/interactivity/src/interactivity.ts.mustache +183 -0
  87. package/templates/interactivity/src/save.tsx.mustache +87 -0
  88. package/templates/interactivity/src/types.ts.mustache +29 -0
  89. package/templates/interactivity/tsconfig.json.mustache +9 -0
  90. package/templates/interactivity/webpack.config.js.mustache +85 -0
@@ -0,0 +1,115 @@
1
+ ; Query from: https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/99ddf573531c4dbe53f743ecbc1595af5eb1d32f/queries/markdown_inline/highlights.scm
2
+ ; From MDeiml/tree-sitter-markdown
3
+ (code_span) @markup.raw @nospell
4
+
5
+ (emphasis) @markup.italic
6
+
7
+ (strong_emphasis) @markup.strong
8
+
9
+ (strikethrough) @markup.strikethrough
10
+
11
+ (shortcut_link
12
+ (link_text) @nospell)
13
+
14
+ [
15
+ (backslash_escape)
16
+ (hard_line_break)
17
+ ] @string.escape
18
+
19
+ ; Conceal codeblock and text style markers
20
+ ([
21
+ (code_span_delimiter)
22
+ (emphasis_delimiter)
23
+ ] @conceal
24
+ (#set! conceal ""))
25
+
26
+ ; Inline links - style all parts
27
+ (inline_link
28
+ ["[" "(" ")"] @markup.link)
29
+
30
+ (inline_link
31
+ "]" @markup.link.bracket.close)
32
+
33
+ ; Conceal opening bracket
34
+ ((inline_link
35
+ "[" @conceal)
36
+ (#set! conceal ""))
37
+
38
+ ; Conceal closing bracket with space replacement
39
+ ((inline_link
40
+ "]" @conceal)
41
+ (#set! conceal " "))
42
+
43
+ ; Conceal image links
44
+ (image
45
+ [
46
+ "!"
47
+ "["
48
+ "]"
49
+ "("
50
+ (link_destination)
51
+ ")"
52
+ ] @markup.link
53
+ (#set! conceal ""))
54
+
55
+ ; Conceal full reference links
56
+ (full_reference_link
57
+ [
58
+ "["
59
+ "]"
60
+ (link_label)
61
+ ] @markup.link
62
+ (#set! conceal ""))
63
+
64
+ ; Conceal collapsed reference links
65
+ (collapsed_reference_link
66
+ [
67
+ "["
68
+ "]"
69
+ ] @markup.link
70
+ (#set! conceal ""))
71
+
72
+ ; Conceal shortcut links
73
+ (shortcut_link
74
+ [
75
+ "["
76
+ "]"
77
+ ] @markup.link
78
+ (#set! conceal ""))
79
+
80
+ [
81
+ (link_destination)
82
+ (uri_autolink)
83
+ ] @markup.link.url @nospell
84
+
85
+ [
86
+ (link_label)
87
+ (link_text)
88
+ (link_title)
89
+ (image_description)
90
+ ] @markup.link.label
91
+
92
+ ; Replace common HTML entities.
93
+ ((entity_reference) @character.special
94
+ (#eq? @character.special " ")
95
+ (#set! conceal ""))
96
+
97
+ ((entity_reference) @character.special
98
+ (#eq? @character.special "<")
99
+ (#set! conceal "<"))
100
+
101
+ ((entity_reference) @character.special
102
+ (#eq? @character.special "&gt;")
103
+ (#set! conceal ">"))
104
+
105
+ ((entity_reference) @character.special
106
+ (#eq? @character.special "&amp;")
107
+ (#set! conceal "&"))
108
+
109
+ ((entity_reference) @character.special
110
+ (#eq? @character.special "&quot;")
111
+ (#set! conceal "\""))
112
+
113
+ ((entity_reference) @character.special
114
+ (#any-of? @character.special "&ensp;" "&emsp;")
115
+ (#set! conceal " "))
@@ -0,0 +1,27 @@
1
+ ; Query from: https://raw.githubusercontent.com/nvim-treesitter/nvim-treesitter/refs/heads/master/queries/markdown/injections.scm
2
+ (fenced_code_block
3
+ (info_string
4
+ (language) @_lang)
5
+ (code_fence_content) @injection.content
6
+ (#set-lang-from-info-string! @_lang))
7
+
8
+ ((html_block) @injection.content
9
+ (#set! injection.language "html")
10
+ (#set! injection.combined)
11
+ (#set! injection.include-children))
12
+
13
+ ((minus_metadata) @injection.content
14
+ (#set! injection.language "yaml")
15
+ (#offset! @injection.content 1 0 -1 0)
16
+ (#set! injection.include-children))
17
+
18
+ ((plus_metadata) @injection.content
19
+ (#set! injection.language "toml")
20
+ (#offset! @injection.content 1 0 -1 0)
21
+ (#set! injection.include-children))
22
+
23
+ ([
24
+ (inline)
25
+ (pipe_table_cell)
26
+ ] @injection.content
27
+ (#set! injection.language "markdown_inline"))
package/lib/entry.js ADDED
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env node
2
+
3
+ async function main() {
4
+ if (typeof globalThis.Bun !== "undefined") {
5
+ const translatedArgs = process.argv.slice(2).map((arg) => {
6
+ if (arg === "--no-install") {
7
+ return "--noInstall";
8
+ }
9
+ if (arg === "--package-manager") {
10
+ return "--packageManager";
11
+ }
12
+ if (arg.startsWith("--package-manager=")) {
13
+ return `--packageManager=${arg.slice("--package-manager=".length)}`;
14
+ }
15
+ return arg;
16
+ });
17
+ process.argv = [process.argv[0], process.argv[1], ...translatedArgs];
18
+ await import("../src/cli.ts");
19
+ return;
20
+ }
21
+
22
+ const { runNodeCli } = await import("./node-cli.js");
23
+ await runNodeCli(process.argv.slice(2));
24
+ }
25
+
26
+ main().catch((error) => {
27
+ console.error("❌ create-wp-typia failed:", error instanceof Error ? error.message : error);
28
+ process.exit(1);
29
+ });
@@ -0,0 +1,326 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import readline from "node:readline";
5
+ import { access, constants as fsConstants, rm, writeFile } from "node:fs/promises";
6
+ import { execFileSync } from "node:child_process";
7
+
8
+ import {
9
+ collectScaffoldAnswers,
10
+ resolvePackageManagerId,
11
+ resolveTemplateId,
12
+ scaffoldProject,
13
+ } from "./scaffold.js";
14
+ import {
15
+ PACKAGE_MANAGER_IDS,
16
+ formatInstallCommand,
17
+ formatRunScript,
18
+ getPackageManagerSelectOptions,
19
+ } from "./package-managers.js";
20
+ import {
21
+ TEMPLATE_IDS,
22
+ getTemplateById,
23
+ getTemplateSelectOptions,
24
+ listTemplates,
25
+ } from "./template-registry.js";
26
+
27
+ function parseArgs(argv) {
28
+ const parsed = {
29
+ help: false,
30
+ noInstall: false,
31
+ packageManager: undefined,
32
+ positionals: [],
33
+ template: undefined,
34
+ yes: false,
35
+ };
36
+
37
+ for (let index = 0; index < argv.length; index += 1) {
38
+ const arg = argv[index];
39
+
40
+ if (!arg.startsWith("-")) {
41
+ parsed.positionals.push(arg);
42
+ continue;
43
+ }
44
+
45
+ if (arg === "--help" || arg === "-h") {
46
+ parsed.help = true;
47
+ continue;
48
+ }
49
+ if (arg === "--yes" || arg === "-y") {
50
+ parsed.yes = true;
51
+ continue;
52
+ }
53
+ if (arg === "--no-install") {
54
+ parsed.noInstall = true;
55
+ continue;
56
+ }
57
+ if (arg === "--template" || arg === "-t") {
58
+ parsed.template = argv[index + 1];
59
+ index += 1;
60
+ continue;
61
+ }
62
+ if (arg.startsWith("--template=")) {
63
+ parsed.template = arg.split("=", 2)[1];
64
+ continue;
65
+ }
66
+ if (arg === "--package-manager" || arg === "-p") {
67
+ parsed.packageManager = argv[index + 1];
68
+ index += 1;
69
+ continue;
70
+ }
71
+ if (arg.startsWith("--package-manager=")) {
72
+ parsed.packageManager = arg.split("=", 2)[1];
73
+ continue;
74
+ }
75
+
76
+ throw new Error(`Unknown flag: ${arg}`);
77
+ }
78
+
79
+ return parsed;
80
+ }
81
+
82
+ function createPrompt() {
83
+ const rl = readline.createInterface({
84
+ input: process.stdin,
85
+ output: process.stdout,
86
+ });
87
+
88
+ return {
89
+ async text(message, defaultValue, validate) {
90
+ const suffix = defaultValue ? ` (${defaultValue})` : "";
91
+ const answer = await new Promise((resolve) => {
92
+ rl.question(`${message}${suffix}: `, resolve);
93
+ });
94
+ const value = String(answer).trim() || defaultValue;
95
+ if (validate) {
96
+ const result = validate(value);
97
+ if (result !== true) {
98
+ console.error(`❌ ${result}`);
99
+ return this.text(message, defaultValue, validate);
100
+ }
101
+ }
102
+ return value;
103
+ },
104
+ async select(message, options, defaultIndex = 1) {
105
+ console.log(message);
106
+ options.forEach((option, index) => {
107
+ const hint = option.hint ? ` - ${option.hint}` : "";
108
+ console.log(` ${index + 1}. ${option.label}${hint}`);
109
+ });
110
+
111
+ const answer = await this.text("Choice", String(defaultIndex));
112
+ const numeric = Number(answer);
113
+ if (!Number.isNaN(numeric) && options[numeric - 1]) {
114
+ return options[numeric - 1].value;
115
+ }
116
+
117
+ const direct = options.find((option) => option.value === answer);
118
+ if (direct) {
119
+ return direct.value;
120
+ }
121
+
122
+ console.error(`❌ Invalid selection: ${answer}`);
123
+ return this.select(message, options, defaultIndex);
124
+ },
125
+ close() {
126
+ rl.close();
127
+ },
128
+ };
129
+ }
130
+
131
+ function printHelp() {
132
+ console.log(`Usage:
133
+ create-wp-typia <project-dir> [--template <id>] [--yes] [--no-install] [--package-manager <id>]
134
+ create-wp-typia templates list
135
+ create-wp-typia templates inspect <id>
136
+ create-wp-typia doctor
137
+
138
+ Templates: ${TEMPLATE_IDS.join(", ")}
139
+ Package managers: ${PACKAGE_MANAGER_IDS.join(", ")}`);
140
+ }
141
+
142
+ function printTemplate(template) {
143
+ console.log(`${template.id}`);
144
+ console.log(template.description);
145
+ console.log(`Category: ${template.defaultCategory}`);
146
+ console.log(`Path: ${template.templateDir}`);
147
+ console.log(`Features: ${template.features.join(", ")}`);
148
+ }
149
+
150
+ function readCommandVersion(command, args = ["--version"]) {
151
+ try {
152
+ return execFileSync(command, args, {
153
+ encoding: "utf8",
154
+ stdio: ["ignore", "pipe", "ignore"],
155
+ }).trim();
156
+ } catch {
157
+ return null;
158
+ }
159
+ }
160
+
161
+ function compareMajorVersion(actualVersion, minimumMajor) {
162
+ const parsed = Number.parseInt(actualVersion.replace(/^v/, "").split(".")[0] ?? "", 10);
163
+ return Number.isFinite(parsed) && parsed >= minimumMajor;
164
+ }
165
+
166
+ async function checkWritableDirectory(directory) {
167
+ try {
168
+ await access(directory, fsConstants.W_OK);
169
+ return true;
170
+ } catch {
171
+ return false;
172
+ }
173
+ }
174
+
175
+ async function checkTempDirectory() {
176
+ const tempFile = path.join(os.tmpdir(), `create-wp-typia-${Date.now()}.tmp`);
177
+ try {
178
+ await writeFile(tempFile, "ok", "utf8");
179
+ await rm(tempFile, { force: true });
180
+ return true;
181
+ } catch {
182
+ return false;
183
+ }
184
+ }
185
+
186
+ function printDoctorLine(status, label, detail) {
187
+ const prefix = status === "PASS" ? "PASS" : "FAIL";
188
+ console.log(`${prefix} ${label}: ${detail}`);
189
+ }
190
+
191
+ async function runDoctor(cwd) {
192
+ const bunVersion = readCommandVersion("bun");
193
+ const nodeVersion = readCommandVersion("node");
194
+ const gitVersion = readCommandVersion("git");
195
+ const cwdWritable = await checkWritableDirectory(cwd);
196
+ const tempWritable = await checkTempDirectory();
197
+ let hasFailures = false;
198
+
199
+ printDoctorLine(
200
+ bunVersion && compareMajorVersion(bunVersion, 1) ? "PASS" : "FAIL",
201
+ "Bun",
202
+ bunVersion ? `Detected ${bunVersion}` : "Not available",
203
+ );
204
+ hasFailures ||= !(bunVersion && compareMajorVersion(bunVersion, 1));
205
+
206
+ printDoctorLine(
207
+ nodeVersion && compareMajorVersion(nodeVersion, 16) ? "PASS" : "FAIL",
208
+ "Node",
209
+ nodeVersion ? `Detected ${nodeVersion}` : "Not available",
210
+ );
211
+ hasFailures ||= !(nodeVersion && compareMajorVersion(nodeVersion, 16));
212
+
213
+ printDoctorLine(gitVersion ? "PASS" : "FAIL", "git", gitVersion ?? "Not available");
214
+ hasFailures ||= !gitVersion;
215
+
216
+ printDoctorLine(cwdWritable ? "PASS" : "FAIL", "Current directory", cwdWritable ? "Writable" : "Not writable");
217
+ hasFailures ||= !cwdWritable;
218
+
219
+ printDoctorLine(tempWritable ? "PASS" : "FAIL", "Temp directory", tempWritable ? "Writable" : "Not writable");
220
+ hasFailures ||= !tempWritable;
221
+
222
+ for (const template of listTemplates()) {
223
+ const hasAssets =
224
+ fs.existsSync(template.templateDir) &&
225
+ fs.existsSync(path.join(template.templateDir, "package.json.mustache"));
226
+ printDoctorLine(
227
+ hasAssets ? "PASS" : "FAIL",
228
+ `Template ${template.id}`,
229
+ hasAssets ? template.templateDir : "Missing core template assets",
230
+ );
231
+ hasFailures ||= !hasAssets;
232
+ }
233
+
234
+ if (hasFailures) {
235
+ throw new Error("Doctor found one or more failing checks.");
236
+ }
237
+ }
238
+
239
+ async function runScaffold(parsed, cwd) {
240
+ const isInteractive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
241
+ const prompt = createPrompt();
242
+
243
+ try {
244
+ const projectInput = parsed.positionals[0];
245
+ if (!projectInput) {
246
+ throw new Error("Project directory is required. Usage: create-wp-typia <project-dir>");
247
+ }
248
+
249
+ const templateId = await resolveTemplateId({
250
+ templateId: parsed.template,
251
+ yes: parsed.yes,
252
+ isInteractive,
253
+ selectTemplate: () =>
254
+ prompt.select("Select a template", getTemplateSelectOptions(), 1),
255
+ });
256
+ const packageManager = await resolvePackageManagerId({
257
+ packageManager: parsed.packageManager,
258
+ yes: parsed.yes,
259
+ isInteractive,
260
+ selectPackageManager: () =>
261
+ prompt.select("Choose a package manager", getPackageManagerSelectOptions(), 1),
262
+ });
263
+ const projectDir = path.resolve(cwd, projectInput);
264
+ const projectName = path.basename(projectDir);
265
+ const answers = await collectScaffoldAnswers({
266
+ projectName,
267
+ templateId,
268
+ yes: parsed.yes,
269
+ promptText: (message, defaultValue, validate) =>
270
+ prompt.text(message, defaultValue, validate),
271
+ });
272
+ const result = await scaffoldProject({
273
+ answers,
274
+ noInstall: parsed.noInstall,
275
+ packageManager,
276
+ projectDir,
277
+ templateId,
278
+ });
279
+
280
+ console.log(`\n✅ Created ${result.variables.title} in ${projectDir}`);
281
+ console.log("Next steps:");
282
+ console.log(` cd ${path.isAbsolute(projectInput) ? projectDir : projectInput}`);
283
+ if (parsed.noInstall) {
284
+ console.log(` ${formatInstallCommand(packageManager)}`);
285
+ }
286
+ console.log(` ${formatRunScript(packageManager, "start")}`);
287
+ } finally {
288
+ prompt.close();
289
+ }
290
+ }
291
+
292
+ export async function runNodeCli(argv = process.argv.slice(2), cwd = process.cwd()) {
293
+ const parsed = parseArgs(argv);
294
+
295
+ if (parsed.help) {
296
+ printHelp();
297
+ return;
298
+ }
299
+
300
+ const [first, second] = parsed.positionals;
301
+ if (first === "templates") {
302
+ if (second === "list") {
303
+ for (const template of listTemplates()) {
304
+ console.log(`${template.id.padEnd(14)} ${template.description}`);
305
+ console.log(` ${template.features.join(" • ")}`);
306
+ }
307
+ return;
308
+ }
309
+ if (second === "inspect") {
310
+ const templateId = parsed.positionals[2];
311
+ if (!templateId) {
312
+ throw new Error(`Template ID is required. Use one of: ${TEMPLATE_IDS.join(", ")}`);
313
+ }
314
+ printTemplate(getTemplateById(templateId));
315
+ return;
316
+ }
317
+ throw new Error("Usage: create-wp-typia templates <list|inspect>");
318
+ }
319
+
320
+ if (first === "doctor") {
321
+ await runDoctor(cwd);
322
+ return;
323
+ }
324
+
325
+ await runScaffold(parsed, cwd);
326
+ }
@@ -0,0 +1,29 @@
1
+ export type PackageManagerId = "bun" | "npm" | "pnpm" | "yarn";
2
+
3
+ export interface PackageManagerConfig {
4
+ id: PackageManagerId;
5
+ label: string;
6
+ packageManagerField: string;
7
+ installCommand: string;
8
+ frozenInstallCommand: string;
9
+ }
10
+
11
+ export const PACKAGE_MANAGER_IDS: PackageManagerId[];
12
+ export const PACKAGE_MANAGERS: Record<PackageManagerId, PackageManagerConfig>;
13
+
14
+ export function getPackageManager(id: PackageManagerId | string): PackageManagerConfig;
15
+ export function getPackageManagerSelectOptions(): Array<{
16
+ label: string;
17
+ value: PackageManagerId;
18
+ hint: string;
19
+ }>;
20
+ export function formatRunScript(
21
+ packageManagerId: PackageManagerId,
22
+ scriptName: string,
23
+ extraArgs?: string,
24
+ ): string;
25
+ export function formatInstallCommand(packageManagerId: PackageManagerId): string;
26
+ export function transformPackageManagerText(
27
+ content: string,
28
+ packageManagerId: PackageManagerId,
29
+ ): string;
@@ -0,0 +1,170 @@
1
+ const PACKAGE_MANAGER_DATA = [
2
+ {
3
+ id: "bun",
4
+ label: "Bun",
5
+ packageManagerField: "bun@1.3.10",
6
+ installCommand: "bun install",
7
+ frozenInstallCommand: "bun install --frozen-lockfile",
8
+ },
9
+ {
10
+ id: "npm",
11
+ label: "npm",
12
+ packageManagerField: "npm@11.6.1",
13
+ installCommand: "npm install",
14
+ frozenInstallCommand: "npm ci",
15
+ },
16
+ {
17
+ id: "pnpm",
18
+ label: "pnpm",
19
+ packageManagerField: "pnpm@8.3.1",
20
+ installCommand: "pnpm install",
21
+ frozenInstallCommand: "pnpm install --frozen-lockfile",
22
+ },
23
+ {
24
+ id: "yarn",
25
+ label: "Yarn",
26
+ packageManagerField: "yarn@3.2.4",
27
+ installCommand: "yarn install",
28
+ frozenInstallCommand: "yarn install --frozen-lockfile",
29
+ },
30
+ ];
31
+
32
+ export const PACKAGE_MANAGER_IDS = PACKAGE_MANAGER_DATA.map((manager) => manager.id);
33
+ export const PACKAGE_MANAGERS = Object.freeze(
34
+ Object.fromEntries(PACKAGE_MANAGER_DATA.map((manager) => [manager.id, manager])),
35
+ );
36
+
37
+ const DEV_INSTALL_FLAGS = {
38
+ bun: "add -d",
39
+ npm: "install --save-dev",
40
+ pnpm: "add -D",
41
+ yarn: "add -D",
42
+ };
43
+
44
+ const STOP_CHARS = new Set(["\n", "\r", "`", "\"", "'", ")", "]", "}", "!", ",", "."]);
45
+
46
+ export function getPackageManager(id) {
47
+ const manager = PACKAGE_MANAGERS[id];
48
+ if (!manager) {
49
+ throw new Error(
50
+ `Unknown package manager "${id}". Expected one of: ${PACKAGE_MANAGER_IDS.join(", ")}`,
51
+ );
52
+ }
53
+ return manager;
54
+ }
55
+
56
+ export function getPackageManagerSelectOptions() {
57
+ return PACKAGE_MANAGER_DATA.map((manager) => ({
58
+ label: manager.label,
59
+ value: manager.id,
60
+ hint: manager.installCommand,
61
+ }));
62
+ }
63
+
64
+ export function formatRunScript(packageManagerId, scriptName, extraArgs = "") {
65
+ const args = extraArgs.trim();
66
+
67
+ if (packageManagerId === "bun") {
68
+ return args ? `bun run ${scriptName} ${args}` : `bun run ${scriptName}`;
69
+ }
70
+ if (packageManagerId === "npm") {
71
+ return args ? `npm run ${scriptName} -- ${args}` : `npm run ${scriptName}`;
72
+ }
73
+ if (packageManagerId === "pnpm") {
74
+ return args ? `pnpm run ${scriptName} -- ${args}` : `pnpm run ${scriptName}`;
75
+ }
76
+
77
+ return args ? `yarn run ${scriptName} ${args}` : `yarn run ${scriptName}`;
78
+ }
79
+
80
+ export function formatInstallCommand(packageManagerId) {
81
+ return getPackageManager(packageManagerId).installCommand;
82
+ }
83
+
84
+ function consumeCommandArguments(content, startIndex) {
85
+ let cursor = startIndex;
86
+ let args = "";
87
+
88
+ while (cursor < content.length) {
89
+ const current = content[cursor];
90
+ if (
91
+ STOP_CHARS.has(current) ||
92
+ content.startsWith("&&", cursor) ||
93
+ content.startsWith("||", cursor) ||
94
+ current === ";"
95
+ ) {
96
+ break;
97
+ }
98
+
99
+ args += current;
100
+ cursor += 1;
101
+ }
102
+
103
+ return {
104
+ args: args.trim(),
105
+ cursor,
106
+ };
107
+ }
108
+
109
+ function replaceBunRunCommands(content, packageManagerId) {
110
+ const marker = "bun run ";
111
+ let result = "";
112
+ let cursor = 0;
113
+
114
+ while (cursor < content.length) {
115
+ const index = content.indexOf(marker, cursor);
116
+ if (index === -1) {
117
+ result += content.slice(cursor);
118
+ break;
119
+ }
120
+
121
+ if (index > 0 && /[A-Za-z0-9_-]/.test(content[index - 1])) {
122
+ result += content.slice(cursor, index + marker.length);
123
+ cursor = index + marker.length;
124
+ continue;
125
+ }
126
+
127
+ result += content.slice(cursor, index);
128
+ const scriptNameStart = index + marker.length;
129
+ const scriptNameMatch = /^[A-Za-z0-9:_-]+/.exec(content.slice(scriptNameStart));
130
+ if (!scriptNameMatch) {
131
+ result += marker;
132
+ cursor = scriptNameStart;
133
+ continue;
134
+ }
135
+
136
+ const scriptName = scriptNameMatch[0];
137
+ const argsStart = scriptNameStart + scriptName.length;
138
+ const { args, cursor: nextCursor } = consumeCommandArguments(content, argsStart);
139
+ result += formatRunScript(packageManagerId, scriptName, args);
140
+ cursor = nextCursor;
141
+ }
142
+
143
+ return result;
144
+ }
145
+
146
+ function replaceDevDependencyInstalls(content, packageManagerId) {
147
+ return content.replace(/\bbun add -d ([^\s&|;`"'()]+)\b/g, (_, packageName) => {
148
+ if (packageManagerId === "bun") {
149
+ return `bun add -d ${packageName}`;
150
+ }
151
+
152
+ return `${packageManagerId} ${DEV_INSTALL_FLAGS[packageManagerId]} ${packageName}`;
153
+ });
154
+ }
155
+
156
+ export function transformPackageManagerText(content, packageManagerId) {
157
+ const manager = getPackageManager(packageManagerId);
158
+
159
+ return replaceDevDependencyInstalls(
160
+ replaceBunRunCommands(
161
+ content
162
+ .replace(/\bbun install --frozen-lockfile\b/g, manager.frozenInstallCommand)
163
+ .replace(/\bbun install\b/g, manager.installCommand),
164
+ packageManagerId,
165
+ ),
166
+ packageManagerId,
167
+ )
168
+ .replace(/\s*&&\s*/g, " && ")
169
+ .replace(/\s*\|\|\s*/g, " || ");
170
+ }