@shivaedev/create-app 0.0.0-stage → 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.
@@ -0,0 +1,5 @@
1
+ import { Effect, FileSystem, Schema } from "effect";
2
+ import { ChildProcessSpawner } from "effect/unstable/process";
3
+ import { CreateAppError } from "./CreateAppError.ts";
4
+ export declare const createApp: (folder: string, templatePath: string) => Effect.Effect<undefined, CreateAppError | import("effect/PlatformError").PlatformError | Schema.SchemaError, ChildProcessSpawner.ChildProcessSpawner | FileSystem.FileSystem>;
5
+ //# sourceMappingURL=createApp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createApp.d.ts","sourceRoot":"","sources":["../src/createApp.ts"],"names":[],"mappings":"AAEA,OAAO,EAAW,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAgB,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC5E,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAKrD,eAAO,MAAM,SAAS,yNA+BpB,CAAC"}
@@ -0,0 +1,38 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { Console, Effect, FileSystem, Schema } from "effect";
4
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
5
+ import { CreateAppError } from "./CreateAppError.js";
6
+ import { Template } from "./template.js";
7
+ const decodeName = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)));
8
+ export const createApp = Effect.fn("CreateApp.createApp")(function* (folder, templatePath) {
9
+ const fs = yield* FileSystem.FileSystem;
10
+ const target = resolve(folder);
11
+ const name = basename(target);
12
+ if (!/^[a-z0-9][a-z0-9._-]*$/u.test(name) || name.length > 214 || ["node_modules", "favicon.ico"].includes(name)) {
13
+ return yield* new CreateAppError({ message: "The final folder name must be a lowercase npm package name." });
14
+ }
15
+ if ((yield* fs.exists(target)) && ((yield* fs.stat(target)).type !== "Directory" || (yield* fs.readDirectory(target)).length > 0)) {
16
+ return yield* new CreateAppError({ message: `Refusing to write into ${folder}: the destination must be an empty folder.` });
17
+ }
18
+ const template = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Template))(yield* fs.readFileString(templatePath));
19
+ yield* fs.makeDirectory(target, { recursive: true });
20
+ for (const entry of template) {
21
+ const path = join(target, entry.path);
22
+ let content = Buffer.from(entry.content, "base64");
23
+ if (entry.path === "package.json") {
24
+ const manifest = yield* decodeName(content.toString());
25
+ content = Buffer.from(`${JSON.stringify({ ...manifest, name }, null, "\t")}\n`);
26
+ }
27
+ yield* fs.makeDirectory(dirname(path), { recursive: true });
28
+ yield* fs.writeFile(path, content);
29
+ }
30
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
31
+ const code = yield* spawner.exitCode(ChildProcess.make("pnpm", ["install"], { cwd: target, stderr: "inherit", stdout: "inherit" }));
32
+ if (code !== 0) {
33
+ return yield* new CreateAppError({ message: `pnpm install failed (exit ${code}). Run pnpm install in ${folder} to retry.` });
34
+ }
35
+ const quotedFolder = `'${folder.replaceAll("'", "'\"'\"'")}'`;
36
+ yield* Console.log(`Created ${folder}.\n\nNext steps:\n cd ${quotedFolder}\n cp .env.example .env.local\n Set DATABASE_URL to your PostgreSQL database.\n Set BETTER_AUTH_SECRET to a random secret of at least 32 characters.\n pnpm dev`);
37
+ });
38
+ //# sourceMappingURL=createApp.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"createApp.js","sourceRoot":"","sources":["../src/createApp.ts"],"names":[],"mappings":"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"}
package/dist/pack.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=pack.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.d.ts","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":""}
package/dist/pack.js ADDED
@@ -0,0 +1,6 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
3
+ import { Effect } from "effect";
4
+ import { packTemplate } from "./packTemplate.js";
5
+ NodeRuntime.runMain(packTemplate(fileURLToPath(new URL("../../../examples/starter", import.meta.url)), fileURLToPath(new URL("../template.json", import.meta.url))).pipe(Effect.provide(NodeServices.layer)));
6
+ //# sourceMappingURL=pack.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pack.js","sourceRoot":"","sources":["../src/pack.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAClE,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAEjD,WAAW,CAAC,OAAO,CAClB,YAAY,CACX,aAAa,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EACpE,aAAa,CAAC,IAAI,GAAG,CAAC,kBAAkB,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAC3D,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,YAAY,CAAC,KAAK,CAAC,CAAC,CAC1C,CAAC"}
@@ -0,0 +1,4 @@
1
+ import { Effect, FileSystem, type PlatformError, Schema } from "effect";
2
+ import { CreateAppError } from "./CreateAppError.ts";
3
+ export declare const packTemplate: (starter: string, output: string) => Effect.Effect<void, CreateAppError | PlatformError.PlatformError | Schema.SchemaError, FileSystem.FileSystem>;
4
+ //# sourceMappingURL=packTemplate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packTemplate.d.ts","sourceRoot":"","sources":["../src/packTemplate.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,KAAK,aAAa,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAQrD,eAAO,MAAM,YAAY,oJA8BvB,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { basename, join } from "node:path";
3
+ import { Effect, FileSystem, Schema } from "effect";
4
+ import { CreateAppError } from "./CreateAppError.js";
5
+ import { prepareFile, Versions } from "./template.js";
6
+ const excluded = new Set(["node_modules", "dist", "coverage", "pnpm-lock.yaml", ".git"]);
7
+ function included(path) {
8
+ return !excluded.has(basename(path)) && (!basename(path).startsWith(".env") || basename(path) === ".env.example");
9
+ }
10
+ export const packTemplate = Effect.fn("CreateApp.packTemplate")(function* (starter, output) {
11
+ const fs = yield* FileSystem.FileSystem;
12
+ const versions = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Versions))(yield* fs.readFileString(join(starter, "published-versions.json")));
13
+ const entries = [];
14
+ const visit = Effect.fn("CreateApp.visit")(function* (directory) {
15
+ for (const name of (yield* fs.readDirectory(join(starter, directory))).sort()) {
16
+ const path = join(directory, name);
17
+ if (!included(path)) {
18
+ continue;
19
+ }
20
+ if ((yield* fs.stat(join(starter, path))).type === "Directory") {
21
+ yield* visit(path);
22
+ }
23
+ else {
24
+ const target = path === "pnpm-workspace.copy.yaml" ? "pnpm-workspace.yaml" : path;
25
+ const source = Buffer.from(yield* fs.readFile(join(starter, path)));
26
+ const content = yield* Effect.try({
27
+ catch: (cause) => new CreateAppError({ message: String(cause) }),
28
+ try: () => ["biome.json", "package.json", "tsconfig.json", "vite.config.ts", "vitest.config.ts"].includes(path)
29
+ ? Buffer.from(prepareFile(path, source.toString(), versions))
30
+ : source,
31
+ });
32
+ entries.push({ content: content.toString("base64"), path: target });
33
+ }
34
+ }
35
+ });
36
+ yield* visit("");
37
+ yield* fs.writeFileString(output, `${JSON.stringify(entries)}\n`);
38
+ });
39
+ //# sourceMappingURL=packTemplate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"packTemplate.js","sourceRoot":"","sources":["../src/packTemplate.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { Schema } from "effect";
2
+ export declare const Versions: Schema.$Record<Schema.String, Schema.String>;
3
+ export declare const Template: Schema.$Array<Schema.Struct<{
4
+ readonly content: Schema.String;
5
+ readonly path: Schema.String;
6
+ }>>;
7
+ export type Template = typeof Template.Type;
8
+ export declare const decodeTemplate: (input: unknown, options?: import("effect/SchemaAST").ParseOptions) => readonly {
9
+ readonly content: string;
10
+ readonly path: string;
11
+ }[];
12
+ export declare function registryDependencies(dependencies: Readonly<Record<string, string>>, versions: Readonly<Record<string, string>>): {
13
+ [k: string]: string;
14
+ };
15
+ export declare function prepareManifest(source: string, versions: Readonly<Record<string, string>>): string;
16
+ export declare function prepareFile(path: string, content: string, versions: Readonly<Record<string, string>>): string;
17
+ //# sourceMappingURL=template.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.d.ts","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,eAAO,MAAM,QAAQ,8CAA8C,CAAC;AACpE,eAAO,MAAM,QAAQ;;;GAA+E,CAAC;AACrG,MAAM,MAAM,QAAQ,GAAG,OAAO,QAAQ,CAAC,IAAI,CAAC;AAC5C,eAAO,MAAM,cAAc;;;GAA4D,CAAC;AAexF,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;;EAa9H;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,UAazF;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,UAWpG"}
@@ -0,0 +1,53 @@
1
+ import { Schema } from "effect";
2
+ import { CreateAppError } from "./CreateAppError.js";
3
+ export const Versions = Schema.Record(Schema.String, Schema.String);
4
+ export const Template = Schema.Array(Schema.Struct({ content: Schema.String, path: Schema.String }));
5
+ export const decodeTemplate = Schema.decodeUnknownSync(Schema.fromJsonString(Template));
6
+ const Manifest = Schema.Struct({
7
+ dependencies: Versions,
8
+ devDependencies: Versions,
9
+ engines: Versions,
10
+ imports: Versions,
11
+ name: Schema.String,
12
+ packageManager: Schema.String,
13
+ private: Schema.Boolean,
14
+ scripts: Versions,
15
+ type: Schema.String,
16
+ version: Schema.String,
17
+ });
18
+ const decodeManifest = Schema.decodeUnknownSync(Schema.fromJsonString(Manifest), { propertyOrder: "original" });
19
+ export function registryDependencies(dependencies, versions) {
20
+ return Object.fromEntries(Object.entries(dependencies).map(([name, version]) => {
21
+ if (!version.startsWith("workspace:")) {
22
+ return [name, version];
23
+ }
24
+ const published = versions[name];
25
+ if (published === undefined || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/u.test(published)) {
26
+ throw new CreateAppError({ message: `${name}: name an exact published version in published-versions.json` });
27
+ }
28
+ return [name, published];
29
+ }));
30
+ }
31
+ export function prepareManifest(source, versions) {
32
+ const manifest = decodeManifest(source);
33
+ const { "check:published": _check, ...scripts } = manifest.scripts;
34
+ return `${JSON.stringify({
35
+ ...manifest,
36
+ dependencies: registryDependencies(manifest.dependencies, versions),
37
+ devDependencies: registryDependencies(manifest.devDependencies, versions),
38
+ scripts: Object.fromEntries(Object.entries(scripts).map(([name, script]) => [name, script.replaceAll("--conditions=source ", "")])),
39
+ }, null, "\t")}\n`;
40
+ }
41
+ export function prepareFile(path, content, versions) {
42
+ if (path === "package.json") {
43
+ return prepareManifest(content, versions);
44
+ }
45
+ if (path === "biome.json") {
46
+ return content.replace('"root": false', '"root": true');
47
+ }
48
+ if (["tsconfig.json", "vite.config.ts", "vitest.config.ts"].includes(path)) {
49
+ return content.replaceAll('"source", ', "").replaceAll('["source"]', "[]");
50
+ }
51
+ return content;
52
+ }
53
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ # One command to a running app
2
+
3
+ New applications should start with the same small, working example that Platform maintains and tests. A newcomer should not need to understand workspace dependency protocols or repository-specific configuration before seeing their application run.
4
+
5
+ The command writes only into an absent or empty folder, names the application, installs its dependencies and prints the environment and development steps. The starter owns the application's files and its published-version manifest; packaging takes a snapshot of that source. Generated files are package artifacts, never a second checked-in template.
6
+
7
+ Preserving existing user files takes precedence over convenience. Real generated-app checks against npm and PostgreSQL take precedence over fast scaffolding tests. Keep the interface to the folder argument and normal CLI help and version output. Application choices, environment secrets, deployment and releases belong to the application and maintainer.
@@ -0,0 +1,16 @@
1
+ # Roadmap
2
+
3
+ ## Built
4
+
5
+ - A folder argument creates and installs the starter with exact published Platform versions.
6
+ - Packing snapshots `examples/starter` without local dependencies, build output, lockfiles or environment secrets.
7
+ - Existing nonempty folders are refused before any write or install.
8
+ - The published-starter check runs the packed command's output through typecheck, build and PostgreSQL/authenticated HTTP tests.
9
+
10
+ ## Next
11
+
12
+ - The maintainer selects and authorizes the first release beyond the npm placeholder.
13
+
14
+ ## Open questions
15
+
16
+ No additional generator options are selected.
package/package.json CHANGED
@@ -1,6 +1,63 @@
1
1
  {
2
2
  "name": "@shivaedev/create-app",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0",
4
+ "description": "Create and install a Platform application from the maintained starter",
5
+ "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/create-app#readme",
6
+ "bugs": {
7
+ "url": "https://github.com/ShivaeDev/platform/issues"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/ShivaeDev/platform.git",
12
+ "directory": "packages/create-app"
13
+ },
14
+ "license": "MIT",
15
+ "sideEffects": false,
16
+ "type": "module",
17
+ "imports": {
18
+ "#*.ts": {
19
+ "source": "./src/*.ts",
20
+ "types": "./dist/*.d.ts",
21
+ "default": "./dist/*.js"
22
+ },
23
+ "#test/*": "./src/test-support/*"
24
+ },
25
+ "exports": {
26
+ "./package.json": "./package.json"
27
+ },
28
+ "bin": {
29
+ "create-app": "./dist/cli.js"
30
+ },
31
+ "files": [
32
+ "dist",
33
+ "src",
34
+ "!src/**/*.test.ts",
35
+ "!src/**/test-support",
36
+ "template.json",
37
+ "docs",
38
+ "CHANGELOG.md",
39
+ "README.md"
40
+ ],
41
+ "devDependencies": {
42
+ "@effect/platform-node": "4.0.0-rc.112",
43
+ "@effect/platform-node-shared": "4.0.0-rc.112",
44
+ "@shivaedev/quality": "0.19.0",
45
+ "@types/node": "24.19.1",
46
+ "effect": "4.0.0-rc.112",
47
+ "esbuild": "0.28.2",
48
+ "typescript": "npm:@typescript/typescript6@6.0.2",
49
+ "vitest": "4.1.11"
50
+ },
51
+ "engines": {
52
+ "node": ">=24"
53
+ },
54
+ "publishConfig": {
55
+ "access": "public",
56
+ "provenance": true
57
+ },
58
+ "scripts": {
59
+ "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc --project tsconfig.build.json && esbuild src/cli.ts --bundle --platform=node --format=esm --conditions=source --target=node24 --minify --outfile=dist/cli.js && node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist/cli.js.map', { force: true }))\"",
60
+ "test": "vitest run",
61
+ "typecheck": "tsc --noEmit"
62
+ }
6
63
  }
