create-foldkit-app 0.19.0 → 0.19.1

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.
@@ -1,21 +1,24 @@
1
- import { Array, Effect, Match, Record, Schema, pipe } from 'effect';
1
+ import { Array, Effect, FileSystem, Match, Order, Path, Record, Result, Schema, pipe, } from 'effect';
2
2
  import { HttpClient, HttpClientRequest } from 'effect/unstable/http';
3
3
  import { spawn } from 'node:child_process';
4
4
  const GITHUB_RAW_BASE_URL = 'https://raw.githubusercontent.com/foldkit/foldkit/main/examples';
5
+ const NPM_REGISTRY_BASE_URL = 'https://registry.npmjs.org';
6
+ const FOLDKIT_SCOPE_PREFIX = '@foldkit/';
5
7
  const isWindows = process.platform === 'win32';
6
- const getInstallArgs = (packageManager, isDev = false) => pipe(Match.value(packageManager), Match.when('npm', () => ['install']), Match.when('yarn', () => ['add']), Match.when('pnpm', () => ['add']), Match.when('bun', () => ['add']), Match.exhaustive, args => (isDev ? [...args, '-D'] : args));
7
8
  const StringRecord = Schema.Record(Schema.String, Schema.String);
8
9
  const PackageJson = Schema.Struct({
9
10
  dependencies: StringRecord.pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
10
11
  devDependencies: StringRecord.pipe(Schema.withDecodingDefaultKey(Effect.succeed({}))),
11
12
  });
