@bleedingdev/modern-js-create 3.2.0-ultramodern.84 → 3.2.0-ultramodern.86

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/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { execFileSync } from "node:child_process";
1
2
  import node_crypto from "node:crypto";
2
3
  import node_fs from "node:fs";
3
4
  import node_path from "node:path";
@@ -1010,7 +1011,7 @@ function createRootPackageJson(scope, packageSource, remotes = []) {
1010
1011
  'ultramodern:assert-mf-types': "node ./scripts/assert-mf-types.mjs",
1011
1012
  'ultramodern:check': "node ./scripts/validate-ultramodern-workspace.mjs",
1012
1013
  'ultramodern:i18n-boundaries': "node ./scripts/check-ultramodern-i18n-boundaries.mjs",
1013
- postinstall: "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs && node ./scripts/setup-agent-reference-repos.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)",
1014
+ postinstall: "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs && node ./scripts/setup-agent-reference-repos.mjs",
1014
1015
  check: 'pnpm format:check && pnpm lint && pnpm typecheck && pnpm skills:check && pnpm ultramodern:i18n-boundaries && pnpm ultramodern:check'
1015
1016
  },
1016
1017
  engines: {
@@ -4876,7 +4877,7 @@ assert(rootPackage.scripts?.['cloudflare:deploy'] === expectedCloudflareDeploySc
4876
4877
  assert(rootPackage.scripts?.['cloudflare:proof'] === 'node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json', 'Root must expose cloudflare:proof');
4877
4878
  assert(rootPackage.scripts?.['skills:install'] === 'node ./scripts/bootstrap-agent-skills.mjs', 'Root must expose skills:install');
4878
4879
  assert(rootPackage.scripts?.['skills:check'] === 'node ./scripts/bootstrap-agent-skills.mjs --check', 'Root must expose skills:check');
4879
- assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs && node ./scripts/setup-agent-reference-repos.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)", 'Root postinstall must format, bootstrap agent skills, install reference repositories, and enable hooks last');
4880
+ assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs && node ./scripts/setup-agent-reference-repos.mjs", 'Root postinstall must format, bootstrap agent skills, initialize git/hooks, and install reference repositories');
4880
4881
 
4881
4882
  const expectedAppIds = ['shell-super-app', ...fullStackVerticals.map(vertical => vertical.id)];
4882
4883
  const expectedCloudflareCompatibilityFlags = ['nodejs_compat', 'global_fetch_strictly_public'];
@@ -6236,6 +6237,91 @@ function writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorksp
6236
6237
  });
