@tetherto/mdk-cli 0.0.0-stage → 0.12.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.
Files changed (112) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +446 -2
  3. package/dist/commands/add.js +61 -0
  4. package/dist/commands/add.js.map +1 -0
  5. package/dist/commands/agent.js +42 -0
  6. package/dist/commands/agent.js.map +1 -0
  7. package/dist/commands/create.js +261 -0
  8. package/dist/commands/create.js.map +1 -0
  9. package/dist/commands/inspect.js +171 -0
  10. package/dist/commands/inspect.js.map +1 -0
  11. package/dist/commands/meta.js +12 -0
  12. package/dist/commands/meta.js.map +1 -0
  13. package/dist/commands/onboard.js +447 -0
  14. package/dist/commands/onboard.js.map +1 -0
  15. package/dist/commands/run.js +203 -0
  16. package/dist/commands/run.js.map +1 -0
  17. package/dist/commands/validate.js +125 -0
  18. package/dist/commands/validate.js.map +1 -0
  19. package/dist/index.js +9 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/lib/add-package.js +148 -0
  22. package/dist/lib/add-package.js.map +1 -0
  23. package/dist/lib/app-package.js +182 -0
  24. package/dist/lib/app-package.js.map +1 -0
  25. package/dist/lib/app-scaffold.js +221 -0
  26. package/dist/lib/app-scaffold.js.map +1 -0
  27. package/dist/lib/app-ui-check.js +178 -0
  28. package/dist/lib/app-ui-check.js.map +1 -0
  29. package/dist/lib/app-ui-runner.js +122 -0
  30. package/dist/lib/app-ui-runner.js.map +1 -0
  31. package/dist/lib/catalog.js +69 -0
  32. package/dist/lib/catalog.js.map +1 -0
  33. package/dist/lib/checkout.js +14 -0
  34. package/dist/lib/checkout.js.map +1 -0
  35. package/dist/lib/dashboard-apps.js +71 -0
  36. package/dist/lib/dashboard-apps.js.map +1 -0
  37. package/dist/lib/dashboard.js +318 -0
  38. package/dist/lib/dashboard.js.map +1 -0
  39. package/dist/lib/detect.js +38 -0
  40. package/dist/lib/detect.js.map +1 -0
  41. package/dist/lib/env-file.js +57 -0
  42. package/dist/lib/env-file.js.map +1 -0
  43. package/dist/lib/fetch-template.js +33 -0
  44. package/dist/lib/fetch-template.js.map +1 -0
  45. package/dist/lib/help.js +22 -0
  46. package/dist/lib/help.js.map +1 -0
  47. package/dist/lib/names.js +36 -0
  48. package/dist/lib/names.js.map +1 -0
  49. package/dist/lib/npm.js +113 -0
  50. package/dist/lib/npm.js.map +1 -0
  51. package/dist/lib/package-source.js +76 -0
  52. package/dist/lib/package-source.js.map +1 -0
  53. package/dist/lib/pkg.js +5 -0
  54. package/dist/lib/pkg.js.map +1 -0
  55. package/dist/lib/plugin-setup.js +204 -0
  56. package/dist/lib/plugin-setup.js.map +1 -0
  57. package/dist/lib/ports.js +31 -0
  58. package/dist/lib/ports.js.map +1 -0
  59. package/dist/lib/project.js +297 -0
  60. package/dist/lib/project.js.map +1 -0
  61. package/dist/lib/run-lock.js +188 -0
  62. package/dist/lib/run-lock.js.map +1 -0
  63. package/dist/lib/runtime.js +683 -0
  64. package/dist/lib/runtime.js.map +1 -0
  65. package/dist/lib/scaffold.js +82 -0
  66. package/dist/lib/scaffold.js.map +1 -0
  67. package/dist/lib/shutdown.js +53 -0
  68. package/dist/lib/shutdown.js.map +1 -0
  69. package/dist/lib/skill.js +38 -0
  70. package/dist/lib/skill.js.map +1 -0
  71. package/dist/lib/spec.js +375 -0
  72. package/dist/lib/spec.js.map +1 -0
  73. package/dist/lib/status.js +245 -0
  74. package/dist/lib/status.js.map +1 -0
  75. package/dist/lib/theme.js +50 -0
  76. package/dist/lib/theme.js.map +1 -0
  77. package/dist/lib/worker-package.js +74 -0
  78. package/dist/lib/worker-package.js.map +1 -0
  79. package/dist/lib/worker-scaffold.js +122 -0
  80. package/dist/lib/worker-scaffold.js.map +1 -0
  81. package/dist/lib/worker-smoke.js +243 -0
  82. package/dist/lib/worker-smoke.js.map +1 -0
  83. package/dist/program.js +58 -0
  84. package/dist/program.js.map +1 -0
  85. package/package.json +57 -4
  86. package/templates/app/README.md +66 -0
  87. package/templates/app/mdk-app.yaml +10 -0
  88. package/templates/app/package.json +17 -0
  89. package/templates/app/plugin/controllers/summary.js +36 -0
  90. package/templates/app/plugin/lib/client.js +11 -0
  91. package/templates/app/plugin/mdk-plugin.json +16 -0
  92. package/templates/app-ui/tsconfig.json +29 -0
  93. package/templates/app-ui/ui-src/components/summary-panel.tsx +24 -0
  94. package/templates/app-ui/ui-src/data/summary.ts +18 -0
  95. package/templates/app-ui/ui-src/hooks/use-summary.ts +54 -0
  96. package/templates/app-ui/ui-src/index.tsx +15 -0
  97. package/templates/app-ui/ui-src/pages/overview-page.tsx +18 -0
  98. package/templates/app-ui/ui-src/styles.css +13 -0
  99. package/templates/app-ui/ui-src/styles.d.ts +3 -0
  100. package/templates/app-ui/ui-src/vite.config.ts +26 -0
  101. package/templates/worker/README.md +93 -0
  102. package/templates/worker/mdk-contract.json +26 -0
  103. package/templates/worker/mock/server.js +93 -0
  104. package/templates/worker/package.json +23 -0
  105. package/templates/worker/smoke.config.js +31 -0
  106. package/templates/worker/src/client.js +47 -0
  107. package/templates/worker/src/commands/reboot.js +5 -0
  108. package/templates/worker/src/commands/set-power-mode.js +10 -0
  109. package/templates/worker/src/telemetry/hashrate-rt.js +5 -0
  110. package/templates/worker/src/telemetry/power-mode.js +5 -0
  111. package/templates/worker/src/telemetry/power.js +5 -0
  112. package/templates/worker/src/telemetry/temperature.js +5 -0