@@ -0,0 +1,5 @@
1
+ import { Data, Runtime } from "effect";
2
+
3
+ export class CreateAppError extends Data.TaggedError("CreateAppError")<{ readonly message: string }> {
4
+ override readonly [Runtime.errorReported] = false;
5
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env node
2
+ import { fileURLToPath } from "node:url";
3
+ import * as NodeRuntime from "@effect/platform-node/NodeRuntime";
4
+ import * as NodeServices from "@effect/platform-node/NodeServices";
5
+ import { Console, Effect, FileSystem, Schema } from "effect";
6
+ import { Argument, Command } from "effect/unstable/cli";
7
+ import { createApp } from "./createApp.ts";
8
+
9
+ const program = Effect.gen(function* () {
10
+ const fs = yield* FileSystem.FileSystem;
11
+ const manifest = yield* fs.readFileString(fileURLToPath(new URL("../package.json", import.meta.url)));
12
+ const { version } = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Struct({ version: Schema.String })))(manifest);
13
+ const cli = Command.make("create-app", { folder: Argument.string("folder") }, ({ folder }) =>
14
+ createApp(folder, fileURLToPath(new URL("../template.json", import.meta.url))),
15
+ ).pipe(Command.withDescription("Create and install a Platform application from the maintained starter."));
16
+ yield* Command.run(cli, { version });
17
+ });
18
+
19
+ program.pipe(
20
+ Effect.tapErrorTag("CreateAppError", (error) => Console.error(`create-app: ${error.message}`)),
21
+ Effect.provide(NodeServices.layer),
22
+ NodeRuntime.runMain,
23
+ );
@@ -0,0 +1,41 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { basename, dirname, join, resolve } from "node:path";
3
+ import { Console, Effect, FileSystem, Schema } from "effect";
4
+ import { ChildProcess, ChildProcessSpawner } from "effect/unstable/process";
5
+ import { CreateAppError } from "./CreateAppError.ts";
6
+ import { Template } from "./template.ts";
7
+
8
+ const decodeName = Schema.decodeUnknownEffect(Schema.fromJsonString(Schema.Record(Schema.String, Schema.Unknown)));
9
+
10
+ export const createApp = Effect.fn("CreateApp.createApp")(function* (folder: string, templatePath: string) {
11
+ const fs = yield* FileSystem.FileSystem;
12
+ const target = resolve(folder);
13
+ const name = basename(target);
14
+ if (!/^[a-z0-9][a-z0-9._-]*$/u.test(name) || name.length > 214 || ["node_modules", "favicon.ico"].includes(name)) {
15
+ return yield* new CreateAppError({ message: "The final folder name must be a lowercase npm package name." });
16
+ }
17
+ if ((yield* fs.exists(target)) && ((yield* fs.stat(target)).type !== "Directory" || (yield* fs.readDirectory(target)).length > 0)) {
18
+ return yield* new CreateAppError({ message: `Refusing to write into ${folder}: the destination must be an empty folder.` });
19
+ }
20
+ const template = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Template))(yield* fs.readFileString(templatePath));
21
+ yield* fs.makeDirectory(target, { recursive: true });
22
+ for (const entry of template) {
23
+ const path = join(target, entry.path);
24
+ let content = Buffer.from(entry.content, "base64");
25
+ if (entry.path === "package.json") {
26
+ const manifest = yield* decodeName(content.toString());
27
+ content = Buffer.from(`${JSON.stringify({ ...manifest, name }, null, "\t")}\n`);
28
+ }
29
+ yield* fs.makeDirectory(dirname(path), { recursive: true });
30
+ yield* fs.writeFile(path, content);
31
+ }
32
+ const spawner = yield* ChildProcessSpawner.ChildProcessSpawner;
33
+ const code = yield* spawner.exitCode(ChildProcess.make("pnpm", ["install"], { cwd: target, stderr: "inherit", stdout: "inherit" }));
34
+ if (code !== 0) {
35
+ return yield* new CreateAppError({ message: `pnpm install failed (exit ${code}). Run pnpm install in ${folder} to retry.` });
36
+ }
37
+ const quotedFolder = `'${folder.replaceAll("'", "'\"'\"'")}'`;
38
+ yield* Console.log(
39
+ `Created ${folder}.\n\nNext steps:\n cd ${quotedFolder}\n cp .env.example .env.local\n Set DATABASE_URL to your PostgreSQL database.\n Set BETTER_AUTH_SECRET to a random secret of at least 32 characters.\n pnpm dev`,
40
+ );
41
+ });
package/src/pack.ts ADDED
@@ -0,0 +1,11 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
3
+ import { Effect } from "effect";
4
+ import { packTemplate } from "./packTemplate.ts";
5
+
6
+ NodeRuntime.runMain(
7
+ packTemplate(
8
+ fileURLToPath(new URL("../../../examples/starter", import.meta.url)),
9
+ fileURLToPath(new URL("../template.json", import.meta.url)),
10
+ ).pipe(Effect.provide(NodeServices.layer)),
11
+ );
@@ -0,0 +1,42 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { basename, join } from "node:path";
3
+ import { Effect, FileSystem, type PlatformError, Schema } from "effect";
4
+ import { CreateAppError } from "./CreateAppError.ts";
5
+ import { prepareFile, type Template, Versions } from "./template.ts";
6
+
7
+ const excluded = new Set(["node_modules", "dist", "coverage", "pnpm-lock.yaml", ".git"]);
8
+ function included(path: string) {
9
+ return !excluded.has(basename(path)) && (!basename(path).startsWith(".env") || basename(path) === ".env.example");
10
+ }
11
+
12
+ export const packTemplate = Effect.fn("CreateApp.packTemplate")(function* (starter: string, output: string) {
13
+ const fs = yield* FileSystem.FileSystem;
14
+ const versions = yield* Schema.decodeUnknownEffect(Schema.fromJsonString(Versions))(
15
+ yield* fs.readFileString(join(starter, "published-versions.json")),
16
+ );
17
+ const entries: Template[number][] = [];
18
+ const visit = Effect.fn("CreateApp.visit")(function* (directory: string): Effect.fn.Return<void, PlatformError.PlatformError | CreateAppError> {
19
+ for (const name of (yield* fs.readDirectory(join(starter, directory))).sort()) {
20
+ const path = join(directory, name);
21
+ if (!included(path)) {
22
+ continue;
23
+ }
24
+ if ((yield* fs.stat(join(starter, path))).type === "Directory") {
25
+ yield* visit(path);
26
+ } else {
27
+ const target = path === "pnpm-workspace.copy.yaml" ? "pnpm-workspace.yaml" : path;
28
+ const source = Buffer.from(yield* fs.readFile(join(starter, path)));
29
+ const content = yield* Effect.try({
30
+ catch: (cause) => new CreateAppError({ message: String(cause) }),
31
+ try: () =>
32
+ ["biome.json", "package.json", "tsconfig.json", "vite.config.ts", "vitest.config.ts"].includes(path)
33
+ ? Buffer.from(prepareFile(path, source.toString(), versions))
34
+ : source,
35
+ });
36
+ entries.push({ content: content.toString("base64"), path: target });
37
+ }
38
+ }
39
+ });
40
+ yield* visit("");
41
+ yield* fs.writeFileString(output, `${JSON.stringify(entries)}\n`);
42
+ });
@@ -0,0 +1,63 @@
1
+ import { Schema } from "effect";
2
+ import { CreateAppError } from "./CreateAppError.ts";
3
+
4
+ export const Versions = Schema.Record(Schema.String, Schema.String);
5
+ export const Template = Schema.Array(Schema.Struct({ content: Schema.String, path: Schema.String }));
6
+ export type Template = typeof Template.Type;
7
+ export const decodeTemplate = Schema.decodeUnknownSync(Schema.fromJsonString(Template));
8
+ const Manifest = Schema.Struct({
9
+ dependencies: Versions,
10
+ devDependencies: Versions,
11
+ engines: Versions,
12
+ imports: Versions,
13
+ name: Schema.String,
14
+ packageManager: Schema.String,
15
+ private: Schema.Boolean,
16
+ scripts: Versions,
17
+ type: Schema.String,
18
+ version: Schema.String,
19
+ });
20
+ const decodeManifest = Schema.decodeUnknownSync(Schema.fromJsonString(Manifest), { propertyOrder: "original" });
21
+
22
+ export function registryDependencies(dependencies: Readonly<Record<string, string>>, versions: Readonly<Record<string, string>>) {
23
+ return Object.fromEntries(
24
+ Object.entries(dependencies).map(([name, version]) => {
25
+ if (!version.startsWith("workspace:")) {
26
+ return [name, version];
27
+ }
28
+ const published = versions[name];
29
+ if (published === undefined || !/^\d+\.\d+\.\d+(?:-[\w.-]+)?$/u.test(published)) {
30
+ throw new CreateAppError({ message: `${name}: name an exact published version in published-versions.json` });
31
+ }
32
+ return [name, published];
33
+ }),
34
+ );
35
+ }
36
+
37
+ export function prepareManifest(source: string, versions: Readonly<Record<string, string>>) {
38
+ const manifest = decodeManifest(source);
39
+ const { "check:published": _check, ...scripts } = manifest.scripts;
40
+ return `${JSON.stringify(
41
+ {
42
+ ...manifest,
43
+ dependencies: registryDependencies(manifest.dependencies, versions),
44
+ devDependencies: registryDependencies(manifest.devDependencies, versions),
45
+ scripts: Object.fromEntries(Object.entries(scripts).map(([name, script]) => [name, script.replaceAll("--conditions=source ", "")])),
46
+ },
47
+ null,
48
+ "\t",
49
+ )}\n`;
50
+ }
51
+
52
+ export function prepareFile(path: string, content: string, versions: Readonly<Record<string, string>>) {
53
+ if (path === "package.json") {
54
+ return prepareManifest(content, versions);
55
+ }
56
+ if (path === "biome.json") {
57
+ return content.replace('"root": false', '"root": true');
58
+ }
59
+ if (["tsconfig.json", "vite.config.ts", "vitest.config.ts"].includes(path)) {
60
+ return content.replaceAll('"source", ', "").replaceAll('["source"]', "[]");
61
+ }
62
+ return content;
63
+ }