6237
6238
  node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol), null, 2)}\n`);
6238
6239
  }
6240
+ function runSetupCommand(command, args, options = {}) {
6241
+ return execFileSync(command, args, {
6242
+ cwd: options.cwd,
6243
+ encoding: 'utf-8',
6244
+ stdio: options.stdio ?? [
6245
+ 'ignore',
6246
+ 'pipe',
6247
+ 'pipe'
6248
+ ]
6249
+ });
6250
+ }
6251
+ function commandExists(command) {
6252
+ try {
6253
+ runSetupCommand(command, [
6254
+ '--version'
6255
+ ], {
6256
+ stdio: 'ignore'
6257
+ });
6258
+ return true;
6259
+ } catch {
6260
+ return false;
6261
+ }
6262
+ }
6263
+ function installGitForGeneratedProject() {
6264
+ if (commandExists('git')) return;
6265
+ const runShell = (script)=>runSetupCommand('sh', [
6266
+ '-lc',
6267
+ script
6268
+ ], {
6269
+ stdio: 'inherit'
6270
+ });
6271
+ const sudo = 'function' == typeof process.getuid && 0 === process.getuid() ? '' : 'sudo ';
6272
+ if (commandExists('brew')) runSetupCommand('brew', [
6273
+ 'install',
6274
+ 'git'
6275
+ ], {
6276
+ stdio: 'inherit'
6277
+ });
6278
+ else if ('linux' === process.platform && commandExists('apt-get')) runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
6279
+ else if ('linux' === process.platform && commandExists('dnf')) runShell(`${sudo}dnf install -y git`);
6280
+ else if ('linux' === process.platform && commandExists('yum')) runShell(`${sudo}yum install -y git`);
6281
+ else if ('linux' === process.platform && commandExists('apk')) runShell('apk add --no-cache git');
6282
+ if (!commandExists('git')) throw new Error('Git is required for UltraModern setup. Install git and rerun create, or run pnpm skills:install after installing git.');
6283
+ }
6284
+ function isInsideGitWorkTree(targetDir) {
6285
+ try {
6286
+ return 'true' === runSetupCommand('git', [
6287
+ 'rev-parse',
6288
+ '--is-inside-work-tree'
6289
+ ], {
6290
+ cwd: targetDir
6291
+ }).trim();
6292
+ } catch {
6293
+ return false;
6294
+ }
6295
+ }
6296
+ function initializeGeneratedGitRepository(targetDir) {
6297
+ installGitForGeneratedProject();
6298
+ if (isInsideGitWorkTree(targetDir)) return;
6299
+ try {
6300
+ runSetupCommand('git', [
6301
+ 'init',
6302
+ '-b',
6303
+ 'main'
6304
+ ], {
6305
+ cwd: targetDir,
6306
+ stdio: 'inherit'
6307
+ });
6308
+ } catch {
6309
+ runSetupCommand('git', [
6310
+ 'init'
6311
+ ], {
6312
+ cwd: targetDir,
6313
+ stdio: 'inherit'
6314
+ });
6315
+ runSetupCommand('git', [
6316
+ 'branch',
6317
+ '-M',
6318
+ 'main'
6319
+ ], {
6320
+ cwd: targetDir,
6321
+ stdio: 'inherit'
6322
+ });
6323
+ }
6324
+ }
6239
6325
  function isDirectoryEmpty(dirPath) {
6240
6326
  if (!node_fs.existsSync(dirPath)) return false;
6241
6327
  try {
@@ -6361,6 +6447,7 @@ async function main() {
6361
6447
  enableTailwind: detectTailwindFlag(),
6362
6448
  packageSource: detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage)
6363
6449
  });
6450
+ initializeGeneratedGitRepository(targetDir);
6364
6451
  const dim = '\x1b[2m\x1b[3m';
6365
6452
  const reset = '\x1b[0m';
6366
6453
  console.log(`${i18n.t(localeKeys.message.success)}\n`);
@@ -6445,6 +6532,7 @@ async function main() {
6445
6532
  node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
6446
6533
  writeTemplateManifestEvidence(targetDir, templateManifest);
6447
6534
  writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorkspaceProtocol);
6535
+ if (!isSubproject) initializeGeneratedGitRepository(targetDir);
6448
6536
  const dim = '\x1b[2m\x1b[3m';
6449
6537
  const reset = '\x1b[0m';
6450
6538
  console.log(`${i18n.t(localeKeys.message.success)}\n`);
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.84",
24
+ "version": "3.2.0-ultramodern.86",
25
25
  "types": "./dist/types/index.d.ts",
26
26
  "main": "./dist/index.js",
27
27
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "@types/node": "^25.9.1",
42
42
  "@typescript/native-preview": "7.0.0-dev.20260527.2",
43
43
  "tsx": "^4.22.3",
44
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.84"
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.86"
45
45
  },
46
46
  "publishConfig": {
47
47
  "registry": "https://registry.npmjs.org/",
@@ -54,6 +54,6 @@
54
54
  "start": "node ./dist/index.js"
55
55
  },
56
56
  "ultramodern": {
57
- "frameworkVersion": "3.2.0-ultramodern.84"
57
+ "frameworkVersion": "3.2.0-ultramodern.86"
58
58
  }
59
59
  }
@@ -4,18 +4,29 @@
4
4
  Layer,
5
5
  defineEffectBff,
6
6
  } from '@modern-js/plugin-bff/effect-server';
7
+ import type { HttpApi, HttpApiGroup } from '@modern-js/plugin-bff/effect-server';
7
8
  import { bffEffectApi } from '../../shared/effect/api';
8
9
 
9
- const greetingsLayer = HttpApiBuilder.group(bffEffectApi, 'greetings', (handlers) =>
10
- handlers.handle('hello', () =>
11
- Effect.succeed({
12
- message: 'Hello from Effect HttpApi',
13
- runtime: 'effect' as const,
14
- }),
15
- ),
10
+ type ApiGroups<TApi> = TApi extends HttpApi.HttpApi<string, infer TGroups> ? TGroups : never;
11
+ type GreetingsHandlers = HttpApiBuilder.Handlers.FromGroup<
12
+ HttpApiGroup.WithName<ApiGroups<typeof bffEffectApi>, 'greetings'>
13
+ >;
14
+
15
+ const greetingsLayer = HttpApiBuilder.group(
16
+ bffEffectApi,
17
+ 'greetings',
18
+ (handlers: GreetingsHandlers) =>
19
+ handlers.handle('hello', () =>
20
+ Effect.succeed({
21
+ message: 'Hello from Effect HttpApi',
22
+ runtime: 'effect' as const,
23
+ }),
24
+ ),
16
25
  );
17
26
 
18
- const layer = HttpApiBuilder.layer(bffEffectApi).pipe(Layer.provide(greetingsLayer));
27
+ const layer = HttpApiBuilder.layer(bffEffectApi).pipe(
28
+ Layer.provide(greetingsLayer),
29
+ );
19
30
 
20
31
  export default defineEffectBff({
21
32
  api: bffEffectApi,
@@ -96,15 +96,34 @@ const removeTree = (dir) =>
96
96
  });
97
97
 
98
98
  const cloneSource = (source, targetDir) => {
99
+ if (source.commit) {
100
+ run('git', ['init', targetDir]);
101
+ run('git', ['remote', 'add', 'origin', source.repository], {
102
+ cwd: targetDir,
103
+ });
104
+ run('git', ['fetch', '--depth', '1', '--quiet', 'origin', source.commit], {
105
+ cwd: targetDir,
106
+ });
107
+ run(
108
+ 'git',
109
+ [
110
+ '-c',
111
+ 'advice.detachedHead=false',
112
+ 'checkout',
113
+ '--detach',
114
+ '--quiet',
115
+ 'FETCH_HEAD',
116
+ ],
117
+ { cwd: targetDir },
118
+ );
119
+ return;
120
+ }
121
+
99
122
  const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
100
123
  try {
101
- run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1'], {
102
- stdio: 'inherit',
103
- });
124
+ run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1', '--quiet']);
104
125
  } catch {
105
- run('git', ['clone', '--depth', '1', source.repository, targetDir], {
106
- stdio: 'inherit',
107
- });
126
+ run('git', ['clone', '--depth', '1', '--quiet', source.repository, targetDir]);
108
127
  }
109
128
  };
110
129
 
@@ -7,6 +7,9 @@ import { useEffect, useState } from 'react';
7
7
  {{/if}}
8
8
  import { useTranslation } from 'react-i18next';
9
9
 
10
+ {{#if useEffectBff}}type GreetingResponse = Awaited<ReturnType<typeof effectBff.client.greetings.hello>>;
11
+
12
+ {{/if}}
10
13
  const fallbackLanguage = 'en';
11
14
  const supportedLanguages = ['en', 'cs'] as const;
12
15
  type SupportedLanguage = (typeof supportedLanguages)[number];
@@ -62,7 +65,7 @@ const Index = () => {
62
65
  let mounted = true;
63
66
  Effect.runFork(
64
67
  Effect.promise(() => effectBff.client.greetings.hello({})).pipe(
65
- Effect.tap((data) =>
68
+ Effect.tap((data: GreetingResponse) =>
66
69
  Effect.sync(() => {
67
70
  if (mounted) {
68
71
  setEffectMessage(data.message);
@@ -11,7 +11,7 @@ instructions, not optional reading.
11
11
  - `pnpm typecheck` runs effect-tsgo as the TypeScript checker.
12
12
  - `pnpm check` runs formatting, linting, effect-tsgo, private-skill availability checks, and the generated workspace contract.
13
13
  - Generated Codex stop hooks and subagent-stop hooks run `pnpm format && pnpm lint:fix && pnpm check`.
14
- - `postinstall` formats the generated tree, installs agent skills and reference repos, then installs `lefthook` when the workspace is inside a Git worktree. Generated `lefthook.yml` runs `pnpm format && pnpm lint:fix && pnpm check` on pre-commit; pre-push runs `pnpm check`.
14
+ - `postinstall` formats the generated tree, initializes Git when needed, installs agent skills and reference repos, then installs `lefthook`. Generated `lefthook.yml` runs `pnpm format && pnpm lint:fix && pnpm check` on pre-commit; pre-push runs `pnpm check`.
15
15
 
16
16
  ## Localized Routes
17
17
 
@@ -17,6 +17,85 @@ const run = (command, args, options = {}) =>
17
17
  stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
18
18
  });
19
19
 
20
+ const commandExists = command => {
21
+ try {
22
+ run(command, ['--version'], { stdio: 'ignore' });
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ };
28
+
29
+ const runShell = script =>
30
+ run('sh', ['-lc', script], {
31
+ stdio: 'inherit',
32
+ });
33
+
34
+ const installGit = () => {
35
+ if (commandExists('git')) {
36
+ return;
37
+ }
38
+
39
+ if (commandExists('brew')) {
40
+ run('brew', ['install', 'git'], { stdio: 'inherit' });
41
+ } else if (process.platform === 'linux' && commandExists('apt-get')) {
42
+ const sudo =
43
+ typeof process.getuid === 'function' && process.getuid() === 0
44
+ ? ''
45
+ : 'sudo ';
46
+ runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
47
+ } else if (process.platform === 'linux' && commandExists('dnf')) {
48
+ const sudo =
49
+ typeof process.getuid === 'function' && process.getuid() === 0
50
+ ? ''
51
+ : 'sudo ';
52
+ runShell(`${sudo}dnf install -y git`);
53
+ } else if (process.platform === 'linux' && commandExists('yum')) {
54
+ const sudo =
55
+ typeof process.getuid === 'function' && process.getuid() === 0
56
+ ? ''
57
+ : 'sudo ';
58
+ runShell(`${sudo}yum install -y git`);
59
+ } else if (process.platform === 'linux' && commandExists('apk')) {
60
+ runShell('apk add --no-cache git');
61
+ }
62
+
63
+ if (!commandExists('git')) {
64
+ throw new Error(
65
+ 'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
66
+ );
67
+ }
68
+ };
69
+
70
+ const isInsideGitWorkTree = () => {
71
+ try {
72
+ return run('git', ['rev-parse', '--is-inside-work-tree']).trim() === 'true';
73
+ } catch {
74
+ return false;
75
+ }
76
+ };
77
+
78
+ const initializeGitRepository = () => {
79
+ if (isInsideGitWorkTree()) {
80
+ return;
81
+ }
82
+
83
+ try {
84
+ run('git', ['init', '-b', 'main'], { stdio: 'inherit' });
85
+ } catch {
86
+ run('git', ['init'], { stdio: 'inherit' });
87
+ run('git', ['branch', '-M', 'main'], { stdio: 'inherit' });
88
+ }
89
+ };
90
+
91
+ const installLefthook = () => {
92
+ try {
93
+ run('lefthook', ['install'], { stdio: 'inherit' });
94
+ } catch (error) {
95
+ console.warn(`Unable to install lefthook hooks: ${error.message}`);
96
+ }
97
+ };
98
+
20
99
  const removeTree = dir =>
21
100
  fs.rmSync(dir, {
22
101
  force: true,
@@ -26,32 +105,50 @@ const removeTree = dir =>
26
105
  });
27
106
 
28
107
  const cloneSource = (source, targetDir) => {
29
- const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
30
- try {
31
- run('gh', ['repo', 'clone', repo, targetDir, '--', '--depth', '1'], {
32
- stdio: 'inherit',
108
+ if (source.commit) {
109
+ run('git', ['init', targetDir]);
110
+ run('git', ['remote', 'add', 'origin', source.repository], {
111
+ cwd: targetDir,
33
112
  });
34
- } catch {
35
- run('git', ['clone', '--depth', '1', source.repository, targetDir], {
36
- stdio: 'inherit',
113
+ run('git', ['fetch', '--depth', '1', '--quiet', 'origin', source.commit], {
114
+ cwd: targetDir,
37
115
  });
116
+ run(
117
+ 'git',
118
+ [
119
+ '-c',
120
+ 'advice.detachedHead=false',
121
+ 'checkout',
122
+ '--detach',
123
+ '--quiet',
124
+ 'FETCH_HEAD',
125
+ ],
126
+ { cwd: targetDir },
127
+ );
128
+ return;
38
129
  }
39
- if (source.commit) {
40
- try {
41
- run('git', ['checkout', source.commit], {
42
- cwd: targetDir,
43
- stdio: 'inherit',
44
- });
45
- } catch {
46
- run('git', ['fetch', '--depth', '1', 'origin', source.commit], {
47
- cwd: targetDir,
48
- stdio: 'inherit',
49
- });
50
- run('git', ['checkout', source.commit], {
51
- cwd: targetDir,
52
- stdio: 'inherit',
53
- });
54
- }
130
+
131
+ const repo = source.repository.replace(/^https:\/\/github.com\//u, '');
132
+ try {
133
+ run('gh', [
134
+ 'repo',
135
+ 'clone',
136
+ repo,
137
+ targetDir,
138
+ '--',
139
+ '--depth',
140
+ '1',
141
+ '--quiet',
142
+ ]);
143
+ } catch {
144
+ run('git', [
145
+ 'clone',
146
+ '--depth',
147
+ '1',
148
+ '--quiet',
149
+ source.repository,
150
+ targetDir,
151
+ ]);
55
152
  }
56
153
  };
57
154
 
@@ -124,6 +221,8 @@ if (checkOnly) {
124
221
  }
125
222
 
126
223
  fs.mkdirSync(installDir, { recursive: true });
224
+ installGit();
225
+ initializeGitRepository();
127
226
 
128
227
  for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
129
228
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
@@ -161,3 +260,5 @@ for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
161
260
  removeTree(tempDir);
162
261
  }
163
262
  }
263
+
264
+ installLefthook();