@yunsoft/yuncms 0.1.13 → 0.1.15

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/README.md CHANGED
@@ -15,7 +15,9 @@ npx --yes @yunsoft/yuncms start
15
15
 
16
16
  `init` creates the project `.env`, asks for MySQL connection details, applies the required database migrations and creates the first Administrator account.
17
17
 
18
- The current directory is used as the YunCMS project directory, including local Files and local `extensions/`.
18
+ The current directory is used as the YunCMS project directory. Initialization also creates an `uploads/` directory, a Plesk-compatible `start.js`, a 200-response health endpoint example under `extensions/health/`, and a minimal hook example under `extensions/example-hook/`. Existing project files are preserved and are not overwritten by the scaffold.
19
+
20
+ The generated health extension is available at `/extensions/health`. The core `/health` and `/ready` routes remain unchanged.
19
21
 
20
22
  ## Persistent project dependency
21
23
 
@@ -30,6 +32,12 @@ npx yuncms start
30
32
 
31
33
  Once installed locally, `npx yuncms ...` resolves the project's pinned package version.
32
34
 
35
+ For Plesk, install `@yunsoft/yuncms` locally and use the generated `start.js` as the application startup file. The same entry can be checked manually with:
36
+
37
+ ```bash
38
+ node start.js
39
+ ```
40
+
33
41
  Production maintenance commands:
34
42
 