12
- // NOTE: foldkit workspace packages an example may depend on that aren't
13
- // already installed at `latest` by the hardcoded commands below. They ship
14
- // from the same monorepo, so pin them to `latest` like `foldkit` itself.
15
- const PASS_THROUGH_WORKSPACE_PACKAGES = new Set([
16
- '@foldkit/ui',
17
- '@foldkit/devtools',
18
- ]);
13
+ const ProjectPackageJson = Schema.Struct({
14
+ name: Schema.String,
15
+ version: Schema.String,
16
+ type: Schema.String,
17
+ scripts: StringRecord,
18
+ });
19
+ const NpmPackument = Schema.Struct({
20
+ version: Schema.String,
21
+ });
19
22
  const TEMPLATE_DEV_DEPENDENCIES = [
20
23
  '@foldkit/vite-plugin',
21
24
  '@foldkit/devtools-mcp',
@@ -26,18 +29,73 @@ const TEMPLATE_DEV_DEPENDENCIES = [
26
29
  'prettier',
27
30
  'vitest',
28
31
  ];
29
- const formatDeps = (deps) => pipe(deps, Record.toEntries, Array.filter(([name, version]) => !version.includes('workspace:') ||
30
- PASS_THROUGH_WORKSPACE_PACKAGES.has(name)), Array.map(([name, version]) => version.includes('workspace:') ? `${name}@latest` : `${name}@${version}`));
31
- const fetchExampleDeps = (example) => Effect.gen(function* () {
32
+ const isFoldkitPackage = (name) => name === 'foldkit' || name.startsWith(FOLDKIT_SCOPE_PREFIX);
33
+ const Keep = (version) => ({ _tag: 'Keep', version });
34
+ const Latest = { _tag: 'Latest' };
35
+ const toUnresolvedSpec = (spec, name) => {
36
+ if (spec.includes('workspace:')) {
37
+ return isFoldkitPackage(name) ? Result.succeed(Latest) : Result.failVoid;
38
+ }
39
+ else {
40
+ return Result.succeed(Keep(spec));
41
+ }
42
+ };
43
+ /**
44
+ * Build the runtime dependency map for a scaffolded project from an example's
45
+ * raw `dependencies`. Concrete versions are kept, Foldkit monorepo packages are
46
+ * marked for latest-version resolution, and any other workspace packages (which
47
+ * are not published) are dropped.
48
+ */
49
+ export const buildUnresolvedDeps = (exampleDeps) => Record.filterMap(exampleDeps, toUnresolvedSpec);
50
+ /**
51
+ * Build the devDependency map for a scaffolded project by merging the always-on
52
+ * template tooling with the example's own `devDependencies`. A concrete version
53
+ * from the example wins over the template's latest marker for the same package.
54
+ */
55
+ export const buildUnresolvedDevDeps = (exampleDevDeps) => {
56
+ const templateSpecs = Record.fromIterableWith(TEMPLATE_DEV_DEPENDENCIES, name => [name, Latest]);
57
+ const exampleSpecs = Record.filterMap(exampleDevDeps, toUnresolvedSpec);
58
+ return Record.union(templateSpecs, exampleSpecs, (_templateSpec, exampleSpec) => exampleSpec);
59
+ };
60
+ const resolveLatestVersion = (name) => Effect.gen(function* () {
61
+ const client = yield* HttpClient.HttpClient;
62
+ const encodedName = name.replace('/', '%2F');
63
+ const url = `${NPM_REGISTRY_BASE_URL}/${encodedName}/latest`;
64
+ const response = yield* client.execute(HttpClientRequest.get(url));
65
+ const json = yield* response.json;
66
+ const packument = yield* Schema.decodeUnknownEffect(NpmPackument)(json);
67
+ return `^${packument.version}`;
68
+ });
69
+ const resolveEntry = (name, spec) => Match.value(spec).pipe(Match.tagsExhaustive({
70
+ Keep: ({ version }) => Effect.succeed([name, version]),
71
+ Latest: () => Effect.map(resolveLatestVersion(name), version => [name, version]),
72
+ }));
73
+ const resolveSpecs = (unresolved) => Effect.gen(function* () {
74
+ const entries = Record.toEntries(unresolved);
75
+ const resolved = yield* Effect.forEach(entries, ([name, spec]) => resolveEntry(name, spec), { concurrency: 'unbounded' });
76
+ return Record.fromEntries(resolved);
77
+ });
78
+ const byPackageName = Order.mapInput(Order.String, ([name]) => name);
79
+ const sortDependencies = (dependencies) => pipe(dependencies, Record.toEntries, Array.sort(byPackageName), Record.fromEntries);
80
+ const fetchExamplePackageJson = (example) => Effect.gen(function* () {
32
81
  const client = yield* HttpClient.HttpClient;
33
82
  const url = `${GITHUB_RAW_BASE_URL}/${example}/package.json`;
34
83
  const response = yield* client.execute(HttpClientRequest.get(url));
35
84
  const json = yield* response.json;
36
- const packageJson = yield* Schema.decodeUnknownEffect(PackageJson)(json);
37
- return {
38
- dependencies: formatDeps(packageJson.dependencies),
39
- devDependencies: formatDeps(packageJson.devDependencies),
85
+ return yield* Schema.decodeUnknownEffect(PackageJson)(json);
86
+ });
87
+ const writeManifest = (projectPath, dependencies, devDependencies) => Effect.gen(function* () {
88
+ const fs = yield* FileSystem.FileSystem;
89
+ const path = yield* Path.Path;
90
+ const packageJsonPath = path.join(projectPath, 'package.json');
91
+ const content = yield* fs.readFileString(packageJsonPath);
92
+ const packageJson = yield* Schema.decodeUnknownEffect(ProjectPackageJson)(JSON.parse(content));
93
+ const updated = {
94
+ ...packageJson,
95
+ dependencies,
96
+ devDependencies,
40
97
  };
98
+ yield* fs.writeFileString(packageJsonPath, `${JSON.stringify(updated, null, 2)}\n`);
41
99
  });
42
100
  const runCommand = (command, args, cwd) => Effect.callback((resume) => {
43
101
  const child = spawn(command, [...args], {
@@ -64,13 +122,9 @@ const runCommand = (command, args, cwd) => Effect.callback((resume) => {
64
122
  });
65
123
  });
66
124
  export const installDependencies = (projectPath, packageManager, example) => Effect.gen(function* () {
67
- const exampleDeps = yield* fetchExampleDeps(example);
68
- const installArgs = getInstallArgs(packageManager);
69
- yield* runCommand(packageManager, [...installArgs, 'foldkit', ...exampleDeps.dependencies], projectPath);
70
- const installDevArgs = getInstallArgs(packageManager, true);
71
- yield* runCommand(packageManager, [
72
- ...installDevArgs,
73
- ...TEMPLATE_DEV_DEPENDENCIES,
74
- ...exampleDeps.devDependencies,
75
- ], projectPath);
125
+ const examplePackageJson = yield* fetchExamplePackageJson(example);
126
+ const dependencies = yield* resolveSpecs(buildUnresolvedDeps(examplePackageJson.dependencies));
127
+ const devDependencies = yield* resolveSpecs(buildUnresolvedDevDeps(examplePackageJson.devDependencies));
128
+ yield* writeManifest(projectPath, sortDependencies(dependencies), sortDependencies(devDependencies));
129
+ yield* runCommand(packageManager, ['install'], projectPath);
76
130
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-foldkit-app",
3
- "version": "0.19.0",
3
+ "version": "0.19.1",
4
4
  "description": "Create Foldkit applications",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -20,7 +20,9 @@
20
20
  "typescript": "^6.0.3"
21
21
  },
22
22
  "devDependencies": {
23
- "@types/node": "^25.9.3"
23
+ "@effect/vitest": "4.0.0-beta.88",
24
+ "@types/node": "^25.9.3",
25
+ "vitest": "^4.1.9"
24
26
  },
25
27
  "keywords": [
26
28
  "create-foldkit-app",
@@ -47,6 +49,7 @@
47
49
  "clean": "rimraf dist *.tsbuildinfo",
48
50
  "build": "pnpm run clean && tsc -b",
49
51
  "format": "prettier -w .",
52
+ "test": "vitest run",
50
53
  "typecheck": "tsc -b --noEmit"
51
54
  }
52
55
  }