@@ -0,0 +1,182 @@
1
+ import { existsSync, readFileSync, statSync } from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import { isAbsolute, join, relative, resolve } from 'node:path';
4
+ import { Script } from 'node:vm';
5
+ import { parse as parseYaml } from 'yaml';
6
+ import { checkAppUi } from './app-ui-check.js';
7
+ import { isInside } from './names.js';
8
+ const require = createRequire(import.meta.url);
9
+ export const APP_MANIFEST = 'mdk-app.yaml';
10
+ /** Where the app runtime looks for the route table, in order: `plugin/` first, then the package root. */
11
+ const ROUTE_TABLES = ['plugin/mdk-plugin.json', 'mdk-plugin.json'];
12
+ const MANIFEST_KEYS = ['id', 'version', 'displayName', 'description', 'permissions', 'config', 'main'];
13
+ const RULE_KEYS = ['resources', 'verbs', 'resourceNames'];
14
+ // The app runtime's own rules (@tetherto/mdk-app-runtime lib/manifest.js): a
15
+ // looser pattern here would pass a package the runtime then refuses to start.
16
+ const ID_PATTERN = /^[a-z][a-z0-9]*(\.[a-z0-9][a-z0-9-]*)+$/;
17
+ const SEMVER_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$/;
18
+ export const isMapping = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
19
+ const isNameList = (value) => Array.isArray(value) && value.length > 0 && value.every((s) => typeof s === 'string' && s !== '');
20
+ export const message = (error) => (error instanceof Error ? error.message : String(error));
21
+ export function readFile(dir, name, parse, errors) {
22
+ const file = join(dir, name);
23
+ if (!existsSync(file)) {
24
+ errors.push(`${name}: not found`);
25
+ return undefined;
26
+ }
27
+ try {
28
+ return parse(readFileSync(file, 'utf8'));
29
+ }
30
+ catch (error) {
31
+ errors.push(`${name}: ${message(error)}`);
32
+ return undefined;
33
+ }
34
+ }
35
+ /** The error loading a CommonJS file would throw at compile time, without running it. */
36
+ export function syntaxError(file) {
37
+ try {
38
+ new Script(`(function (exports, require, module, __filename, __dirname) {${readFileSync(file, 'utf8')}\n})`, {
39
+ filename: file,
40
+ });
41
+ return null;
42
+ }
43
+ catch (error) {
44
+ return message(error);
45
+ }
46
+ }
47
+ function checkUnknownKeys(value, allowed, at, errors) {
48
+ for (const key of Object.keys(value)) {
49
+ if (!allowed.includes(key))
50
+ errors.push(`${at}: unknown key "${key}" (allowed: ${allowed.join(', ')})`);
51
+ }
52
+ }
53
+ function checkRule(rule, at, errors) {
54
+ if (!isMapping(rule)) {
55
+ errors.push(`${at} must be a mapping with resources and verbs`);
56
+ return;
57
+ }
58
+ checkUnknownKeys(rule, RULE_KEYS, at, errors);
59
+ for (const key of ['resources', 'verbs']) {
60
+ if (!isNameList(rule[key]))
61
+ errors.push(`${at}.${key} must be a non-empty list of strings`);
62
+ }
63
+ if (rule.resourceNames !== undefined && !isNameList(rule.resourceNames)) {
64
+ errors.push(`${at}.resourceNames must be a non-empty list of strings`);
65
+ }
66
+ }
67
+ function checkManifest(manifest, pkg, errors) {
68
+ if (!isMapping(manifest)) {
69
+ errors.push(`${APP_MANIFEST}: must be a mapping`);
70
+ return;
71
+ }
72
+ checkUnknownKeys(manifest, MANIFEST_KEYS, APP_MANIFEST, errors);
73
+ const { id, version, displayName, permissions, config } = manifest;
74
+ if (typeof id !== 'string' || !ID_PATTERN.test(id)) {
75
+ errors.push(`${APP_MANIFEST}: "id" must be reverse-DNS, e.g. io.tether.demo`);
76
+ }
77
+ if (typeof version !== 'string' || !SEMVER_PATTERN.test(version)) {
78
+ errors.push(`${APP_MANIFEST}: "version" must be a semver string, e.g. 0.1.0`);
79
+ }
80
+ else if (isMapping(pkg) && version !== pkg.version) {
81
+ errors.push(`${APP_MANIFEST}: "version" ${version} does not match package.json version ${String(pkg.version)}`);
82
+ }
83
+ if (typeof displayName !== 'string' || !displayName.trim()) {
84
+ errors.push(`${APP_MANIFEST}: "displayName" must be a non-empty string`);
85
+ }
86
+ if (manifest.description !== undefined && typeof manifest.description !== 'string') {
87
+ errors.push(`${APP_MANIFEST}: "description" must be a string`);
88
+ }
89
+ if (permissions !== undefined) {
90
+ if (Array.isArray(permissions)) {
91
+ permissions.forEach((rule, i) => checkRule(rule, `${APP_MANIFEST}: permissions[${i}]`, errors));
92
+ }
93
+ else {
94
+ errors.push(`${APP_MANIFEST}: "permissions" must be a list of rules`);
95
+ }
96
+ }
97
+ if (config !== undefined && !isMapping(config)) {
98
+ errors.push(`${APP_MANIFEST}: "config" must be a mapping`);
99
+ }
100
+ if (manifest.main !== undefined && (typeof manifest.main !== 'string' || !manifest.main.trim())) {
101
+ errors.push(`${APP_MANIFEST}: "main" must be a "<file>[#export]" string`);
102
+ }
103
+ }
104
+ /** `<file>[#export]` beside the route table: inside it, present, and compiling. */
105
+ function checkModule(dir, pluginDir, spec, what, errors) {
106
+ const file = spec.split('#')[0] ?? '';
107
+ // The loader refuses these at boot (ERR_PLUGIN_HANDLER_OUTSIDE_PLUGIN).
108
+ const target = resolve(pluginDir, file);
109
+ if (isAbsolute(file) || target === pluginDir || !isInside(pluginDir, target)) {
110
+ errors.push(`${what} "${spec}" must be inside ${relative(dir, pluginDir) || 'the package'}`);
111
+ return;
112
+ }
113
+ let resolved;
114
+ try {
115
+ resolved = require.resolve(target);
116
+ }
117
+ catch {
118
+ errors.push(`${what} "${spec}" not found`);
119
+ return;
120
+ }
121
+ const syntax = syntaxError(resolved);
122
+ if (syntax)
123
+ errors.push(`${what} "${spec}" does not compile: ${syntax}`);
124
+ }
125
+ function checkRouteTable(dir, errors) {
126
+ const name = ROUTE_TABLES.find((candidate) => existsSync(join(dir, candidate)));
127
+ if (!name) {
128
+ errors.push('mdk-plugin.json: not found (looked in plugin/ and the package root)');
129
+ return { routes: 0, pluginDir: null };
130
+ }
131
+ const pluginDir = resolve(dir, name, '..');
132
+ const table = readFile(dir, name, JSON.parse, errors);
133
+ if (table === undefined)
134
+ return { routes: 0, pluginDir };
135
+ const loader = require('@tetherto/mdk-gateway/workers/lib/plugin-loader');
136
+ try {
137
+ loader.validateManifest(table, name);
138
+ }
139
+ catch (error) {
140
+ errors.push(message(error));
141
+ return { routes: 0, pluginDir };
142
+ }
143
+ const { routes } = table;
144
+ for (const route of routes) {
145
+ checkModule(dir, pluginDir, route.handler, `${name}: route "${route.id}" handler`, errors);
146
+ }
147
+ return { routes: routes.length, pluginDir };
148
+ }
149
+ /**
150
+ * Checks an app package before it is published: the manifest, its version against
151
+ * package.json, the permission rules' shape, the route table (by the Gateway's own
152
+ * rules) and that every handler and `main` compiles. Nothing is executed, so a module
153
+ * that throws when loaded or a missing named export only shows when the app starts.
154
+ * A built UI (`ui/`) is checked by `checkAppUi`: syntax, imports, no bundled React,
155
+ * and with `runUiContract` its exports.
156
+ */
157
+ export function validateApp(dir, opts = {}) {
158
+ if (!existsSync(dir) || !statSync(dir).isDirectory()) {
159
+ return { errors: [`${dir}: not a directory`], warnings: [], manifest: undefined, routes: 0, ui: false };
160
+ }
161
+ const errors = [];
162
+ const warnings = [];
163
+ const pkg = readFile(dir, 'package.json', JSON.parse, errors);
164
+ const manifest = readFile(dir, APP_MANIFEST, parseYaml, errors);
165
+ if (manifest !== undefined) {
166
+ checkManifest(manifest, pkg, errors);
167
+ }
168
+ const { routes, pluginDir } = checkRouteTable(dir, errors);
169
+ const main = isMapping(manifest) ? manifest.main : undefined;
170
+ if (pluginDir && typeof main === 'string' && main.trim()) {
171
+ checkModule(dir, pluginDir, main, `${APP_MANIFEST}: main`, errors);
172
+ }
173
+ const ui = existsSync(join(dir, 'ui'));
174
+ if (ui) {
175
+ const id = isMapping(manifest) && typeof manifest.id === 'string' ? manifest.id : undefined;
176
+ const check = checkAppUi(dir, { id, runContract: opts.runUiContract });
177
+ errors.push(...check.errors);
178
+ warnings.push(...check.warnings);
179
+ }
180
+ return { errors, warnings, manifest: manifest, routes, ui };
181
+ }
182
+ //# sourceMappingURL=app-package.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-package.js","sourceRoot":"","sources":["../../src/lib/app-package.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAChE,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,KAAK,IAAI,SAAS,EAAE,MAAM,MAAM,CAAC;AAC1C,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC/C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;AAE/C,MAAM,CAAC,MAAM,YAAY,GAAG,cAAc,CAAC;AAC3C,yGAAyG;AACzG,MAAM,YAAY,GAAG,CAAC,wBAAwB,EAAE,iBAAiB,CAAC,CAAC;AACnE,MAAM,aAAa,GAAG,CAAC,IAAI,EAAE,SAAS,EAAE,aAAa,EAAE,aAAa,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;AACvG,MAAM,SAAS,GAAG,CAAC,WAAW,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;AAC1D,6EAA6E;AAC7E,8EAA8E;AAC9E,MAAM,UAAU,GAAG,yCAAyC,CAAC;AAC7D,MAAM,cAAc,GAClB,mHAAmH,CAAC;AA4CtH,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAc,EAAoC,EAAE,CAC5E,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEvE,MAAM,UAAU,GAAG,CAAC,KAAc,EAAW,EAAE,CAC7C,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;AAEpG,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,KAAc,EAAU,EAAE,CAAC,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;AAE5G,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,IAAY,EAAE,KAAgC,EAAE,MAAgB;IACpG,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IAC7B,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QACtB,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IACD,IAAI,CAAC;QACH,OAAO,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QAC1C,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,IAAI,MAAM,CAAC,gEAAgE,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE;YAC3G,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QACH,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC;IACxB,CAAC;AACH,CAAC;AAED,SAAS,gBAAgB,CAAC,KAA8B,EAAE,OAAiB,EAAE,EAAU,EAAE,MAAgB;IACvG,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,GAAG,eAAe,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1G,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,IAAa,EAAE,EAAU,EAAE,MAAgB;IAC5D,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,6CAA6C,CAAC,CAAC;QAChE,OAAO;IACT,CAAC;IACD,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,CAAC,CAAC;IAC9C,KAAK,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAAE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,GAAG,sCAAsC,CAAC,CAAC;IAC9F,CAAC;IACD,IAAI,IAAI,CAAC,aAAa,KAAK,SAAS,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;QACxE,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,oDAAoD,CAAC,CAAC;IACzE,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,QAAiB,EAAE,GAAY,EAAE,MAAgB;IACtE,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,EAAE,CAAC;QACzB,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,qBAAqB,CAAC,CAAC;QAClD,OAAO;IACT,CAAC;IACD,gBAAgB,CAAC,QAAQ,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,CAAC,CAAC;IAEhE,MAAM,EAAE,EAAE,EAAE,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,QAAQ,CAAC;IACnE,IAAI,OAAO,EAAE,KAAK,QAAQ,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC;QACnD,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,iDAAiD,CAAC,CAAC;IAChF,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;QACjE,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,iDAAiD,CAAC,CAAC;IAChF,CAAC;SAAM,IAAI,SAAS,CAAC,GAAG,CAAC,IAAI,OAAO,KAAK,GAAG,CAAC,OAAO,EAAE,CAAC;QACrD,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,eAAe,OAAO,wCAAwC,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAClH,CAAC;IACD,IAAI,OAAO,WAAW,KAAK,QAAQ,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,CAAC;QAC3D,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,4CAA4C,CAAC,CAAC;IAC3E,CAAC;IACD,IAAI,QAAQ,CAAC,WAAW,KAAK,SAAS,IAAI,OAAO,QAAQ,CAAC,WAAW,KAAK,QAAQ,EAAE,CAAC;QACnF,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,kCAAkC,CAAC,CAAC;IACjE,CAAC;IACD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;QAC9B,IAAI,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,CAAC;YAC/B,WAAW,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,YAAY,iBAAiB,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,CAAC;QAClG,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,yCAAyC,CAAC,CAAC;QACxE,CAAC;IACH,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,EAAE,CAAC;QAC/C,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,8BAA8B,CAAC,CAAC;IAC7D,CAAC;IACD,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,QAAQ,CAAC,IAAI,KAAK,QAAQ,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,CAAC;QAChG,MAAM,CAAC,IAAI,CAAC,GAAG,YAAY,6CAA6C,CAAC,CAAC;IAC5E,CAAC;AACH,CAAC;AAED,mFAAmF;AACnF,SAAS,WAAW,CAAC,GAAW,EAAE,SAAiB,EAAE,IAAY,EAAE,IAAY,EAAE,MAAgB;IAC/F,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACtC,wEAAwE;IACxE,MAAM,MAAM,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;IACxC,IAAI,UAAU,CAAC,IAAI,CAAC,IAAI,MAAM,KAAK,SAAS,IAAI,CAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,EAAE,CAAC;QAC7E,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,oBAAoB,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,IAAI,aAAa,EAAE,CAAC,CAAC;QAC7F,OAAO;IACT,CAAC;IACD,IAAI,QAAgB,CAAC;IACrB,IAAI,CAAC;QACH,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,aAAa,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,QAAQ,CAAC,CAAC;IACrC,IAAI,MAAM;QAAE,MAAM,CAAC,IAAI,CAAC,GAAG,IAAI,KAAK,IAAI,uBAAuB,MAAM,EAAE,CAAC,CAAC;AAC3E,CAAC;AAED,SAAS,eAAe,CAAC,GAAW,EAAE,MAAgB;IACpD,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,CAAC;IAChF,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,CAAC,IAAI,CAAC,qEAAqE,CAAC,CAAC;QACnF,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IACxC,CAAC;IACD,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IACtD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;IAEzD,MAAM,MAAM,GAAG,OAAO,CAAC,iDAAiD,CAAiB,CAAC;IAC1F,IAAI,CAAC;QACH,MAAM,CAAC,gBAAgB,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC;QAC5B,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC;IAClC,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,KAAmB,CAAC;IACvC,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,KAAK,CAAC,OAAO,EAAE,GAAG,IAAI,YAAY,KAAK,CAAC,EAAE,WAAW,EAAE,MAAM,CAAC,CAAC;IAC7F,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,SAAS,EAAE,CAAC;AAC9C,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW,EAAE,IAAI,GAAuB,EAAE;IACpE,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,CAAC,GAAG,GAAG,mBAAmB,CAAC,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,EAAE,SAAmC,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC;IACpI,CAAC;IACD,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,MAAM,QAAQ,GAAa,EAAE,CAAC;IAC9B,MAAM,GAAG,GAAG,QAAQ,CAAC,GAAG,EAAE,cAAc,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC9D,MAAM,QAAQ,GAAG,QAAQ,CAAC,GAAG,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;IAChE,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC3B,aAAa,CAAC,QAAQ,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,GAAG,eAAe,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IAC3D,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;IAC7D,IAAI,SAAS,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QACzD,WAAW,CAAC,GAAG,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,YAAY,QAAQ,EAAE,MAAM,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,EAAE,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,CAAC;IACvC,IAAI,EAAE,EAAE,CAAC;QACP,MAAM,EAAE,GAAG,SAAS,CAAC,QAAQ,CAAC,IAAI,OAAO,QAAQ,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC;QAC5F,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;QACvE,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;QAC7B,QAAQ,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC;IACnC,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAuB,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC;AAC7E,CAAC","sourcesContent":["import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\nimport { Script } from 'node:vm';\nimport { parse as parseYaml } from 'yaml';\nimport { checkAppUi } from './app-ui-check.js';\nimport { isInside } from './names.js';\n\nconst require = createRequire(import.meta.url);\n\nexport const APP_MANIFEST = 'mdk-app.yaml';\n/** Where the app runtime looks for the route table, in order: `plugin/` first, then the package root. */\nconst ROUTE_TABLES = ['plugin/mdk-plugin.json', 'mdk-plugin.json'];\nconst MANIFEST_KEYS = ['id', 'version', 'displayName', 'description', 'permissions', 'config', 'main'];\nconst RULE_KEYS = ['resources', 'verbs', 'resourceNames'];\n// The app runtime's own rules (@tetherto/mdk-app-runtime lib/manifest.js): a\n// looser pattern here would pass a package the runtime then refuses to start.\nconst ID_PATTERN = /^[a-z][a-z0-9]*(\\.[a-z0-9][a-z0-9-]*)+$/;\nconst SEMVER_PATTERN =\n /^(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(-[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?(\\+[0-9A-Za-z-]+(\\.[0-9A-Za-z-]+)*)?$/;\n\nexport interface PermissionRule {\n resources: string[];\n verbs: string[];\n resourceNames?: string[];\n}\n\nexport interface AppManifest {\n id: string;\n version: string;\n displayName: string;\n permissions?: PermissionRule[];\n config?: Record<string, unknown>;\n main?: string;\n}\n\nexport interface AppValidation {\n errors: string[];\n /** Problems that do not stop publishing, e.g. a UI build older than `ui-src/`. */\n warnings: string[];\n manifest: AppManifest;\n routes: number;\n /** True when the package ships a UI (`ui/`). */\n ui: boolean;\n}\n\nexport interface ValidateAppOptions {\n /**\n * Also load `ui/index.js` (in a child process, imports stubbed) to check its\n * exports against the app UI contract. That runs the app's top-level code, so\n * only `mdk validate app` sets it — never an install of someone else's app.\n */\n runUiContract?: boolean;\n}\n\ninterface RouteTable {\n routes: Array<{ id: string; handler: string }>;\n}\n\ninterface PluginLoader {\n validateManifest(manifest: unknown, label: string): void;\n}\n\nexport const isMapping = (value: unknown): value is Record<string, unknown> =>\n value !== null && typeof value === 'object' && !Array.isArray(value);\n\nconst isNameList = (value: unknown): boolean =>\n Array.isArray(value) && value.length > 0 && value.every((s) => typeof s === 'string' && s !== '');\n\nexport const message = (error: unknown): string => (error instanceof Error ? error.message : String(error));\n\nexport function readFile(dir: string, name: string, parse: (text: string) => unknown, errors: string[]): unknown {\n const file = join(dir, name);\n if (!existsSync(file)) {\n errors.push(`${name}: not found`);\n return undefined;\n }\n try {\n return parse(readFileSync(file, 'utf8'));\n } catch (error) {\n errors.push(`${name}: ${message(error)}`);\n return undefined;\n }\n}\n\n/** The error loading a CommonJS file would throw at compile time, without running it. */\nexport function syntaxError(file: string): string | null {\n try {\n new Script(`(function (exports, require, module, __filename, __dirname) {${readFileSync(file, 'utf8')}\\n})`, {\n filename: file,\n });\n return null;\n } catch (error) {\n return message(error);\n }\n}\n\nfunction checkUnknownKeys(value: Record<string, unknown>, allowed: string[], at: string, errors: string[]): void {\n for (const key of Object.keys(value)) {\n if (!allowed.includes(key)) errors.push(`${at}: unknown key \"${key}\" (allowed: ${allowed.join(', ')})`);\n }\n}\n\nfunction checkRule(rule: unknown, at: string, errors: string[]): void {\n if (!isMapping(rule)) {\n errors.push(`${at} must be a mapping with resources and verbs`);\n return;\n }\n checkUnknownKeys(rule, RULE_KEYS, at, errors);\n for (const key of ['resources', 'verbs']) {\n if (!isNameList(rule[key])) errors.push(`${at}.${key} must be a non-empty list of strings`);\n }\n if (rule.resourceNames !== undefined && !isNameList(rule.resourceNames)) {\n errors.push(`${at}.resourceNames must be a non-empty list of strings`);\n }\n}\n\nfunction checkManifest(manifest: unknown, pkg: unknown, errors: string[]): void {\n if (!isMapping(manifest)) {\n errors.push(`${APP_MANIFEST}: must be a mapping`);\n return;\n }\n checkUnknownKeys(manifest, MANIFEST_KEYS, APP_MANIFEST, errors);\n\n const { id, version, displayName, permissions, config } = manifest;\n if (typeof id !== 'string' || !ID_PATTERN.test(id)) {\n errors.push(`${APP_MANIFEST}: \"id\" must be reverse-DNS, e.g. io.tether.demo`);\n }\n if (typeof version !== 'string' || !SEMVER_PATTERN.test(version)) {\n errors.push(`${APP_MANIFEST}: \"version\" must be a semver string, e.g. 0.1.0`);\n } else if (isMapping(pkg) && version !== pkg.version) {\n errors.push(`${APP_MANIFEST}: \"version\" ${version} does not match package.json version ${String(pkg.version)}`);\n }\n if (typeof displayName !== 'string' || !displayName.trim()) {\n errors.push(`${APP_MANIFEST}: \"displayName\" must be a non-empty string`);\n }\n if (manifest.description !== undefined && typeof manifest.description !== 'string') {\n errors.push(`${APP_MANIFEST}: \"description\" must be a string`);\n }\n if (permissions !== undefined) {\n if (Array.isArray(permissions)) {\n permissions.forEach((rule, i) => checkRule(rule, `${APP_MANIFEST}: permissions[${i}]`, errors));\n } else {\n errors.push(`${APP_MANIFEST}: \"permissions\" must be a list of rules`);\n }\n }\n if (config !== undefined && !isMapping(config)) {\n errors.push(`${APP_MANIFEST}: \"config\" must be a mapping`);\n }\n if (manifest.main !== undefined && (typeof manifest.main !== 'string' || !manifest.main.trim())) {\n errors.push(`${APP_MANIFEST}: \"main\" must be a \"<file>[#export]\" string`);\n }\n}\n\n/** `<file>[#export]` beside the route table: inside it, present, and compiling. */\nfunction checkModule(dir: string, pluginDir: string, spec: string, what: string, errors: string[]): void {\n const file = spec.split('#')[0] ?? '';\n // The loader refuses these at boot (ERR_PLUGIN_HANDLER_OUTSIDE_PLUGIN).\n const target = resolve(pluginDir, file);\n if (isAbsolute(file) || target === pluginDir || !isInside(pluginDir, target)) {\n errors.push(`${what} \"${spec}\" must be inside ${relative(dir, pluginDir) || 'the package'}`);\n return;\n }\n let resolved: string;\n try {\n resolved = require.resolve(target);\n } catch {\n errors.push(`${what} \"${spec}\" not found`);\n return;\n }\n const syntax = syntaxError(resolved);\n if (syntax) errors.push(`${what} \"${spec}\" does not compile: ${syntax}`);\n}\n\nfunction checkRouteTable(dir: string, errors: string[]): { routes: number; pluginDir: string | null } {\n const name = ROUTE_TABLES.find((candidate) => existsSync(join(dir, candidate)));\n if (!name) {\n errors.push('mdk-plugin.json: not found (looked in plugin/ and the package root)');\n return { routes: 0, pluginDir: null };\n }\n const pluginDir = resolve(dir, name, '..');\n const table = readFile(dir, name, JSON.parse, errors);\n if (table === undefined) return { routes: 0, pluginDir };\n\n const loader = require('@tetherto/mdk-gateway/workers/lib/plugin-loader') as PluginLoader;\n try {\n loader.validateManifest(table, name);\n } catch (error) {\n errors.push(message(error));\n return { routes: 0, pluginDir };\n }\n\n const { routes } = table as RouteTable;\n for (const route of routes) {\n checkModule(dir, pluginDir, route.handler, `${name}: route \"${route.id}\" handler`, errors);\n }\n return { routes: routes.length, pluginDir };\n}\n\n/**\n * Checks an app package before it is published: the manifest, its version against\n * package.json, the permission rules' shape, the route table (by the Gateway's own\n * rules) and that every handler and `main` compiles. Nothing is executed, so a module\n * that throws when loaded or a missing named export only shows when the app starts.\n * A built UI (`ui/`) is checked by `checkAppUi`: syntax, imports, no bundled React,\n * and with `runUiContract` its exports.\n */\nexport function validateApp(dir: string, opts: ValidateAppOptions = {}): AppValidation {\n if (!existsSync(dir) || !statSync(dir).isDirectory()) {\n return { errors: [`${dir}: not a directory`], warnings: [], manifest: undefined as unknown as AppManifest, routes: 0, ui: false };\n }\n const errors: string[] = [];\n const warnings: string[] = [];\n const pkg = readFile(dir, 'package.json', JSON.parse, errors);\n const manifest = readFile(dir, APP_MANIFEST, parseYaml, errors);\n if (manifest !== undefined) {\n checkManifest(manifest, pkg, errors);\n }\n\n const { routes, pluginDir } = checkRouteTable(dir, errors);\n const main = isMapping(manifest) ? manifest.main : undefined;\n if (pluginDir && typeof main === 'string' && main.trim()) {\n checkModule(dir, pluginDir, main, `${APP_MANIFEST}: main`, errors);\n }\n\n const ui = existsSync(join(dir, 'ui'));\n if (ui) {\n const id = isMapping(manifest) && typeof manifest.id === 'string' ? manifest.id : undefined;\n const check = checkAppUi(dir, { id, runContract: opts.runUiContract });\n errors.push(...check.errors);\n warnings.push(...check.warnings);\n }\n\n return { errors, warnings, manifest: manifest as AppManifest, routes, ui };\n}\n"]}
@@ -0,0 +1,221 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { parse as parseYaml } from 'yaml';
6
+ import { checkoutRoot } from './checkout.js';
7
+ import { installDeps, installScaffold, npmBin, npmNeedsShell } from './npm.js';
8
+ import { pkg as cliPkg } from './pkg.js';
9
+ import { DIRS } from './project.js';
10
+ import { NAME_RE, parseOrg } from './names.js';
11
+ import { appendToStackList, copyTemplate, linkIntoProject, updateJson } from './scaffold.js';
12
+ import { toAppName } from './spec.js';
13
+ import { findCatalogApp } from './catalog.js';
14
+ const __dirname = dirname(fileURLToPath(import.meta.url));
15
+ /** Placeholders baked into `templates/app/**`, replaced at scaffold time. */
16
+ const NAME_TOKEN = '__PLUGIN_NAME__';
17
+ /** The `spec.apps[].name` (see `toAppName`), also the last segment of the `mdk-app.yaml` id. */
18
+ const APP_TOKEN = '__PLUGIN_APP__';
19
+ /** The package name, scoped under `--org` when given. */
20
+ const PACKAGE_TOKEN = '__PACKAGE_NAME__';
21
+ /**
22
+ * Absolute path to the bundled app template (`templates/app`).
23
+ * Resolves the same from `dist/lib` (built) and `src/lib` (tsx dev) since both
24
+ * sit one level under the package root.
25
+ */
26
+ function templateDir() {
27
+ return resolve(__dirname, '..', '..', 'templates', 'app');
28
+ }
29
+ const CLI_ROOT = resolve(__dirname, '..', '..');
30
+ /** The starter UI (`templates/app-ui`): `ui-src/` with its own Vite build. */
31
+ export function appUiTemplateDir() {
32
+ return resolve(CLI_ROOT, 'templates', 'app-ui');
33
+ }
34
+ /** The MDK UI packages the starter imports, by their directory under `ui/packages/`. */
35
+ const MDK_UI_PACKAGES = {
36
+ '@tetherto/mdk-ui-foundation': 'ui-foundation',
37
+ '@tetherto/mdk-react-adapter': 'react-adapter',
38
+ '@tetherto/mdk-react-devkit': 'react-devkit',
39
+ };
40
+ /**
41
+ * What the starter UI needs to build, in `ui-src/package.json` — its own
42
+ * package, so building the UI never installs the app's backend dependencies.
43
+ * None of it ships with the app: the built `ui/index.js` leaves React and the
44
+ * MDK packages to the dashboard. Inside an MDK checkout the MDK packages link to
45
+ * it, like a scaffolded dashboard; elsewhere they take the CLI's published range.
46
+ */
47
+ function uiDevDependencies() {
48
+ const root = checkoutRoot(CLI_ROOT);
49
+ const mdk = Object.fromEntries(Object.entries(MDK_UI_PACKAGES).map(([name, dir]) => [
50
+ name,
51
+ root ? `file:${join(root, 'ui', 'packages', dir)}` : `^${cliPkg.version}`,
52
+ ]));
53
+ return {
54
+ ...mdk,
55
+ '@tanstack/react-query': '^5.59.0',
56
+ '@types/react': '^19.2.14',
57
+ '@vitejs/plugin-react': '^5.1.4',
58
+ react: '^19.2.0',
59
+ 'react-dom': '^19.2.0',
60
+ 'react-router': '^8.3.0',
61
+ typescript: '^7.0.2',
62
+ vite: '^7.0.0',
63
+ };
64
+ }
65
+ const writeJson = (file, value) => writeFileSync(file, `${JSON.stringify(value, null, 2)}\n`, 'utf8');
66
+ /**
67
+ * Writes the starter UI into `<appPath>/ui-src/` (one page and one Home panel
68
+ * showing the app's `summary` route, across the three MDK UI layers), its own
69
+ * package.json and tsconfig, the `build:ui` / `dev:ui` / `typecheck:ui` scripts,
70
+ * and a placeholder `ui/index.js` the build replaces.
71
+ */
72
+ function writeStarterUi(appPath, name) {
73
+ const manifest = parseYaml(readFileSync(join(appPath, 'mdk-app.yaml'), 'utf8'));
74
+ const pkgFile = join(appPath, 'package.json');
75
+ const packageName = JSON.parse(readFileSync(pkgFile, 'utf8')).name;
76
+ const uiSrc = join(appPath, 'ui-src');
77
+ // Tokens are slugs or come from the generated manifest, so they are safe
78
+ // inside the string literals and class names they replace.
79
+ copyTemplate(join(appUiTemplateDir(), 'ui-src'), uiSrc, {
80
+ __APP_NAME__: name,
81
+ __APP_ID__: manifest.id,
82
+ __APP_DISPLAY_NAME__: manifest.displayName,
83
+ __APP_CSS__: name.replace(/[^a-z0-9_-]/g, '-'),
84
+ });
85
+ writeJson(join(uiSrc, 'tsconfig.json'), {
86
+ compilerOptions: {
87
+ target: 'ES2022',
88
+ lib: ['ES2022', 'DOM', 'DOM.Iterable'],
89
+ module: 'ESNext',
90
+ moduleResolution: 'bundler',
91
+ jsx: 'react-jsx',
92
+ strict: true,
93
+ noUncheckedIndexedAccess: true,
94
+ skipLibCheck: true,
95
+ noEmit: true,
96
+ isolatedModules: true,
97
+ },
98
+ include: ['.'],
99
+ });
100
+ writeJson(join(uiSrc, 'package.json'), {
101
+ name: `${packageName}-ui`,
102
+ private: true,
103
+ type: 'module',
104
+ scripts: { build: 'vite build', dev: 'vite build --watch', typecheck: 'tsc --noEmit' },
105
+ devDependencies: uiDevDependencies(),
106
+ });
107
+ writeFileSync(join(appPath, '.gitignore'), 'node_modules/\n', 'utf8');
108
+ updateJson(pkgFile, {
109
+ // Publish what the app runs and shows; ui-src/ is local build input.
110
+ files: ['mdk-app.yaml', 'plugin/', 'ui/', 'README.md'],
111
+ scripts: {
112
+ 'build:ui': 'npm --prefix ui-src run build',
113
+ 'dev:ui': 'npm --prefix ui-src run dev',
114
+ 'typecheck:ui': 'npm --prefix ui-src run typecheck',
115
+ },
116
+ });
117
+ // A placeholder the build replaces: the app is a valid package, and loads in
118
+ // the dashboard, even before (or without) its UI is built.
119
+ mkdirSync(join(appPath, 'ui'), { recursive: true });
120
+ writeFileSync(join(appPath, 'ui', 'index.js'), `export default ${JSON.stringify({ id: manifest.id, displayName: manifest.displayName, routes: [], panels: [] }, null, 2)};\n`, 'utf8');
121
+ }
122
+ /** Installs the starter UI's build dependencies and builds `ui/`; returns why it did not, if it did not. */
123
+ function buildStarterUi(appPath) {
124
+ const install = installDeps(join(appPath, 'ui-src'));
125
+ if (!install.ok)
126
+ return install.message;
127
+ const res = spawnSync(npmBin(), ['run', 'build:ui'], {
128
+ cwd: appPath,
129
+ encoding: 'utf8',
130
+ env: process.env,
131
+ shell: npmNeedsShell(),
132
+ });
133
+ if (res.error)
134
+ return `npm run build:ui failed to start: ${res.error.message}`;
135
+ if (res.status !== 0) {
136
+ const detail = (res.stderr || res.stdout || '').trim().split('\n').slice(-8).join('\n');
137
+ return `npm run build:ui exited with code ${res.status}.${detail ? `\n${detail}` : ''}`;
138
+ }
139
+ return undefined;
140
+ }
141
+ /**
142
+ * Appends an app to `spec.apps` in `<parentDir>/mdk.yaml`. Unlike workers, apps are
143
+ * referenced by **package name** (resolved from node_modules), not a local path.
144
+ * No-ops (`'exists'`) when this app (same name and package) is already listed;
145
+ * `'name-taken'` when another package holds the name. The same package under
146
+ * another name is a second app.
147
+ */
148
+ export function addAppToStackFile(parentDir, packageName, appName) {
149
+ return appendToStackList(parentDir, 'apps', appName, (entry) => entry.get('name') === appName && entry.get('package') === packageName, (doc) => {
150
+ const mount = findCatalogApp(packageName)?.mount;
151
+ return doc.createNode({ name: appName, package: packageName, config: {}, ...(mount !== undefined ? { mount } : {}) });
152
+ });
153
+ }
154
+ /**
155
+ * Scaffolds a new app package (`mdk-app.yaml` + `plugin/` route table and
156
+ * controllers) from the bundled template into `<parentDir>/apps/<name>` —
157
+ * the linked layout `mdk run` resolves from node_modules. Substitutes the name
158
+ * tokens, sets the package.json and manifest `name`, links the package, and
159
+ * registers it under `spec.apps` in mdk.yaml.
160
+ */
161
+ export function createApp(opts) {
162
+ const name = opts.name.trim();
163
+ if (!NAME_RE.test(name)) {
164
+ return {
165
+ ok: false,
166
+ message: `Invalid app name "${opts.name}". Use lowercase letters, digits, '.', '_' or '-'.`,
167
+ };
168
+ }
169
+ const org = parseOrg(opts.org);
170
+ if (!org.ok)
171
+ return { ok: false, message: org.message };
172
+ const src = templateDir();
173
+ if (!existsSync(src)) {
174
+ return { ok: false, message: `App template not found at ${src}.` };
175
+ }
176
+ const appPath = resolve(opts.parentDir, DIRS.apps, name);
177
+ if (appPath === resolve(opts.parentDir, DIRS.dashboard)) {
178
+ return {
179
+ ok: false,
180
+ message: `"${name}" is reserved for the dashboard (${DIRS.dashboard}, see \`mdk create dashboard\`); pick another app name.`,
181
+ };
182
+ }
183
+ if (existsSync(appPath) && !opts.force) {
184
+ return { ok: false, message: `${appPath} already exists (use --force to overwrite).` };
185
+ }
186
+ try {
187
+ mkdirSync(resolve(opts.parentDir, DIRS.apps), { recursive: true });
188
+ const appName = toAppName(name);
189
+ // Scope the package name when an org is given; the manifest name mirrors it
190
+ // so logs and the mdk.yaml entry agree. A new app starts at the manifest's
191
+ // version, not the template's release pin.
192
+ const packageName = org.org ? `@${org.org}/${name}` : name;
193
+ copyTemplate(src, appPath, { [NAME_TOKEN]: name, [APP_TOKEN]: appName, [PACKAGE_TOKEN]: packageName });
194
+ updateJson(join(appPath, 'package.json'), { name: packageName, version: '0.1.0' });
195
+ updateJson(join(appPath, 'plugin', 'mdk-plugin.json'), { name: packageName });
196
+ writeStarterUi(appPath, name);
197
+ const linkWarning = linkIntoProject(opts.parentDir, packageName, appPath);
198
+ const install = installScaffold(opts.parentDir, appPath, opts.install !== false);
199
+ const stackFile = opts.updateStackFile === false ? undefined : addAppToStackFile(opts.parentDir, packageName, appName);
200
+ const uiWarning = opts.install === false ? 'install skipped (--no-install)' : buildStarterUi(appPath);
201
+ return {
202
+ ok: true,
203
+ appPath,
204
+ packageName,
205
+ appName,
206
+ installWarning: linkWarning ?? (install.ok ? undefined : install.message),
207
+ installDir: install.dir,
208
+ stackFile,
209
+ uiBuilt: uiWarning === undefined,
210
+ ...(uiWarning !== undefined ? { uiWarning } : {}),
211
+ };
212
+ }
213
+ catch (error) {
214
+ return {
215
+ ok: false,
216
+ message: 'Could not scaffold the app.',
217
+ detail: error instanceof Error ? error.message : String(error),
218
+ };
219
+ }
220
+ }
221
+ //# sourceMappingURL=app-scaffold.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app-scaffold.js","sourceRoot":"","sources":["../../src/lib/app-scaffold.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join, resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\nimport { checkoutRoot } from './checkout.js';\nimport { installDeps, installScaffold, npmBin, npmNeedsShell } from './npm.js';\nimport { pkg as cliPkg } from './pkg.js';\nimport { DIRS } from './project.js';\nimport { NAME_RE, parseOrg } from './names.js';\nimport { appendToStackList, copyTemplate, linkIntoProject, updateJson, type StackFileUpdate } from './scaffold.js';\nimport { toAppName } from './spec.js';\nimport { findCatalogApp } from './catalog.js';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\n/** Placeholders baked into `templates/app/**`, replaced at scaffold time. */\nconst NAME_TOKEN = '__PLUGIN_NAME__';\n/** The `spec.apps[].name` (see `toAppName`), also the last segment of the `mdk-app.yaml` id. */\nconst APP_TOKEN = '__PLUGIN_APP__';\n/** The package name, scoped under `--org` when given. */\nconst PACKAGE_TOKEN = '__PACKAGE_NAME__';\n\n/**\n * Absolute path to the bundled app template (`templates/app`).\n * Resolves the same from `dist/lib` (built) and `src/lib` (tsx dev) since both\n * sit one level under the package root.\n */\nfunction templateDir(): string {\n return resolve(__dirname, '..', '..', 'templates', 'app');\n}\n\nconst CLI_ROOT = resolve(__dirname, '..', '..');\n\n/** The starter UI (`templates/app-ui`): `ui-src/` with its own Vite build. */\nexport function appUiTemplateDir(): string {\n return resolve(CLI_ROOT, 'templates', 'app-ui');\n}\n\n/** The MDK UI packages the starter imports, by their directory under `ui/packages/`. */\nconst MDK_UI_PACKAGES: Record<string, string> = {\n '@tetherto/mdk-ui-foundation': 'ui-foundation',\n '@tetherto/mdk-react-adapter': 'react-adapter',\n '@tetherto/mdk-react-devkit': 'react-devkit',\n};\n\n/**\n * What the starter UI needs to build, in `ui-src/package.json` — its own\n * package, so building the UI never installs the app's backend dependencies.\n * None of it ships with the app: the built `ui/index.js` leaves React and the\n * MDK packages to the dashboard. Inside an MDK checkout the MDK packages link to\n * it, like a scaffolded dashboard; elsewhere they take the CLI's published range.\n */\nfunction uiDevDependencies(): Record<string, string> {\n const root = checkoutRoot(CLI_ROOT);\n const mdk = Object.fromEntries(\n Object.entries(MDK_UI_PACKAGES).map(([name, dir]) => [\n name,\n root ? `file:${join(root, 'ui', 'packages', dir)}` : `^${cliPkg.version}`,\n ]),\n );\n return {\n ...mdk,\n '@tanstack/react-query': '^5.59.0',\n '@types/react': '^19.2.14',\n '@vitejs/plugin-react': '^5.1.4',\n react: '^19.2.0',\n 'react-dom': '^19.2.0',\n 'react-router': '^8.3.0',\n typescript: '^7.0.2',\n vite: '^7.0.0',\n };\n}\n\nconst writeJson = (file: string, value: unknown): void =>\n writeFileSync(file, `${JSON.stringify(value, null, 2)}\\n`, 'utf8');\n\n/**\n * Writes the starter UI into `<appPath>/ui-src/` (one page and one Home panel\n * showing the app's `summary` route, across the three MDK UI layers), its own\n * package.json and tsconfig, the `build:ui` / `dev:ui` / `typecheck:ui` scripts,\n * and a placeholder `ui/index.js` the build replaces.\n */\nfunction writeStarterUi(appPath: string, name: string): void {\n const manifest = parseYaml(readFileSync(join(appPath, 'mdk-app.yaml'), 'utf8')) as { id: string; displayName: string };\n const pkgFile = join(appPath, 'package.json');\n const packageName = (JSON.parse(readFileSync(pkgFile, 'utf8')) as { name: string }).name;\n const uiSrc = join(appPath, 'ui-src');\n\n // Tokens are slugs or come from the generated manifest, so they are safe\n // inside the string literals and class names they replace.\n copyTemplate(join(appUiTemplateDir(), 'ui-src'), uiSrc, {\n __APP_NAME__: name,\n __APP_ID__: manifest.id,\n __APP_DISPLAY_NAME__: manifest.displayName,\n __APP_CSS__: name.replace(/[^a-z0-9_-]/g, '-'),\n });\n writeJson(join(uiSrc, 'tsconfig.json'), {\n compilerOptions: {\n target: 'ES2022',\n lib: ['ES2022', 'DOM', 'DOM.Iterable'],\n module: 'ESNext',\n moduleResolution: 'bundler',\n jsx: 'react-jsx',\n strict: true,\n noUncheckedIndexedAccess: true,\n skipLibCheck: true,\n noEmit: true,\n isolatedModules: true,\n },\n include: ['.'],\n });\n writeJson(join(uiSrc, 'package.json'), {\n name: `${packageName}-ui`,\n private: true,\n type: 'module',\n scripts: { build: 'vite build', dev: 'vite build --watch', typecheck: 'tsc --noEmit' },\n devDependencies: uiDevDependencies(),\n });\n writeFileSync(join(appPath, '.gitignore'), 'node_modules/\\n', 'utf8');\n updateJson(pkgFile, {\n // Publish what the app runs and shows; ui-src/ is local build input.\n files: ['mdk-app.yaml', 'plugin/', 'ui/', 'README.md'],\n scripts: {\n 'build:ui': 'npm --prefix ui-src run build',\n 'dev:ui': 'npm --prefix ui-src run dev',\n 'typecheck:ui': 'npm --prefix ui-src run typecheck',\n },\n });\n\n // A placeholder the build replaces: the app is a valid package, and loads in\n // the dashboard, even before (or without) its UI is built.\n mkdirSync(join(appPath, 'ui'), { recursive: true });\n writeFileSync(\n join(appPath, 'ui', 'index.js'),\n `export default ${JSON.stringify({ id: manifest.id, displayName: manifest.displayName, routes: [], panels: [] }, null, 2)};\\n`,\n 'utf8',\n );\n}\n\n/** Installs the starter UI's build dependencies and builds `ui/`; returns why it did not, if it did not. */\nfunction buildStarterUi(appPath: string): string | undefined {\n const install = installDeps(join(appPath, 'ui-src'));\n if (!install.ok) return install.message;\n const res = spawnSync(npmBin(), ['run', 'build:ui'], {\n cwd: appPath,\n encoding: 'utf8',\n env: process.env,\n shell: npmNeedsShell(),\n });\n if (res.error) return `npm run build:ui failed to start: ${res.error.message}`;\n if (res.status !== 0) {\n const detail = (res.stderr || res.stdout || '').trim().split('\\n').slice(-8).join('\\n');\n return `npm run build:ui exited with code ${res.status}.${detail ? `\\n${detail}` : ''}`;\n }\n return undefined;\n}\n\nexport interface AppScaffoldOptions {\n /** App name — used for the folder, route ids/paths, and the package name. */\n name: string;\n /** Project directory; the app is created under `<parentDir>/apps/<name>`. */\n parentDir: string;\n /** npm scope/org for the generated package name (e.g. `demo` -> `@demo/<name>`). */\n org?: string;\n /** Overwrite the target if it already exists (default: false). */\n force?: boolean;\n /** Run `npm install` after scaffolding (default: true). */\n install?: boolean;\n /** Add the app to `<parentDir>/mdk.yaml` under `spec.apps` (default: true). */\n updateStackFile?: boolean;\n}\n\nexport interface AppScaffoldResult {\n ok: boolean;\n message?: string;\n detail?: string;\n /** Absolute path of the scaffolded app. */\n appPath?: string;\n /** Resolved package.json `name` — the value referenced in mdk.yaml. */\n packageName?: string;\n /** The `spec.apps[].name` the app is registered under. */\n appName?: string;\n /** Warning surfaced when `npm install` could not complete (scaffold still ok). */\n installWarning?: string;\n /** Directory `npm install` ran in (the workspace root for a linked app). */\n installDir?: string;\n /** How the `mdk.yaml` update went (so the caller can guide the user). */\n stackFile?: StackFileUpdate;\n /** True when the starter UI was built into `ui/`. */\n uiBuilt?: boolean;\n /** Why `ui/` still holds the placeholder (install or build failed, or was skipped). */\n uiWarning?: string;\n}\n\n/**\n * Appends an app to `spec.apps` in `<parentDir>/mdk.yaml`. Unlike workers, apps are\n * referenced by **package name** (resolved from node_modules), not a local path.\n * No-ops (`'exists'`) when this app (same name and package) is already listed;\n * `'name-taken'` when another package holds the name. The same package under\n * another name is a second app.\n */\nexport function addAppToStackFile(parentDir: string, packageName: string, appName: string): StackFileUpdate {\n return appendToStackList(\n parentDir,\n 'apps',\n appName,\n (entry) => entry.get('name') === appName && entry.get('package') === packageName,\n (doc) => {\n const mount = findCatalogApp(packageName)?.mount;\n return doc.createNode({ name: appName, package: packageName, config: {}, ...(mount !== undefined ? { mount } : {}) });\n },\n );\n}\n\n/**\n * Scaffolds a new app package (`mdk-app.yaml` + `plugin/` route table and\n * controllers) from the bundled template into `<parentDir>/apps/<name>` —\n * the linked layout `mdk run` resolves from node_modules. Substitutes the name\n * tokens, sets the package.json and manifest `name`, links the package, and\n * registers it under `spec.apps` in mdk.yaml.\n */\nexport function createApp(opts: AppScaffoldOptions): AppScaffoldResult {\n const name = opts.name.trim();\n if (!NAME_RE.test(name)) {\n return {\n ok: false,\n message: `Invalid app name \"${opts.name}\". Use lowercase letters, digits, '.', '_' or '-'.`,\n };\n }\n\n const org = parseOrg(opts.org);\n if (!org.ok) return { ok: false, message: org.message };\n\n const src = templateDir();\n if (!existsSync(src)) {\n return { ok: false, message: `App template not found at ${src}.` };\n }\n\n const appPath = resolve(opts.parentDir, DIRS.apps, name);\n if (appPath === resolve(opts.parentDir, DIRS.dashboard)) {\n return {\n ok: false,\n message: `\"${name}\" is reserved for the dashboard (${DIRS.dashboard}, see \\`mdk create dashboard\\`); pick another app name.`,\n };\n }\n if (existsSync(appPath) && !opts.force) {\n return { ok: false, message: `${appPath} already exists (use --force to overwrite).` };\n }\n\n try {\n mkdirSync(resolve(opts.parentDir, DIRS.apps), { recursive: true });\n const appName = toAppName(name);\n // Scope the package name when an org is given; the manifest name mirrors it\n // so logs and the mdk.yaml entry agree. A new app starts at the manifest's\n // version, not the template's release pin.\n const packageName = org.org ? `@${org.org}/${name}` : name;\n copyTemplate(src, appPath, { [NAME_TOKEN]: name, [APP_TOKEN]: appName, [PACKAGE_TOKEN]: packageName });\n updateJson(join(appPath, 'package.json'), { name: packageName, version: '0.1.0' });\n updateJson(join(appPath, 'plugin', 'mdk-plugin.json'), { name: packageName });\n writeStarterUi(appPath, name);\n\n const linkWarning = linkIntoProject(opts.parentDir, packageName, appPath);\n const install = installScaffold(opts.parentDir, appPath, opts.install !== false);\n const stackFile =\n opts.updateStackFile === false ? undefined : addAppToStackFile(opts.parentDir, packageName, appName);\n const uiWarning = opts.install === false ? 'install skipped (--no-install)' : buildStarterUi(appPath);\n\n return {\n ok: true,\n appPath,\n packageName,\n appName,\n installWarning: linkWarning ?? (install.ok ? undefined : install.message),\n installDir: install.dir,\n stackFile,\n uiBuilt: uiWarning === undefined,\n ...(uiWarning !== undefined ? { uiWarning } : {}),\n };\n } catch (error) {\n return {\n ok: false,\n message: 'Could not scaffold the app.',\n detail: error instanceof Error ? error.message : String(error),\n };\n }\n}\n"]}