@fragno-dev/create 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,12 @@
1
+ $ tsdown
2
+ ℹ tsdown v0.15.6 powered by rolldown v1.0.0-beta.41
3
+ ℹ Using tsdown config: /home/runner/work/fragno/fragno/packages/create/tsdown.config.ts
4
+ ℹ entry: src/index.ts
5
+ ℹ tsconfig: tsconfig.json
6
+ ℹ Build start
7
+ ℹ dist/index.js 4.48 kB │ gzip: 1.51 kB
8
+ ℹ dist/index.js.map 8.87 kB │ gzip: 2.80 kB
9
+ ℹ dist/index.d.ts.map 0.35 kB │ gzip: 0.22 kB
10
+ ℹ dist/index.d.ts 0.40 kB │ gzip: 0.25 kB
11
+ ℹ 4 files, total: 14.10 kB
12
+ ✔ Build complete in 13800ms
@@ -0,0 +1,66 @@
1
+ $ vitest run
2
+
3
+  RUN  v3.2.4 /home/runner/work/fragno/fragno/packages/create
4
+ Coverage enabled with istanbul
5
+
6
+ ✓ src/utils.test.ts (1 test) 24ms
7
+ stdout | src/integration.test.ts > fragment with rspack
8
+ temp /tmp/fragment-test-tsdown-1759910775013
9
+
10
+ stdout | src/integration.test.ts > fragment with tsdown > package.json correctly templated
11
+ temp /tmp/fragment-test-esbuild-1759910775014
12
+
13
+ stdout | src/integration.test.ts > fragment with esbuild > package.json correctly templated
14
+ temp /tmp/fragment-test-vite-1759910775018
15
+
16
+ stdout | src/integration.test.ts > fragment with vite > package.json correctly templated
17
+ temp /tmp/fragment-test-rollup-1759910775018
18
+
19
+ stdout | src/integration.test.ts > fragment with rollup > package.json correctly templated
20
+ temp /tmp/fragment-test-webpack-1759910775018
21
+
22
+ stdout | src/integration.test.ts > fragment with webpack > package.json correctly templated
23
+ temp /tmp/fragment-test-rspack-1759910775018
24
+
25
+ stdout | src/integration.test.ts > fragment with vite > compiles
26
+ 
27
+
28
+ stdout | src/integration.test.ts > fragment with rspack > compiles
29
+ 
30
+
31
+ stdout | src/integration.test.ts > fragment with tsdown > builds
32
+ 
33
+
34
+ stdout | src/integration.test.ts > fragment with esbuild > builds
35
+ 
36
+
37
+ stdout | src/integration.test.ts
38
+ 
39
+
40
+ stdout | src/integration.test.ts > fragment with vite > builds
41
+ 
42
+
43
+ ✓ src/integration.test.ts (24 tests | 1 skipped) 48944ms
44
+ ✓ fragment with tsdown > installs  5270ms
45
+ ✓ fragment with tsdown > compiles  12751ms
46
+ ✓ fragment with tsdown > builds  22526ms
47
+ ✓ fragment with esbuild > installs  4957ms
48
+ ✓ fragment with esbuild > compiles  13190ms
49
+ ✓ fragment with esbuild > builds  5102ms
50
+ ✓ fragment with vite > installs  5743ms
51
+ ✓ fragment with vite > compiles  14099ms
52
+ ✓ fragment with vite > builds  11778ms
53
+ ✓ fragment with rollup > installs  5212ms
54
+ ✓ fragment with rollup > compiles  13215ms
55
+ ✓ fragment with webpack > installs  5278ms
56
+ ✓ fragment with webpack > compiles  12925ms
57
+ ✓ fragment with webpack > builds  29912ms
58
+ ✓ fragment with rspack > installs  5536ms
59
+ ✓ fragment with rspack > compiles  12422ms
60
+ ✓ fragment with rspack > builds  8061ms
61
+
62
+  Test Files  2 passed (2)
63
+  Tests  24 passed | 1 skipped (25)
64
+  Start at  08:06:10
65
+  Duration  52.54s (transform 2.20s, setup 0ms, collect 3.60s, tests 48.97s, environment 1ms, prepare 909ms)
66
+
@@ -0,0 +1 @@
1
+ $ tsc --noEmit
package/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # @fragno-dev/create
2
+
3
+ ## 0.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - ae891ac: Create fragment templates using 'npm create fragno'
@@ -0,0 +1,13 @@
1
+ //#region src/index.d.ts
2
+ type TemplateTypes = "fragment";
3
+ type BuildTools = "esbuild" | "tsdown" | "vite" | "rollup" | "webpack" | "rspack" | "none";
4
+ interface CreateOptions {
5
+ path: string;
6
+ buildTool: BuildTools;
7
+ name: string;
8
+ template: TemplateTypes;
9
+ }
10
+ declare function create(options: CreateOptions): void;
11
+ //#endregion
12
+ export { BuildTools, create };
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";KAMK,aAAA;AAAA,KAEO,UAAA,GAFM,SAAA,GAAA,QAAA,GAAA,MAAA,GAAA,QAAA,GAAA,SAAA,GAAA,QAAA,GAAA,MAAA;AAElB,UAEU,aAAA,CAFY;EAEZ,IAAA,EAAA,MAAA;EAAa,SAAA,EAEV,UAFU;MAEV,EAAA,MAAA;UAED,EAAA,aAAA;;AAGI,iBAAA,MAAA,CAAgB,OAAa,EAAb,aAAa,CAAA,EAAA,IAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,154 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ //#region src/utils.ts
6
+ function mkdirp(dir) {
7
+ try {
8
+ fs.mkdirSync(dir, { recursive: true });
9
+ } catch (e) {
10
+ if (e instanceof Error && "code" in e && e.code === "EEXIST") return;
11
+ throw e;
12
+ }
13
+ }
14
+ function isPlainObject(value) {
15
+ return value !== null && typeof value === "object" && !Array.isArray(value) && Object.prototype.toString.call(value) === "[object Object]";
16
+ }
17
+ /**
18
+ * Deep merge plain objects. Arrays and primitives are overwritten, not merged.
19
+ * @param target The target object
20
+ * @param source The source object
21
+ * @returns The merged object
22
+ */
23
+ function merge(target, source) {
24
+ const result = { ...target };
25
+ for (const key in source) {
26
+ const sourceValue = source[key];
27
+ const targetValue = result[key];
28
+ if (isPlainObject(sourceValue) && isPlainObject(targetValue)) result[key] = merge(targetValue, sourceValue);
29
+ else result[key] = sourceValue;
30
+ }
31
+ return result;
32
+ }
33
+ function identity(x) {
34
+ return x;
35
+ }
36
+ function copy(from, to, rename = identity) {
37
+ if (!fs.existsSync(from)) return;
38
+ if (fs.statSync(from).isDirectory()) fs.readdirSync(from).forEach((file) => {
39
+ copy(path.join(from, file), path.join(to, rename(file)));
40
+ });
41
+ else {
42
+ mkdirp(path.dirname(to));
43
+ fs.copyFileSync(from, to);
44
+ }
45
+ }
46
+
47
+ //#endregion
48
+ //#region src/package-json.ts
49
+ const unpluginFragnoVersion = "^0.0.1";
50
+ const buildToolPkg = {
51
+ none: {},
52
+ tsdown: {
53
+ devDependencies: {
54
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
55
+ tsdown: "^0.11.9"
56
+ },
57
+ scripts: { build: "tsdown" }
58
+ },
59
+ esbuild: {
60
+ devDependencies: {
61
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
62
+ esbuild: "^0.25.10"
63
+ },
64
+ scripts: { build: "./esbuild.config.js" }
65
+ },
66
+ vite: {
67
+ devDependencies: {
68
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
69
+ vite: "^6.0.0"
70
+ },
71
+ scripts: { build: "vite build" }
72
+ },
73
+ rollup: {
74
+ devDependencies: {
75
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
76
+ "@rollup/plugin-node-resolve": "^16.0.2",
77
+ "@rollup/plugin-typescript": "^12.1.4",
78
+ tslib: "^2.8.1",
79
+ rollup: "^4.52.4"
80
+ },
81
+ scripts: { build: "rollup -c" }
82
+ },
83
+ webpack: {
84
+ devDependencies: {
85
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
86
+ webpack: "^5.102.0",
87
+ "webpack-cli": "^6.0.1",
88
+ "ts-loader": "^9.5.1"
89
+ },
90
+ scripts: { build: "webpack" }
91
+ },
92
+ rspack: {
93
+ devDependencies: {
94
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
95
+ "@rspack/core": "^1.5.8",
96
+ "@rspack/cli": "^1.5.8"
97
+ },
98
+ scripts: { build: "rspack build" }
99
+ }
100
+ };
101
+
102
+ //#endregion
103
+ //#region src/index.ts
104
+ function create(options) {
105
+ let pkgOverride = { name: options.name };
106
+ pkgOverride = merge(pkgOverride, buildToolPkg[options.buildTool]);
107
+ if (options.template == "fragment") writeFragmentTemplate(options.path, pkgOverride);
108
+ else throw new Error(`Unsupported template type: ${options.template}`);
109
+ switch (options.buildTool) {
110
+ case "esbuild":
111
+ writeOptionalTemplate(options.path, "builder/esbuild.config.js");
112
+ break;
113
+ case "tsdown":
114
+ writeOptionalTemplate(options.path, "builder/tsdown.config.ts");
115
+ break;
116
+ case "vite":
117
+ writeOptionalTemplate(options.path, "builder/vite.config.ts");
118
+ break;
119
+ case "rollup":
120
+ writeOptionalTemplate(options.path, "builder/rollup.config.js");
121
+ break;
122
+ case "webpack":
123
+ writeOptionalTemplate(options.path, "builder/webpack.config.js");
124
+ break;
125
+ case "rspack":
126
+ writeOptionalTemplate(options.path, "builder/rspack.config.js");
127
+ break;
128
+ case "none": break;
129
+ }
130
+ }
131
+ function getTemplateDir() {
132
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
133
+ return path.join(__dirname, "..", "templates");
134
+ }
135
+ function writeOptionalTemplate(targetPath, template) {
136
+ const templatePath = path.join(getTemplateDir(), "optional", template);
137
+ const targetFile = path.join(targetPath, path.basename(template));
138
+ copy(templatePath, targetFile);
139
+ }
140
+ function writeFragmentTemplate(targetPath, pkgOverrides) {
141
+ const templateDir = path.join(getTemplateDir(), "fragment");
142
+ copy(templateDir, targetPath, (basename) => {
143
+ if (basename === "package.template.json") return "package.json";
144
+ return basename;
145
+ });
146
+ const packageJsonPath = path.join(targetPath, "package.json");
147
+ const basePkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
148
+ const newPkg = merge(basePkg, pkgOverrides);
149
+ fs.writeFileSync(packageJsonPath, JSON.stringify(newPkg, null, 2) + "\n");
150
+ }
151
+
152
+ //#endregion
153
+ export { create };
154
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":["e: unknown","buildToolPkg: Record<BuildTools, Record<string, unknown>>","pkgOverride: Record<string, unknown>"],"sources":["../src/utils.ts","../src/package-json.ts","../src/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\n\nexport function mkdirp(dir: string): void {\n try {\n fs.mkdirSync(dir, { recursive: true });\n } catch (e: unknown) {\n if (e instanceof Error && \"code\" in e && e.code === \"EEXIST\") return;\n throw e;\n }\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return (\n value !== null &&\n typeof value === \"object\" &&\n !Array.isArray(value) &&\n Object.prototype.toString.call(value) === \"[object Object]\"\n );\n}\n\n/**\n * Deep merge plain objects. Arrays and primitives are overwritten, not merged.\n * @param target The target object\n * @param source The source object\n * @returns The merged object\n */\nexport function merge<T extends Record<string, unknown>>(\n target: T,\n source: Record<string, unknown>,\n): T {\n const result = { ...target } as Record<string, unknown>;\n\n for (const key in source) {\n const sourceValue = source[key];\n const targetValue = result[key];\n\n if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {\n result[key] = merge(targetValue, sourceValue);\n } else {\n result[key] = sourceValue;\n }\n }\n\n return result as T;\n}\n\nfunction identity<T>(x: T): T {\n return x;\n}\n\nexport function copy(\n from: string,\n to: string,\n rename: (basename: string) => string = identity,\n): void {\n if (!fs.existsSync(from)) return;\n\n const stats = fs.statSync(from);\n\n if (stats.isDirectory()) {\n fs.readdirSync(from).forEach((file) => {\n copy(path.join(from, file), path.join(to, rename(file)));\n });\n } else {\n mkdirp(path.dirname(to));\n fs.copyFileSync(from, to);\n }\n}\n","import type { BuildTools } from \"./index\";\n\nconst unpluginFragnoVersion = \"^0.0.1\";\n\nexport const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {\n none: {},\n tsdown: {\n devDependencies: {\n \"@fragno-dev/unplugin-fragno\": unpluginFragnoVersion,\n tsdown: \"^0.11.9\",\n },\n scripts: {\n build: \"tsdown\",\n },\n },\n esbuild: {\n devDependencies: {\n \"@fragno-dev/unplugin-fragno\": unpluginFragnoVersion,\n esbuild: \"^0.25.10\",\n },\n scripts: {\n build: \"./esbuild.config.js\",\n },\n },\n vite: {\n devDependencies: {\n \"@fragno-dev/unplugin-fragno\": unpluginFragnoVersion,\n vite: \"^6.0.0\",\n },\n scripts: {\n build: \"vite build\",\n },\n },\n rollup: {\n devDependencies: {\n \"@fragno-dev/unplugin-fragno\": unpluginFragnoVersion,\n \"@rollup/plugin-node-resolve\": \"^16.0.2\",\n \"@rollup/plugin-typescript\": \"^12.1.4\",\n tslib: \"^2.8.1\",\n rollup: \"^4.52.4\",\n },\n scripts: {\n build: \"rollup -c\",\n },\n },\n webpack: {\n devDependencies: {\n \"@fragno-dev/unplugin-fragno\": unpluginFragnoVersion,\n webpack: \"^5.102.0\",\n \"webpack-cli\": \"^6.0.1\",\n \"ts-loader\": \"^9.5.1\",\n },\n scripts: {\n build: \"webpack\",\n },\n },\n rspack: {\n devDependencies: {\n \"@fragno-dev/unplugin-fragno\": unpluginFragnoVersion,\n \"@rspack/core\": \"^1.5.8\",\n \"@rspack/cli\": \"^1.5.8\",\n },\n scripts: {\n build: \"rspack build\",\n },\n },\n};\n","import fs from \"node:fs\";\nimport path from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { copy, merge } from \"./utils.ts\";\nimport { buildToolPkg } from \"./package-json.ts\";\n\ntype TemplateTypes = \"fragment\";\n// TODO: the others\nexport type BuildTools = \"esbuild\" | \"tsdown\" | \"vite\" | \"rollup\" | \"webpack\" | \"rspack\" | \"none\";\n\ninterface CreateOptions {\n path: string;\n buildTool: BuildTools;\n name: string;\n template: TemplateTypes;\n}\n\nexport function create(options: CreateOptions) {\n let pkgOverride: Record<string, unknown> = { name: options.name };\n\n // Build tool pkg overrides\n pkgOverride = merge(pkgOverride, buildToolPkg[options.buildTool]);\n\n if (options.template == \"fragment\") {\n writeFragmentTemplate(options.path, pkgOverride);\n } else {\n throw new Error(`Unsupported template type: ${options.template}`);\n }\n\n switch (options.buildTool) {\n case \"esbuild\":\n writeOptionalTemplate(options.path, \"builder/esbuild.config.js\");\n break;\n case \"tsdown\":\n writeOptionalTemplate(options.path, \"builder/tsdown.config.ts\");\n break;\n case \"vite\":\n writeOptionalTemplate(options.path, \"builder/vite.config.ts\");\n break;\n case \"rollup\":\n writeOptionalTemplate(options.path, \"builder/rollup.config.js\");\n break;\n case \"webpack\":\n writeOptionalTemplate(options.path, \"builder/webpack.config.js\");\n break;\n case \"rspack\":\n writeOptionalTemplate(options.path, \"builder/rspack.config.js\");\n break;\n case \"none\":\n break;\n }\n}\n\nfunction getTemplateDir(): string {\n const __dirname = path.dirname(fileURLToPath(import.meta.url));\n return path.join(__dirname, \"..\", \"templates\");\n}\n\nfunction writeOptionalTemplate(targetPath: string, template: string) {\n const templatePath = path.join(getTemplateDir(), \"optional\", template);\n const targetFile = path.join(targetPath, path.basename(template));\n\n copy(templatePath, targetFile);\n}\n\nfunction writeFragmentTemplate(targetPath: string, pkgOverrides: Record<string, unknown>) {\n const templateDir = path.join(getTemplateDir(), \"fragment\");\n\n // Copy template files\n copy(templateDir, targetPath, (basename) => {\n if (basename === \"package.template.json\") {\n return \"package.json\";\n }\n return basename;\n });\n\n // Update package.json based on chosen options\n const packageJsonPath = path.join(targetPath, \"package.json\");\n const basePkg = JSON.parse(fs.readFileSync(packageJsonPath, \"utf-8\"));\n const newPkg = merge(basePkg, pkgOverrides);\n\n // Write to disk\n fs.writeFileSync(packageJsonPath, JSON.stringify(newPkg, null, 2) + \"\\n\");\n}\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@fragno-dev/create",
3
+ "description": "A library for creating Fragno fragments",
4
+ "version": "0.0.2",
5
+ "exports": {
6
+ ".": {
7
+ "bun": "./src/index.ts",
8
+ "development": "./src/index.ts",
9
+ "types": "./dist/index.d.ts",
10
+ "default": "./dist/index.js"
11
+ }
12
+ },
13
+ "main": "./dist/index.js",
14
+ "module": "./dist/index.js",
15
+ "types": "./dist/index.d.ts",
16
+ "scripts": {
17
+ "test": "vitest run",
18
+ "build": "tsdown",
19
+ "build:watch": "tsdown --watch",
20
+ "types:check": "tsc --noEmit"
21
+ },
22
+ "type": "module",
23
+ "dependencies": {
24
+ "zod": "^4.0.5"
25
+ },
26
+ "devDependencies": {
27
+ "@fragno-private/typescript-config": "0.0.1",
28
+ "@fragno-private/vitest-config": "0.0.0",
29
+ "@types/node": "^20",
30
+ "@types/bun": "latest",
31
+ "@vitest/coverage-istanbul": "^3.2.4",
32
+ "vitest": "^3.2.4"
33
+ },
34
+ "private": false,
35
+ "peerDependencies": {
36
+ "typescript": "^5"
37
+ }
38
+ }
package/src/index.ts ADDED
@@ -0,0 +1,84 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { copy, merge } from "./utils.ts";
5
+ import { buildToolPkg } from "./package-json.ts";
6
+
7
+ type TemplateTypes = "fragment";
8
+ // TODO: the others
9
+ export type BuildTools = "esbuild" | "tsdown" | "vite" | "rollup" | "webpack" | "rspack" | "none";
10
+
11
+ interface CreateOptions {
12
+ path: string;
13
+ buildTool: BuildTools;
14
+ name: string;
15
+ template: TemplateTypes;
16
+ }
17
+
18
+ export function create(options: CreateOptions) {
19
+ let pkgOverride: Record<string, unknown> = { name: options.name };
20
+
21
+ // Build tool pkg overrides
22
+ pkgOverride = merge(pkgOverride, buildToolPkg[options.buildTool]);
23
+
24
+ if (options.template == "fragment") {
25
+ writeFragmentTemplate(options.path, pkgOverride);
26
+ } else {
27
+ throw new Error(`Unsupported template type: ${options.template}`);
28
+ }
29
+
30
+ switch (options.buildTool) {
31
+ case "esbuild":
32
+ writeOptionalTemplate(options.path, "builder/esbuild.config.js");
33
+ break;
34
+ case "tsdown":
35
+ writeOptionalTemplate(options.path, "builder/tsdown.config.ts");
36
+ break;
37
+ case "vite":
38
+ writeOptionalTemplate(options.path, "builder/vite.config.ts");
39
+ break;
40
+ case "rollup":
41
+ writeOptionalTemplate(options.path, "builder/rollup.config.js");
42
+ break;
43
+ case "webpack":
44
+ writeOptionalTemplate(options.path, "builder/webpack.config.js");
45
+ break;
46
+ case "rspack":
47
+ writeOptionalTemplate(options.path, "builder/rspack.config.js");
48
+ break;
49
+ case "none":
50
+ break;
51
+ }
52
+ }
53
+
54
+ function getTemplateDir(): string {
55
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
56
+ return path.join(__dirname, "..", "templates");
57
+ }
58
+
59
+ function writeOptionalTemplate(targetPath: string, template: string) {
60
+ const templatePath = path.join(getTemplateDir(), "optional", template);
61
+ const targetFile = path.join(targetPath, path.basename(template));
62
+
63
+ copy(templatePath, targetFile);
64
+ }
65
+
66
+ function writeFragmentTemplate(targetPath: string, pkgOverrides: Record<string, unknown>) {
67
+ const templateDir = path.join(getTemplateDir(), "fragment");
68
+
69
+ // Copy template files
70
+ copy(templateDir, targetPath, (basename) => {
71
+ if (basename === "package.template.json") {
72
+ return "package.json";
73
+ }
74
+ return basename;
75
+ });
76
+
77
+ // Update package.json based on chosen options
78
+ const packageJsonPath = path.join(targetPath, "package.json");
79
+ const basePkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
80
+ const newPkg = merge(basePkg, pkgOverrides);
81
+
82
+ // Write to disk
83
+ fs.writeFileSync(packageJsonPath, JSON.stringify(newPkg, null, 2) + "\n");
84
+ }
@@ -0,0 +1,97 @@
1
+ import { test, expect, afterAll, beforeAll, describe } from "vitest";
2
+ import { exec } from "node:child_process";
3
+ import { promisify } from "node:util";
4
+ import { tmpdir } from "node:os";
5
+ import path from "node:path";
6
+ import fs from "node:fs/promises";
7
+ import { create } from ".";
8
+
9
+ const execAsync = promisify(exec);
10
+
11
+ async function createTempDir(name: string): Promise<string> {
12
+ const dir = path.join(tmpdir(), `${name}-${Date.now()}`);
13
+ try {
14
+ await fs.rm(dir, { recursive: true });
15
+ } catch {
16
+ // Ignore if directory doesn't exist
17
+ }
18
+ await fs.mkdir(dir, { recursive: true });
19
+ return dir;
20
+ }
21
+
22
+ describe.concurrent.each(["tsdown", "esbuild", "vite", "rollup", "webpack", "rspack"] as const)(
23
+ "fragment with %s",
24
+ (buildTool) => {
25
+ let tempDir: string;
26
+ const testConfig = {
27
+ name: "@myorg/test",
28
+ template: "fragment" as const,
29
+ buildTool,
30
+ };
31
+
32
+ beforeAll(async () => {
33
+ tempDir = await createTempDir(`fragment-test-${buildTool}`);
34
+ console.log("temp", tempDir);
35
+ create({ ...testConfig, path: tempDir });
36
+ });
37
+
38
+ afterAll(async () => {
39
+ await fs.rm(tempDir, { recursive: true });
40
+ });
41
+
42
+ describe.sequential("", () => {
43
+ test("package.json correctly templated", async () => {
44
+ const pkg = path.join(tempDir, "package.json");
45
+ const pkgContent = await fs.readFile(pkg, "utf8");
46
+ expect(pkgContent).toContain(testConfig.name);
47
+ });
48
+
49
+ test("installs", { timeout: 10000 }, async () => {
50
+ const { stdout } = await execAsync("bun install", {
51
+ cwd: tempDir,
52
+ encoding: "utf8",
53
+ });
54
+ expect(stdout).toBeDefined();
55
+ });
56
+
57
+ test("compiles", { timeout: 30000 }, async () => {
58
+ const { stdout } = await execAsync("bun run types:check", {
59
+ cwd: tempDir,
60
+ encoding: "utf8",
61
+ });
62
+ console.log(stdout);
63
+ expect(stdout).toBeDefined();
64
+ });
65
+ /*
66
+ FIXME: Skipping this test for rollup:
67
+ When running rollup directly through bun run build or npm run build the build succeeds,
68
+ but somehow when running through vitest the module resolution mechanism changes causing
69
+ the build to fail.
70
+ */
71
+ test.skipIf(buildTool == "rollup")("builds", { timeout: 40000 }, async () => {
72
+ const result = await execAsync("bun run build", {
73
+ cwd: tempDir,
74
+ encoding: "utf8",
75
+ });
76
+
77
+ expect(result).toBeDefined();
78
+ await expect(fs.access(path.join(tempDir, "dist"))).resolves.toBeUndefined();
79
+ await expect(fs.access(path.join(tempDir, "dist", "browser"))).resolves.toBeUndefined();
80
+
81
+ const reactBundle = path.join(tempDir, "dist", "browser", "client", "react.js");
82
+ const reactBundleContent = await fs.readFile(reactBundle, "utf8");
83
+ // We expect the core package to be included in the fragment build,
84
+ // each fragment has its own version of core so end-users don't have
85
+ // to add it to their dependencies.
86
+ expect(reactBundleContent).not.toMatch(/import\s+.*?\s+from\s+['"]@fragno-dev\/core/);
87
+ // However, the peerDependencies of @fragno-dev/core must not be included
88
+ expect(reactBundleContent).toMatch(/from\s*['"]react['"]/);
89
+
90
+ // Vite builds only the browser bundle
91
+ if (buildTool !== "vite") {
92
+ await expect(fs.access(path.join(tempDir, "dist", "node"))).resolves.toBeUndefined();
93
+ }
94
+ });
95
+ });
96
+ },
97
+ );
@@ -0,0 +1,67 @@
1
+ import type { BuildTools } from "./index";
2
+
3
+ const unpluginFragnoVersion = "^0.0.1";
4
+
5
+ export const buildToolPkg: Record<BuildTools, Record<string, unknown>> = {
6
+ none: {},
7
+ tsdown: {
8
+ devDependencies: {
9
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
10
+ tsdown: "^0.11.9",
11
+ },
12
+ scripts: {
13
+ build: "tsdown",
14
+ },
15
+ },
16
+ esbuild: {
17
+ devDependencies: {
18
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
19
+ esbuild: "^0.25.10",
20
+ },
21
+ scripts: {
22
+ build: "./esbuild.config.js",
23
+ },
24
+ },
25
+ vite: {
26
+ devDependencies: {
27
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
28
+ vite: "^6.0.0",
29
+ },
30
+ scripts: {
31
+ build: "vite build",
32
+ },
33
+ },
34
+ rollup: {
35
+ devDependencies: {
36
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
37
+ "@rollup/plugin-node-resolve": "^16.0.2",
38
+ "@rollup/plugin-typescript": "^12.1.4",
39
+ tslib: "^2.8.1",
40
+ rollup: "^4.52.4",
41
+ },
42
+ scripts: {
43
+ build: "rollup -c",
44
+ },
45
+ },
46
+ webpack: {
47
+ devDependencies: {
48
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
49
+ webpack: "^5.102.0",
50
+ "webpack-cli": "^6.0.1",
51
+ "ts-loader": "^9.5.1",
52
+ },
53
+ scripts: {
54
+ build: "webpack",
55
+ },
56
+ },
57
+ rspack: {
58
+ devDependencies: {
59
+ "@fragno-dev/unplugin-fragno": unpluginFragnoVersion,
60
+ "@rspack/core": "^1.5.8",
61
+ "@rspack/cli": "^1.5.8",
62
+ },
63
+ scripts: {
64
+ build: "rspack build",
65
+ },
66
+ },
67
+ };
@@ -0,0 +1,9 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { merge } from "./utils";
3
+
4
+ describe("merge", () => {
5
+ it("merges nested objects", () => {
6
+ const result = merge({ a: { c: 1 } }, { a: { b: 2 } });
7
+ expect(result).toEqual({ a: { c: 1, b: 2 } });
8
+ });
9
+ });
package/src/utils.ts ADDED
@@ -0,0 +1,69 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+
4
+ export function mkdirp(dir: string): void {
5
+ try {
6
+ fs.mkdirSync(dir, { recursive: true });
7
+ } catch (e: unknown) {
8
+ if (e instanceof Error && "code" in e && e.code === "EEXIST") return;
9
+ throw e;
10
+ }
11
+ }
12
+
13
+ function isPlainObject(value: unknown): value is Record<string, unknown> {
14
+ return (
15
+ value !== null &&
16
+ typeof value === "object" &&
17
+ !Array.isArray(value) &&
18
+ Object.prototype.toString.call(value) === "[object Object]"
19
+ );
20
+ }
21
+
22
+ /**
23
+ * Deep merge plain objects. Arrays and primitives are overwritten, not merged.
24
+ * @param target The target object
25
+ * @param source The source object
26
+ * @returns The merged object
27
+ */
28
+ export function merge<T extends Record<string, unknown>>(
29
+ target: T,
30
+ source: Record<string, unknown>,
31
+ ): T {
32
+ const result = { ...target } as Record<string, unknown>;
33
+
34
+ for (const key in source) {
35
+ const sourceValue = source[key];
36
+ const targetValue = result[key];
37
+
38
+ if (isPlainObject(sourceValue) && isPlainObject(targetValue)) {
39
+ result[key] = merge(targetValue, sourceValue);
40
+ } else {
41
+ result[key] = sourceValue;
42
+ }
43
+ }
44
+
45
+ return result as T;
46
+ }
47
+
48
+ function identity<T>(x: T): T {
49
+ return x;
50
+ }
51
+
52
+ export function copy(
53
+ from: string,
54
+ to: string,
55
+ rename: (basename: string) => string = identity,
56
+ ): void {
57
+ if (!fs.existsSync(from)) return;
58
+
59
+ const stats = fs.statSync(from);
60
+
61
+ if (stats.isDirectory()) {
62
+ fs.readdirSync(from).forEach((file) => {
63
+ copy(path.join(from, file), path.join(to, rename(file)));
64
+ });
65
+ } else {
66
+ mkdirp(path.dirname(to));
67
+ fs.copyFileSync(from, to);
68
+ }
69
+ }