35
43
  ```bash
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yunsoft/yuncms",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "Command-line setup and runtime launcher for YunCMS.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -30,7 +30,7 @@
30
30
  "test": "node --test"
31
31
  },
32
32
  "dependencies": {
33
- "@yunsoft/yuncms-api": "0.1.13",
34
- "@yunsoft/yuncms-core": "0.1.13"
33
+ "@yunsoft/yuncms-api": "0.1.15",
34
+ "@yunsoft/yuncms-core": "0.1.15"
35
35
  }
36
36
  }
@@ -14,6 +14,7 @@ import {
14
14
 
15
15
  import { writeEnvFile } from './env-file.js';
16
16
  import { createInteractivePrompts } from './prompts.js';
17
+ import { ensureProjectScaffold } from './project-scaffold.js';
17
18
 
18
19
  export async function collectEnvironment(prompts) {
19
20
  const DB_HOST = await prompts.line('MySQL host', { defaultValue: '127.0.0.1' });
@@ -29,6 +30,7 @@ export async function collectEnvironment(prompts) {
29
30
  PORT: String(DEFAULT_SERVER_PORT),
30
31
  STUDIO_ORIGIN: studioUrl,
31
32
  AUTH_PUBLIC_URL: studioUrl,
33
+ FILES_LOCAL_ROOT: 'uploads',
32
34
  DB_HOST,
33
35
  DB_PORT,
34
36
  DB_DATABASE,
@@ -61,6 +63,11 @@ export async function runInitCommand({
61
63
  } = {}) {
62
64
  const envPath = join(cwd, '.env');
63
65
  const workingEnv = { ...env };
66
+ const scaffold = await ensureProjectScaffold({ cwd });
67
+
68
+ if (scaffold.created.length > 0) {
69
+ output.log?.(`Created project scaffold: ${scaffold.created.join(', ')}`);
70
+ }
64
71
 
65
72
  if (!existsSync(envPath)) {
66
73
  output.log?.('YunCMS setup: database configuration');
@@ -22,6 +22,13 @@ function positiveInteger(value, code, label, { max = Number.MAX_SAFE_INTEGER } =
22
22
  return parsed;
23
23
  }
24
24
 
25
+ function resolveSpawnCommand(command, platform = process.platform) {
26
+ if (platform === 'win32' && (command === 'npm' || command === 'npx')) {
27
+ return `${command}.cmd`;
28
+ }
29
+ return command;
30
+ }
31
+
25
32
  export function resolveCommandTimeoutMs(env = process.env, explicitTimeoutMs = null) {
26
33
  const value = explicitTimeoutMs ?? env.YUNCMS_CLI_COMMAND_TIMEOUT_MS ?? DEFAULT_COMMAND_TIMEOUT_MS;
27
34
  return positiveInteger(
@@ -57,6 +64,7 @@ function spawnCapturedProcess(spawnProcess, command, args, options) {
57
64
  export async function runCapturedProcess(command, args = [], {
58
65
  cwd = process.cwd(),
59
66
  env = process.env,
67
+ platform = process.platform,
60
68
  spawnProcess = spawn,
61
69
  maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES,
62
70
  timeoutMs = null,
@@ -75,10 +83,11 @@ export async function runCapturedProcess(command, args = [], {
75
83
  'Command output limit',
76
84
  { max: 16 * 1024 * 1024 },
77
85
  );
86
+ const resolvedCommand = resolveSpawnCommand(command, platform);
78
87
 
79
88
  const child = spawnCapturedProcess(
80
89
  spawnProcess,
81
- command,
90
+ resolvedCommand,
82
91
  args,
83
92
  {
84
93
  cwd,
@@ -146,7 +155,7 @@ export async function runCapturedProcess(command, args = [], {
146
155
  return;
147
156
  }
148
157
  error.code ||= 'COMMAND_START_FAILED';
149
- error.command = command;
158
+ error.command = resolvedCommand;
150
159
  error.args = [...args];
151
160
  reject(error);
152
161
  });
@@ -0,0 +1,90 @@
1
+ import { access, mkdir, writeFile } from 'node:fs/promises';
2
+ import { constants as fsConstants } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+
5
+ const START_FILE = `// Plesk-compatible YunCMS startup file.\n// Install @yunsoft/yuncms locally, then configure Plesk to run: node start.js\nimport('@yunsoft/yuncms/src/runtime-entry.js').catch((error) => {\n console.error('YunCMS failed to start', error);\n process.exitCode = 1;\n});\n`;
6
+
7
+ const HEALTH_PACKAGE = `${JSON.stringify({
8
+ name: 'yuncms-extension-health',
9
+ private: true,
10
+ type: 'module',
11
+ yuncms: {
12
+ id: 'health',
13
+ type: 'endpoint',
14
+ entry: './index.js',
15
+ },
16
+ }, null, 2)}\n`;
17
+
18
+ const HEALTH_EXTENSION = `export default Object.freeze({\n __yuncms_extension__: true,\n type: 'endpoint',\n register(router) {\n router.get('/', (_req, res) => {\n res.status(200).json({ status: 'ok' });\n });\n },\n});\n`;
19
+
20
+ const HOOK_PACKAGE = `${JSON.stringify({
21
+ name: 'yuncms-extension-example-hook',
22
+ private: true,
23
+ type: 'module',
24
+ yuncms: {
25
+ id: 'example-hook',
26
+ type: 'hook',
27
+ entry: './index.js',
28
+ },
29
+ }, null, 2)}\n`;
30
+
31
+ const HOOK_EXTENSION = `export default Object.freeze({\n __yuncms_extension__: true,\n type: 'hook',\n register({ init }) {\n init('app.afterStart', ({ logger }) => {\n logger.info?.('YunCMS example hook: app.afterStart');\n });\n },\n});\n`;
32
+
33
+ async function pathExists(path) {
34
+ try {
35
+ await access(path, fsConstants.F_OK);
36
+ return true;
37
+ } catch {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ async function ensureDirectory(path, label, created) {
43
+ if (await pathExists(path)) return;
44
+ await mkdir(path, { recursive: true });
45
+ created.push(label);
46
+ }
47
+
48
+ async function writeIfMissing(path, content, label, created) {
49
+ if (await pathExists(path)) return;
50
+ await mkdir(dirname(path), { recursive: true });
51
+ await writeFile(path, content, 'utf8');
52
+ created.push(label);
53
+ }
54
+
55
+ export async function ensureProjectScaffold({ cwd = process.cwd() } = {}) {
56
+ const created = [];
57
+ const extensionsDir = join(cwd, 'extensions');
58
+ const uploadsDir = join(cwd, 'uploads');
59
+
60
+ await ensureDirectory(extensionsDir, 'extensions/', created);
61
+ await ensureDirectory(uploadsDir, 'uploads/', created);
62
+ await writeIfMissing(join(cwd, 'start.js'), START_FILE, 'start.js', created);
63
+
64
+ await writeIfMissing(
65
+ join(extensionsDir, 'health', 'package.json'),
66
+ HEALTH_PACKAGE,
67
+ 'extensions/health/package.json',
68
+ created,
69
+ );
70
+ await writeIfMissing(
71
+ join(extensionsDir, 'health', 'index.js'),
72
+ HEALTH_EXTENSION,
73
+ 'extensions/health/index.js',
74
+ created,
75
+ );
76
+ await writeIfMissing(
77
+ join(extensionsDir, 'example-hook', 'package.json'),
78
+ HOOK_PACKAGE,
79
+ 'extensions/example-hook/package.json',
80
+ created,
81
+ );
82
+ await writeIfMissing(
83
+ join(extensionsDir, 'example-hook', 'index.js'),
84
+ HOOK_EXTENSION,
85
+ 'extensions/example-hook/index.js',
86
+ created,
87
+ );
88
+
89
+ return Object.freeze({ created: Object.freeze(created) });
90
+ }
@@ -0,0 +1 @@
1
+ import '@yunsoft/yuncms-api/server';