@cityholdings/minicube-dev 0.1.1 → 0.1.3

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
@@ -36,8 +36,17 @@ npm publish
36
36
 
37
37
  Optional tools, used only by the commands that need them: `adb` (Android preview), Xcode
38
38
  command-line tools (`xcrun`, iOS simulator/device preview), `kubectl` (token administration or
39
- `transport: "kube"`), `@antdigital/cube` in card projects, `cube-pack` (inline card images), and the
40
- mini program compiler `mini-pkg-builder`.
39
+ `transport: "kube"`), `cube-pack` (inline card images), and the official mPaaS mini program IDE
40
+ (`mPaaS小程序开发者工具`).
41
+
42
+ Mini programs are compiled with the IDE's own `mini-pkg-builder` (and its `esbuild`), found
43
+ automatically in the default install locations: `/Applications` or `~/Applications` on macOS;
44
+ `%LOCALAPPDATA%\Programs`, `%ProgramFiles%` or the installer's recorded folder on Windows. No
45
+ configuration is needed; `builders.mini` / `MINICUBE_MINI_BUILDER` only override it.
46
+
47
+ Card projects must install ACT v4 themselves (`npm i -D @antdigital/cube@4`). minicube-dev uses only
48
+ that copy, found from the card directory upwards, and stops if it is missing or older than v4; a
49
+ global `act` (often ACT v2, which the apps cannot load) is never used.
41
50
 
42
51
  ## Configuration
43
52
 
@@ -59,16 +68,15 @@ by `MINICUBE_DEV_CONFIG` / `--config`. Create a skeleton with `minicube-dev conf
59
68
  "devices": {
60
69
  "android": { "package": "<android application id>", "cubeConnectActivity": "<debug activity accepting cubeActHost/cubeActPort>" },
61
70
  "ios": { "bundleId": "<bundle id, physical devices only>" }
62
- },
63
- "builders": { "mini": "/path/to/mini-pkg-builder" }
71
+ }
64
72
  }
65
73
  ```
66
74
 
67
75
  * `token` or `tokenEnv` (name of an environment variable holding it) per environment.
68
76
  * Without `baseUrl` (or with `"transport": "kube"`) requests go through `kubectl port-forward`
69
77
  using your local kubeconfig.
70
- * `builders.mini` is the mini-pkg-builder executable (`mini-pkg-builder-macos-arm64`,
71
- `mini-pkg-builder-win-x64.exe`, …) or its runtime `index.js`; `MINICUBE_MINI_BUILDER` overrides it.
78
+ * Optional `"builders": { "mini": "<path>" }` (or `MINICUBE_MINI_BUILDER`) points at another
79
+ mini-pkg-builder executable or runtime `index.js`, e.g. when the IDE is installed elsewhere.
72
80
 
73
81
  `minicube-dev doctor` checks the toolchain and every environment's token.
74
82
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cityholdings/minicube-dev",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Command-line workflow for mPaaS mini programs, H5 offline packages and Cube cards: build, debug upload, device preview and release",
5
5
  "type": "module",
6
6
  "bin": {
package/src/act.js CHANGED
@@ -2,10 +2,10 @@ import { mkdirSync, openSync } from 'node:fs';
2
2
  import { createConnection } from 'node:net';
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
- import { cardDir, resolveAct } from './build.js';
5
+ import { cardDir, resolveAct, runAct } from './build.js';
6
6
  import { adb, androidSerial } from './device.js';
7
7
  import { CliError, log } from './log.js';
8
- import { delay, run, runSync, spawnDetached } from './proc.js';
8
+ import { delay, runSync, spawnDetached } from './proc.js';
9
9
 
10
10
  const DEFAULT_PORT = 9001;
11
11
  const CONNECT_TIMEOUT_MS = 45_000;
@@ -55,8 +55,7 @@ export function advertisedHost(target) {
55
55
  * address is restarted.
56
56
  */
57
57
  export async function ensureActServer(project, port = DEFAULT_PORT, host = null) {
58
- const act = resolveAct(project.root);
59
- if (!act) throw new CliError('ACT not found', 'Install it in the card project: npm i -D @antdigital/cube');
58
+ const act = resolveAct(cardDir(project), project.root);
60
59
  if (await isListening(port)) {
61
60
  const current = (await serverAddress(port)).host;
62
61
  if (!host || current === host) {
@@ -64,13 +63,13 @@ export async function ensureActServer(project, port = DEFAULT_PORT, host = null)
64
63
  return act;
65
64
  }
66
65
  log.detail(`Restarting the ACT server: it advertises ${current}, this device needs ${host}`);
67
- await run(act, ['prepare'], { cwd: project.root, capture: true });
66
+ await runAct(act, ['prepare'], { cwd: project.root, capture: true });
68
67
  const freed = Date.now() + 10_000;
69
68
  while ((await isListening(port)) && Date.now() < freed) await delay(300);
70
69
  }
71
70
  mkdirSync(path.join(project.root, '.minicube'), { recursive: true });
72
71
  const logFile = path.join(project.root, '.minicube', 'act-server.log');
73
- spawnDetached(act, ['server'], { cwd: project.root, logFd: openSync(logFile, 'a'), env: host ? { HOST: host } : undefined });
72
+ spawnDetached(process.execPath, [act.script, 'server'], { cwd: project.root, logFd: openSync(logFile, 'a'), env: host ? { HOST: host } : undefined });
74
73
  const deadline = Date.now() + 20_000;
75
74
  while (Date.now() < deadline) {
76
75
  await delay(500);
@@ -145,6 +144,6 @@ export async function attachDevice(target, env, devices, port = DEFAULT_PORT) {
145
144
  }
146
145
 
147
146
  export async function deliverCard(project, act) {
148
- const result = await run(act, ['preview', cardDir(project)], { cwd: project.root });
147
+ const result = await runAct(act, ['preview', cardDir(project)], { cwd: project.root });
149
148
  if (result.code !== 0) throw new CliError(`act preview failed (exit ${result.code})`);
150
149
  }
package/src/build.js CHANGED
@@ -3,7 +3,7 @@ import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, statSync, writeFil
3
3
  import os from 'node:os';
4
4
  import path from 'node:path';
5
5
  import { CliError, log } from './log.js';
6
- import { run, which } from './proc.js';
6
+ import { run, runSync, which } from './proc.js';
7
7
  import { listFiles, zipDirectory } from './zip.js';
8
8
 
9
9
  export const OUT_DIR = '.minicube';
@@ -39,19 +39,73 @@ function ensureGitIgnored(root) {
39
39
  const MINI_REQUIRED_OUTPUTS = ['appConfig.json', 'index.html', 'index.js', 'index.worker.js', 'manifest.json'];
40
40
  const MINI_SOURCE_EXCLUDES = new Set([OUT_DIR, '.git', '.idea', '.vscode', '.DS_Store']);
41
41
 
42
+ const IDE_NAME = 'mPaaS小程序开发者工具';
43
+
44
+ /** Where the official mPaaS mini program IDE keeps its compiler (`<app>/builder/`). */
45
+ export function ideBuilderDirs(platform = process.platform, env = process.env, home = os.homedir()) {
46
+ if (platform === 'darwin') {
47
+ return ['/Applications', path.join(home, 'Applications')].map((dir) =>
48
+ path.join(dir, `${IDE_NAME}.app`, 'Contents', 'Resources', 'app', 'builder'));
49
+ }
50
+ if (platform === 'win32') {
51
+ const roots = [
52
+ env.LOCALAPPDATA && path.win32.join(env.LOCALAPPDATA, 'Programs'),
53
+ env.ProgramFiles,
54
+ env['ProgramFiles(x86)'],
55
+ ].filter(Boolean);
56
+ return [...roots.map((root) => path.win32.join(root, IDE_NAME)), ...windowsInstallLocations()]
57
+ .map((dir) => path.win32.join(dir, 'resources', 'app', 'builder'));
58
+ }
59
+ return [];
60
+ }
61
+
62
+ /** Install folders of the IDE recorded by its Windows installer (covers custom locations). */
63
+ function windowsInstallLocations() {
64
+ const found = [];
65
+ for (const hive of ['HKCU', 'HKLM']) {
66
+ try {
67
+ const { code, stdout } = runSync('reg', ['query', `${hive}\\Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall`, '/s', '/f', IDE_NAME, '/d']);
68
+ if (code !== 0) continue;
69
+ for (const key of stdout.split(/\r?\n/).filter((line) => line.startsWith('HKEY_'))) {
70
+ const value = runSync('reg', ['query', key, '/v', 'InstallLocation']).stdout.match(/InstallLocation\s+REG_\w+\s+(.+)/);
71
+ if (value) found.push(value[1].trim().replace(/^"|"$/g, ''));
72
+ }
73
+ } catch {
74
+ // reg.exe unavailable: fall back to the default folders
75
+ }
76
+ }
77
+ return found;
78
+ }
79
+
80
+ /**
81
+ * The mini program compiler: MINICUBE_MINI_BUILDER, then builders.mini in the config, then the
82
+ * copy inside an installed mPaaS mini program IDE. `env` carries the IDE's esbuild, which the
83
+ * compiler (like the IDE) finds through ESBUILD_BINARY_PATH.
84
+ */
42
85
  export function resolveMiniBuilder(globalConfig) {
43
86
  const configured = process.env.MINICUBE_MINI_BUILDER ?? globalConfig.data.builders?.mini;
44
- if (!configured || String(configured).includes('<')) {
45
- throw new CliError(
46
- 'No mini program builder configured',
47
- 'Set builders.mini in the minicube-dev config to the mini-pkg-builder executable (or its runtime index.js), or export MINICUBE_MINI_BUILDER.',
48
- );
87
+ if (configured && !String(configured).includes('<')) {
88
+ const resolved = which(String(configured)) ?? path.resolve(String(configured));
89
+ if (!existsSync(resolved)) throw new CliError(`Mini program builder not found: ${resolved}`);
90
+ return builderAt(resolved);
49
91
  }
50
- const resolved = which(String(configured)) ?? path.resolve(String(configured));
51
- if (!existsSync(resolved)) throw new CliError(`Mini program builder not found: ${resolved}`);
52
- return resolved.endsWith('.js') || resolved.endsWith('.mjs')
53
- ? { command: process.execPath, prefix: [resolved] }
54
- : { command: resolved, prefix: [] };
92
+ const exe = process.platform === 'win32' ? '.exe' : '';
93
+ for (const dir of ideBuilderDirs()) {
94
+ const file = path.join(dir, `mini-pkg-builder${exe}`);
95
+ if (existsSync(file)) return builderAt(file);
96
+ }
97
+ throw new CliError(
98
+ 'No mini program builder found',
99
+ `Install the mPaaS mini program IDE (${IDE_NAME}), or set builders.mini in the minicube-dev config / MINICUBE_MINI_BUILDER to a mini-pkg-builder executable.`,
100
+ );
101
+ }
102
+
103
+ function builderAt(file) {
104
+ const esbuild = path.join(path.dirname(file), process.platform === 'win32' ? 'esbuild.exe' : 'esbuild');
105
+ const env = existsSync(esbuild) && !process.env.ESBUILD_BINARY_PATH ? { ESBUILD_BINARY_PATH: esbuild } : {};
106
+ return file.endsWith('.js') || file.endsWith('.mjs')
107
+ ? { command: process.execPath, prefix: [file], path: file, env }
108
+ : { command: file, prefix: [], path: file, env };
55
109
  }
56
110
 
57
111
  function readMiniProjectConfig(sourceDir) {
@@ -87,8 +141,9 @@ async function buildMini(project, globalConfig, outRoot) {
87
141
  '--prod', '--no-minify',
88
142
  ];
89
143
  log.step(`Compiling mini program ${id}`);
144
+ log.detail(`Compiler ${builder.path}`);
90
145
  const logFile = path.join(outRoot, 'mini-build.log');
91
- const result = await run(builder.command, args, { cwd: input, capture: true, env: { FORCE_COLOR: '0', NO_COLOR: '1' } });
146
+ const result = await run(builder.command, args, { cwd: input, capture: true, env: { ...builder.env, FORCE_COLOR: '0', NO_COLOR: '1' } });
92
147
  writeFileSync(logFile, `${result.stdout}\n${result.stderr}`);
93
148
  rmSync(work, { recursive: true, force: true });
94
149
  if (result.code !== 0) {
@@ -176,10 +231,39 @@ async function buildH5(project, outRoot) {
176
231
  // ---------------------------------------------------------------------------------------------
177
232
  // Cards: cube-pack (inlines local images) when installed, otherwise the project's own `act build`.
178
233
 
179
- export function resolveAct(projectRoot) {
180
- const local = which('act', [path.join(projectRoot, 'node_modules', '.bin')]);
181
- if (local && local.startsWith(projectRoot)) return local;
182
- return local ?? null;
234
+ export const ACT_MIN_MAJOR = 4;
235
+ const ACT_INSTALL_HINT = 'Install ACT v4 in the card project: npm i -D @antdigital/cube@4';
236
+
237
+ /**
238
+ * The card project's own ACT v4 CLI script (`@antdigital/cube`), looked up like Node resolves
239
+ * packages: from each start directory up through its parents. A global `act` is never used — it is
240
+ * often ACT v2, whose builds and server the apps' v4 Cube host cannot load.
241
+ */
242
+ export function resolveAct(...startDirs) {
243
+ for (const start of startDirs) {
244
+ for (let dir = path.resolve(start); ; dir = path.dirname(dir)) {
245
+ const pkgDir = path.join(dir, 'node_modules', '@antdigital', 'cube');
246
+ const pkgFile = path.join(pkgDir, 'package.json');
247
+ if (existsSync(pkgFile)) {
248
+ const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'));
249
+ const major = Number.parseInt(String(pkg.version), 10);
250
+ if (!(major >= ACT_MIN_MAJOR)) {
251
+ throw new CliError(`${pkgDir} is ACT ${pkg.version}; ACT v${ACT_MIN_MAJOR} or newer is required`, ACT_INSTALL_HINT);
252
+ }
253
+ const bin = typeof pkg.bin === 'string' ? pkg.bin : pkg.bin?.act;
254
+ const script = bin && path.join(pkgDir, bin);
255
+ if (!script || !existsSync(script)) throw new CliError(`${pkgDir} has no act CLI`, ACT_INSTALL_HINT);
256
+ return { script, version: pkg.version };
257
+ }
258
+ if (path.dirname(dir) === dir) break;
259
+ }
260
+ }
261
+ throw new CliError(`ACT v${ACT_MIN_MAJOR} (@antdigital/cube) is not installed in the card project`, ACT_INSTALL_HINT);
262
+ }
263
+
264
+ /** Run the project's ACT CLI with the current Node (also on Windows, where cli.js is not executable). */
265
+ export function runAct(act, args, options) {
266
+ return run(process.execPath, [act.script, ...args], options);
183
267
  }
184
268
 
185
269
  export function cardDir(project) {
@@ -189,16 +273,13 @@ export function cardDir(project) {
189
273
  async function buildCard(project, outRoot) {
190
274
  const dir = cardDir(project);
191
275
  if (!existsSync(path.join(dir, 'manifest.json'))) throw new CliError(`${dir} is not a card (no manifest.json)`);
276
+ // Checked even when cube-pack builds: it runs the project's act, so a missing v4 must stop here.
277
+ const act = resolveAct(dir, project.root);
192
278
  const cubePack = project.data.card?.useCubePack === false ? null : which('cube-pack');
193
- log.step(`Building card ${project.data.resourceId}`);
194
- let result;
195
- if (cubePack) {
196
- result = await run(cubePack, ['build'], { cwd: project.root });
197
- } else {
198
- const act = resolveAct(project.root);
199
- if (!act) throw new CliError('ACT not found', 'Install it in the card project: npm i -D @antdigital/cube');
200
- result = await run(act, ['build', dir], { cwd: project.root });
201
- }
279
+ log.step(`Building card ${project.data.resourceId} (ACT ${act.version})`);
280
+ const result = cubePack
281
+ ? await run(cubePack, ['build'], { cwd: project.root })
282
+ : await runAct(act, ['build', dir], { cwd: project.root });
202
283
  if (result.code !== 0) throw new CliError(`Card build failed (exit ${result.code})`);
203
284
  const zip = findCardZip(dir);
204
285
  const file = path.join(outRoot, `${project.data.resourceId}.zip`);
package/src/cli.js CHANGED
@@ -385,7 +385,7 @@ const COMMANDS = {
385
385
  const config = loadGlobalConfig(ctx.flags);
386
386
  add('config', config.exists, config.file);
387
387
  try {
388
- add('mini builder', true, resolveMiniBuilder(config).prefix[0] ?? resolveMiniBuilder(config).command);
388
+ add('mini builder', true, resolveMiniBuilder(config).path);
389
389
  } catch (e) {
390
390
  add('mini builder', false, e.message);
391
391
  }
package/src/config.js CHANGED
@@ -58,9 +58,6 @@ export const CONFIG_TEMPLATE = {
58
58
  bundleId: '<ios bundle id>',
59
59
  },
60
60
  },
61
- builders: {
62
- mini: '<path to mini-pkg-builder executable or its runtime index.js>',
63
- },
64
61
  };
65
62
 
66
63
  /**