@typecad/cuttlefish 0.1.0-alpha.2 → 1.0.0-alpha.10
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 +10 -10
- package/dist/api/board-types.d.ts +1 -1
- package/dist/api/config.d.ts +32 -0
- package/dist/api/index.d.ts +1 -1
- package/dist/api/schema/types.d.ts +15 -0
- package/dist/api/shared/async-runtime-static.d.ts +12 -1
- package/dist/api/shared/async-runtime-static.js +69 -24
- package/dist/api/shared/async-symbol-detector.d.ts +11 -0
- package/dist/api/shared/async-symbol-detector.js +140 -0
- package/dist/api/shared/async-types.d.ts +24 -0
- package/dist/api/shared/coop-scheduler.d.ts +60 -0
- package/dist/api/shared/coop-scheduler.js +149 -0
- package/dist/api/shared/display-adapter.d.ts +4 -3
- package/dist/api/shared/display-adapter.js +15 -118
- package/dist/api/shared/display-adapters/sdl.js +38 -31
- package/dist/api/shared/display-profile.d.ts +47 -25
- package/dist/api/shared/display-profile.js +31 -164
- package/dist/api/shared/framework-manifest-registry.d.ts +9 -0
- package/dist/api/shared/framework-manifest-registry.js +27 -0
- package/dist/api/shared/framework-manifest.d.ts +595 -0
- package/dist/api/shared/framework-manifest.js +193 -0
- package/dist/api/shared/glcdfont.d.ts +12 -0
- package/dist/api/shared/glcdfont.js +124 -0
- package/dist/api/shared/graphics-strategy.d.ts +37 -0
- package/dist/api/shared/hal-op-ir.d.ts +764 -1
- package/dist/api/shared/hal-op-ir.js +129 -1
- package/dist/api/shared/index.d.ts +20 -2
- package/dist/api/shared/index.js +25 -1
- package/dist/api/shared/ir-core.d.ts +4 -0
- package/dist/api/shared/ir-declarations.d.ts +6 -0
- package/dist/api/shared/native-display-op-resolver.d.ts +10 -0
- package/dist/api/shared/native-display-op-resolver.js +64 -0
- package/dist/api/shared/platform-strategy.d.ts +119 -2
- package/dist/api/shared/polyfill-helper-registry.js +18 -1
- package/dist/api/shared/promise-runtime.d.ts +1 -1
- package/dist/api/shared/promise-runtime.js +95 -13
- package/dist/api/shared/toolchain-types.d.ts +17 -0
- package/dist/api/shared/types.d.ts +8 -0
- package/dist/api/shared/validate-framework-manifest.d.ts +28 -0
- package/dist/api/shared/validate-framework-manifest.js +494 -0
- package/dist/api/shared/worker-runtime-polyfill.d.ts +22 -0
- package/dist/api/shared/worker-runtime-polyfill.js +34 -0
- package/dist/api/shared/worker-runtime.d.ts +69 -0
- package/dist/api/shared/worker-runtime.js +163 -0
- package/dist/cli-utils.d.ts +1 -0
- package/dist/cli-utils.js +3 -1
- package/dist/cli.js +249 -32
- package/dist/config-loader.d.ts +7 -2
- package/dist/config-loader.js +23 -3
- package/dist/config-schema.d.ts +112 -70
- package/dist/config-schema.js +14 -0
- package/dist/contract/board-generator.d.ts +17 -0
- package/dist/contract/board-generator.js +57 -0
- package/dist/contract/contract-parser.d.ts +217 -0
- package/dist/contract/contract-parser.js +224 -0
- package/dist/contract/index.d.ts +22 -0
- package/dist/contract/index.js +68 -0
- package/dist/create/board-codegen.js +4 -4
- package/dist/create/board-generators.js +4 -5
- package/dist/create/board-spec.d.ts +72 -75
- package/dist/create/board-spec.js +0 -1
- package/dist/create/framework-catalog.d.ts +64 -0
- package/dist/create/framework-catalog.js +136 -0
- package/dist/create/index.d.ts +5 -1
- package/dist/create/index.js +3 -1
- package/dist/create/init-scaffold.d.ts +12 -3
- package/dist/create/init-scaffold.js +49 -14
- package/dist/create/init-templates.d.ts +4 -0
- package/dist/create/init-templates.js +263 -17
- package/dist/create/init-wizard.js +41 -27
- package/dist/create/install-deps.d.ts +32 -0
- package/dist/create/install-deps.js +46 -0
- package/dist/debug/preprocessor.js +178 -39
- package/dist/debug/types.d.ts +23 -0
- package/dist/diagnostics/mermaid-builder.d.ts +1 -1
- package/dist/diagnostics/mermaid-builder.js +34 -24
- package/dist/emit/compliance/arxml-writer.d.ts +11 -0
- package/dist/emit/compliance/arxml-writer.js +34 -0
- package/dist/emit/compliance/compliance-context.d.ts +57 -0
- package/dist/emit/compliance/compliance-context.js +113 -0
- package/dist/emit/compliance/deviation-ledger.d.ts +27 -0
- package/dist/emit/compliance/deviation-ledger.js +47 -0
- package/dist/emit/compliance/deviation-writer.d.ts +30 -0
- package/dist/emit/compliance/deviation-writer.js +37 -0
- package/dist/emit/compliance/index.d.ts +7 -0
- package/dist/emit/compliance/index.js +6 -0
- package/dist/emit/compliance/rule-engine.d.ts +13 -0
- package/dist/emit/compliance/rule-engine.js +101 -0
- package/dist/emit/compliance/rules.d.ts +16 -0
- package/dist/emit/compliance/rules.js +191 -0
- package/dist/emit/compliance/types.d.ts +59 -0
- package/dist/emit/compliance/types.js +8 -0
- package/dist/emit/cpp-emitter.js +4 -3
- package/dist/emit/emitters/class-emitter.js +6 -1
- package/dist/emit/emitters/emitter-context.d.ts +28 -1
- package/dist/emit/emitters/function-emitter-impl.js +153 -53
- package/dist/emit/emitters/line-appender.js +19 -0
- package/dist/emit/emitters/line-marker.d.ts +38 -0
- package/dist/emit/emitters/line-marker.js +39 -0
- package/dist/emit/emitters/output-finalizer.d.ts +6 -0
- package/dist/emit/emitters/output-finalizer.js +101 -12
- package/dist/emit/emitters/setup.d.ts +17 -0
- package/dist/emit/emitters/setup.js +372 -30
- package/dist/emit/emitters/top-level-prep.js +8 -0
- package/dist/emit/emitters/type-decl-emitter.js +28 -3
- package/dist/emit/emitters/ui-emitter.js +33 -9
- package/dist/emit/expression-renderer.d.ts +1 -1
- package/dist/emit/expression-renderer.js +91 -6
- package/dist/emit/route-hal-op.js +18 -5
- package/dist/emit/snprintf-helpers.js +15 -4
- package/dist/emit/statement-renderer.d.ts +10 -0
- package/dist/emit/statement-renderer.js +34 -5
- package/dist/emit/utils/async-state-machine.js +221 -125
- package/dist/emit/utils/hal-op-cpp-type.d.ts +6 -0
- package/dist/emit/utils/hal-op-cpp-type.js +40 -0
- package/dist/framework-package.js +2 -0
- package/dist/framework-registry.d.ts +17 -0
- package/dist/install/framework-catalog.d.ts +53 -0
- package/dist/install/framework-catalog.js +107 -0
- package/dist/install/handle-install.d.ts +35 -0
- package/dist/install/handle-install.js +177 -0
- package/dist/install/index.d.ts +4 -0
- package/dist/install/index.js +3 -0
- package/dist/ir/adc-range-validation.js +40 -26
- package/dist/ir/build-ir-state.d.ts +1 -0
- package/dist/ir/build-ir-state.js +14 -0
- package/dist/ir/build-ir.js +9 -5
- package/dist/ir/call-graph.js +16 -0
- package/dist/ir/expression-to-ir.js +207 -6
- package/dist/ir/feature-registry.js +7 -25
- package/dist/ir/function-builder.js +22 -0
- package/dist/ir/hal/hal-emitter.d.ts +5 -2
- package/dist/ir/hal/hal-emitter.js +50 -18
- package/dist/ir/hal/hal-parser.d.ts +6 -0
- package/dist/ir/hal/hal-parser.js +81 -0
- package/dist/ir/hal/hal-plugins.js +733 -1
- package/dist/ir/identifier-collector.js +35 -0
- package/dist/ir/interrupt-analysis.d.ts +5 -1
- package/dist/ir/interrupt-analysis.js +16 -19
- package/dist/ir/memory-budget-validation.js +1 -0
- package/dist/ir/network-validation.d.ts +4 -0
- package/dist/ir/network-validation.js +184 -0
- package/dist/ir/ownership-analysis.js +39 -1
- package/dist/ir/peripheral-ownership.js +5 -0
- package/dist/ir/peripheral-validation.d.ts +1 -1
- package/dist/ir/peripheral-validation.js +6 -3
- package/dist/ir/pin-alias-conflict.d.ts +1 -1
- package/dist/ir/pin-alias-conflict.js +2 -1
- package/dist/ir/pin-capability-validation.js +71 -32
- package/dist/ir/pin-mode-validation.d.ts +2 -2
- package/dist/ir/pin-mode-validation.js +54 -18
- package/dist/ir/pin-safety.d.ts +1 -1
- package/dist/ir/pin-safety.js +2 -1
- package/dist/ir/program-analysis.d.ts +81 -0
- package/dist/ir/program-analysis.js +429 -0
- package/dist/ir/pulldown-validation.d.ts +1 -1
- package/dist/ir/pulldown-validation.js +2 -1
- package/dist/ir/pwm-timer-sharing.d.ts +1 -1
- package/dist/ir/pwm-timer-sharing.js +2 -1
- package/dist/ir/render-expr.js +11 -0
- package/dist/ir/resource-analysis.js +2 -0
- package/dist/ir/timer0-pwm-timing-conflict.d.ts +1 -1
- package/dist/ir/timer0-pwm-timing-conflict.js +2 -1
- package/dist/ir/timing-validation.d.ts +6 -1
- package/dist/ir/timing-validation.js +61 -13
- package/dist/ir/transformers/call-statement.js +105 -0
- package/dist/ir/transformers/expressions.js +62 -0
- package/dist/ir/transformers/hal-call-resolver.js +21 -0
- package/dist/ir/transformers/hal-emit-helpers.js +1 -1
- package/dist/ir/transformers/namespace-methods.js +17 -12
- package/dist/ir/transformers/ui-reactive.js +2 -2
- package/dist/ir/transformers/variables.js +137 -19
- package/dist/ir/try-catch-validation.js +2 -0
- package/dist/ir/type-resolution.js +20 -2
- package/dist/ir/ui-element-auto-wire.js +7 -5
- package/dist/ir/unit-suspicion-validation.js +9 -7
- package/dist/ir/utils/map-statements.d.ts +4 -0
- package/dist/ir/utils/map-statements.js +79 -0
- package/dist/ir/validation-orchestrator.js +15 -8
- package/dist/ir/worker-analysis.d.ts +10 -0
- package/dist/ir/worker-analysis.js +261 -0
- package/dist/libdef/c-to-decl.d.ts +27 -0
- package/dist/libdef/c-to-decl.js +397 -0
- package/dist/libdef/component-decls.d.ts +2 -0
- package/dist/libdef/component-decls.js +6 -0
- package/dist/libdef/component-discovery.d.ts +43 -0
- package/dist/libdef/component-discovery.js +83 -0
- package/dist/libdef/cpp-to-decl.d.ts +9 -0
- package/dist/libdef/cpp-to-decl.js +72 -0
- package/dist/libdef/registry.js +5 -2
- package/dist/lint-cache.d.ts +59 -0
- package/dist/lint-cache.js +257 -0
- package/dist/orchestrator/graph-builder.js +14 -9
- package/dist/platform/coop-scheduler-runtime.d.ts +19 -0
- package/dist/platform/coop-scheduler-runtime.js +41 -0
- package/dist/platform/generic-strategy.d.ts +15 -3
- package/dist/platform/generic-strategy.js +49 -4
- package/dist/safety/safety-bridge.d.ts +11 -0
- package/dist/safety/safety-bridge.js +48 -0
- package/dist/safety/sidecar-bridge.d.ts +5 -0
- package/dist/safety/sidecar-bridge.js +22 -0
- package/dist/safety-hook.d.ts +79 -0
- package/dist/safety-hook.js +35 -0
- package/dist/testing.d.ts +8 -2
- package/dist/testing.js +9 -2
- package/dist/transpile.d.ts +3 -0
- package/dist/transpile.js +191 -49
- package/dist/types.d.ts +22 -3
- package/dist/ui-hook.d.ts +17 -3
- package/dist/utils/cli.js +122 -2
- package/dist/utils/fs.d.ts +13 -0
- package/dist/utils/fs.js +50 -0
- package/dist/utils/ui.js +3 -1
- package/package.json +23 -3
- package/dist/api/shared/display-adapters/eink-mono.d.ts +0 -2
- package/dist/api/shared/display-adapters/eink-mono.js +0 -53
- package/dist/api/shared/display-adapters/ssd1309.d.ts +0 -2
- package/dist/api/shared/display-adapters/ssd1309.js +0 -136
- package/dist/api/shared/display-adapters/st7796.d.ts +0 -2
- package/dist/api/shared/display-adapters/st7796.js +0 -132
- package/dist/ir/heap-array-validation.d.ts +0 -24
- package/dist/ir/heap-array-validation.js +0 -29
|
@@ -2,11 +2,11 @@ import { LINT_RULES } from '../ir/feature-registry.js';
|
|
|
2
2
|
export function generateProjectPackageJson(options) {
|
|
3
3
|
const { projectName, frameworkPackage, boardPackage } = options;
|
|
4
4
|
const deps = {
|
|
5
|
-
"@typecad/cuttlefish": "^
|
|
6
|
-
[frameworkPackage]: "^
|
|
5
|
+
"@typecad/cuttlefish": "^1.0.0-alpha.3",
|
|
6
|
+
[frameworkPackage]: "^1.0.0-alpha.3",
|
|
7
7
|
};
|
|
8
8
|
if (boardPackage) {
|
|
9
|
-
deps[boardPackage] = "^
|
|
9
|
+
deps[boardPackage] = "^1.0.0-alpha.3";
|
|
10
10
|
}
|
|
11
11
|
const depsJson = Object.entries(deps)
|
|
12
12
|
.map(([k, v]) => ` "${k}": "${v}"`)
|
|
@@ -15,12 +15,27 @@ export function generateProjectPackageJson(options) {
|
|
|
15
15
|
// eslint-transpiler-rules plugin (which is plain JS, no dep). Without these
|
|
16
16
|
// devDependencies `npm run lint` fails to resolve the parser/plugin in a
|
|
17
17
|
// freshly created project. Versions mirror the repo demo's package.json.
|
|
18
|
-
const
|
|
18
|
+
const baseDevDeps = [
|
|
19
19
|
' "eslint": "^10.4.1"',
|
|
20
20
|
' "@typescript-eslint/parser": "^8.61.0"',
|
|
21
21
|
' "@typescript-eslint/eslint-plugin": "^8.61.0"',
|
|
22
|
-
]
|
|
22
|
+
];
|
|
23
|
+
// Developer-utility scripts shared by every target (native + embedded).
|
|
24
|
+
// None require hardware or extra dependencies:
|
|
25
|
+
// dev — auto-retranspile on save (transpile-only; append --compile
|
|
26
|
+
// to also compile on each change). The fast "does it
|
|
27
|
+
// typecheck" feedback loop.
|
|
28
|
+
// gen-decls — generate TypeScript .d.ts from C++ headers. File-argument:
|
|
29
|
+
// `npm run gen-decls -- lib/foo.h` or `-- --all lib/`.
|
|
30
|
+
// gen-libdefs — generate library-definition stubs from a TS file's imports.
|
|
31
|
+
// File-argument: `npm run gen-libdefs -- src/main.ts`.
|
|
32
|
+
const devScripts = [
|
|
33
|
+
'"dev": "cuttlefish build --watch"',
|
|
34
|
+
'"gen-decls": "cuttlefish gen-decls"',
|
|
35
|
+
'"gen-libdefs": "cuttlefish gen-libdefs"',
|
|
36
|
+
];
|
|
23
37
|
if (options.isNative) {
|
|
38
|
+
const devDepsJson = baseDevDeps.join(',\n');
|
|
24
39
|
return `{
|
|
25
40
|
"name": "${projectName}",
|
|
26
41
|
"version": "1.0.0",
|
|
@@ -28,7 +43,8 @@ export function generateProjectPackageJson(options) {
|
|
|
28
43
|
"scripts": {
|
|
29
44
|
"build": "cuttlefish build",
|
|
30
45
|
"compile": "cuttlefish build --compile",
|
|
31
|
-
"lint": "eslint --config .cuttlefish/eslint.config.mjs src/"
|
|
46
|
+
"lint": "eslint --config .cuttlefish/eslint.config.mjs src/",
|
|
47
|
+
${devScripts.join(',\n ')}
|
|
32
48
|
},
|
|
33
49
|
"dependencies": {
|
|
34
50
|
${depsJson}
|
|
@@ -39,6 +55,19 @@ ${devDepsJson}
|
|
|
39
55
|
}
|
|
40
56
|
`;
|
|
41
57
|
}
|
|
58
|
+
// Embedded projects get two host-side testing tiers:
|
|
59
|
+
// - @typecad/expect: hardware tests run on the board via cuttlefish-test
|
|
60
|
+
// (`npm run test:hw`), scoped to tests/**/*.test.ts.
|
|
61
|
+
// - @typecad/simulator + vitest: simulate the board in Node (`npm run
|
|
62
|
+
// simulate`), scoped to sim/**/*.test.ts so vitest never collides with the
|
|
63
|
+
// @typecad/expect no-op stubs under tests/.
|
|
64
|
+
// Versions mirror the workspace's published releases / root devDeps.
|
|
65
|
+
const devDepsJson = [
|
|
66
|
+
...baseDevDeps,
|
|
67
|
+
' "@typecad/expect": "^1.0.0-alpha.3"',
|
|
68
|
+
' "@typecad/simulator": "^1.0.0-alpha.3"',
|
|
69
|
+
' "vitest": "^4.0.18"',
|
|
70
|
+
].join(',\n');
|
|
42
71
|
return `{
|
|
43
72
|
"name": "${projectName}",
|
|
44
73
|
"version": "1.0.0",
|
|
@@ -48,7 +77,10 @@ ${devDepsJson}
|
|
|
48
77
|
"compile": "cuttlefish build --compile",
|
|
49
78
|
"upload": "cuttlefish build --compile --upload",
|
|
50
79
|
"monitor": "cuttlefish build --compile --upload --monitor",
|
|
51
|
-
"
|
|
80
|
+
"test:hw": "npm exec -- cuttlefish-test",
|
|
81
|
+
"simulate": "vitest run sim/",
|
|
82
|
+
"lint": "eslint --config .cuttlefish/eslint.config.mjs src/",
|
|
83
|
+
${devScripts.join(',\n ')}
|
|
52
84
|
},
|
|
53
85
|
"dependencies": {
|
|
54
86
|
${depsJson}
|
|
@@ -85,9 +117,10 @@ export function generateProjectTsconfig(options) {
|
|
|
85
117
|
"noEmit": true,
|
|
86
118
|
"resolveJsonModule": true,
|
|
87
119
|
"allowArbitraryExtensions": true,
|
|
120
|
+
"allowImportingTsExtensions": true,
|
|
88
121
|
"rootDirs": ["src", "types"]${paths}
|
|
89
122
|
},
|
|
90
|
-
"include": ["src/**/*.ts", "types/**/*.ts", "cuttlefish.config.ts"${options.boardPackage ? ', ".cuttlefish/cuttlefish-env.d.ts"' : ''}]
|
|
123
|
+
"include": ["src/**/*.ts", "types/**/*.ts", "cuttlefish.config.ts"${options.boardPackage ? ', ".cuttlefish/cuttlefish-env.d.ts"' : ''}${options.isNative ? '' : ', "sim/**/*.ts"'}]
|
|
91
124
|
}
|
|
92
125
|
`;
|
|
93
126
|
}
|
|
@@ -117,8 +150,12 @@ export default config;
|
|
|
117
150
|
`;
|
|
118
151
|
}
|
|
119
152
|
const buildTarget = options.buildTarget;
|
|
120
|
-
const resolvedToolchain = options.toolchainType
|
|
121
|
-
|
|
153
|
+
const resolvedToolchain = options.toolchainType
|
|
154
|
+
?? 'arduino-cli';
|
|
155
|
+
let frameworkDataBlock = '';
|
|
156
|
+
if (buildTarget) {
|
|
157
|
+
frameworkDataBlock = `\n // Framework data\n frameworkData: {\n buildTarget: '${buildTarget}',\n },`;
|
|
158
|
+
}
|
|
122
159
|
const mcuLine = options.mcu
|
|
123
160
|
? `\n // MCU package — provides silicon-level pin definitions\n mcu: '${options.mcu.startsWith('@') ? options.mcu : `@typecad/mcu-${options.mcu}`}',\n`
|
|
124
161
|
: '';
|
|
@@ -127,6 +164,11 @@ export default config;
|
|
|
127
164
|
: '';
|
|
128
165
|
const portHint = process.platform === 'win32' ? 'COM4' : '/dev/ttyACM0';
|
|
129
166
|
const baudLine = options.baudRate ? `\n\n // Console polyfill configuration\n console: {\n baudRate: ${options.baudRate},\n // Serial port for upload/monitor. Override with --port on the CLI.\n port: '${portHint}',\n },` : '';
|
|
167
|
+
// Hardware test runner configuration — used by \`npm run test:hw\` (cuttlefish-test,
|
|
168
|
+
// provided by @typecad/expect). It transpiles each tests/**/*.test.ts file,
|
|
169
|
+
// flashes it to the board, and evaluates the assertions over serial.
|
|
170
|
+
const resolvedBaud = options.baudRate ?? 115200;
|
|
171
|
+
const testLine = `\n\n // Hardware test runner (@typecad/expect / \`npm run test:hw\`)\n test: {\n // Serial port for the test board. Override with --port on the CLI or the\n // CUTTLEFISH_PORT env var (e.g. CUTTLEFISH_PORT=/dev/ttyUSB0 npm run test:hw).\n port: '${portHint}',\n baudRate: ${resolvedBaud},\n timeout: 30000,\n include: ['tests/**/*.test.ts'],\n },`;
|
|
130
172
|
return `// ---------------------------------------------------------------------------
|
|
131
173
|
// cuttlefish.config.ts — Project configuration
|
|
132
174
|
//
|
|
@@ -143,7 +185,7 @@ const config: CuttlefishConfig = {
|
|
|
143
185
|
target: '${options.architecture}',${mcuLine}${boardLine}
|
|
144
186
|
|
|
145
187
|
// Framework package — controls code generation strategy
|
|
146
|
-
framework: '${options.frameworkPackage}',${
|
|
188
|
+
framework: '${options.frameworkPackage}',${frameworkDataBlock}
|
|
147
189
|
|
|
148
190
|
// Output / build options
|
|
149
191
|
output: {
|
|
@@ -155,7 +197,7 @@ const config: CuttlefishConfig = {
|
|
|
155
197
|
// Toolchain configuration
|
|
156
198
|
toolchain: {
|
|
157
199
|
type: '${resolvedToolchain}',
|
|
158
|
-
},${baudLine}
|
|
200
|
+
},${baudLine}${testLine}
|
|
159
201
|
};
|
|
160
202
|
|
|
161
203
|
export default config;
|
|
@@ -173,6 +215,22 @@ declare global {
|
|
|
173
215
|
type Owned<T = unknown> = T;
|
|
174
216
|
type Shared<T = unknown> = T;
|
|
175
217
|
type Mutable<T = unknown> = T;
|
|
218
|
+
// SafeVariable: SEU-resistant storage. The transpiler lowers SafeVariable<number>
|
|
219
|
+
// to a C++ template with inverted-redundancy storage. Declared as an interface
|
|
220
|
+
// (not a type alias) so the TS type checker recognizes method calls.
|
|
221
|
+
// Arithmetic T only (integral or floating-point); string is rejected by a
|
|
222
|
+
// static_assert in the emitted C++ template.
|
|
223
|
+
interface SafeVariable<T = number> { set(value: T): void; get(): T; valid(): boolean; hasFault(): boolean; }
|
|
224
|
+
// SafeInt: chainable bounds-checked signed-integer arithmetic. The transpiler
|
|
225
|
+
// lowers SafeInt<number> to SafeInt<int32_t> (a C++ template with sticky-fault
|
|
226
|
+
// overflow detection). Signed integer T only — unsigned/bool/float/string are
|
|
227
|
+
// rejected by a static_assert in the emitted C++ template.
|
|
228
|
+
interface SafeInt<T = number> {
|
|
229
|
+
add(delta: T): SafeInt<T>; sub(delta: T): SafeInt<T>;
|
|
230
|
+
mul(factor: T): SafeInt<T>; divide(d: T): SafeInt<T>; mod(d: T): SafeInt<T>;
|
|
231
|
+
negate(): SafeInt<T>; absValue(): SafeInt<T>;
|
|
232
|
+
get(): T; hasFault(): boolean; valid(): boolean; reset(newValue: T): void;
|
|
233
|
+
}
|
|
176
234
|
|
|
177
235
|
type uint8_t = number;
|
|
178
236
|
type int8_t = number;
|
|
@@ -198,6 +256,20 @@ declare global {
|
|
|
198
256
|
error(...args: unknown[]): void;
|
|
199
257
|
}
|
|
200
258
|
const console: Console;
|
|
259
|
+
|
|
260
|
+
// Convenience helper for volatile variables in TypeCAD programs.
|
|
261
|
+
// The transpiler detects calls to volatile() and emits the C++ volatile qualifier.
|
|
262
|
+
declare function volatile<T>(value: T): T;
|
|
263
|
+
|
|
264
|
+
// JS-style timers (declared here so a project does not need "dom" in tsconfig
|
|
265
|
+
// lib to type setInterval/setTimeout). The transpiler rewrites these to
|
|
266
|
+
// __tc_setInterval/__tc_setTimeout and emits the timer_methods polyfill when
|
|
267
|
+
// used. Note: Timing.setInterval/setTimeout (from @typecad/hal) is the
|
|
268
|
+
// preferred single entry point and is typed via the @typecad/board import.
|
|
269
|
+
declare function setInterval(handler: () => void, timeout?: number): number;
|
|
270
|
+
declare function setTimeout(handler: () => void, timeout?: number): number;
|
|
271
|
+
declare function clearInterval(id: number): void;
|
|
272
|
+
declare function clearTimeout(id: number): void;
|
|
201
273
|
}
|
|
202
274
|
|
|
203
275
|
export {};
|
|
@@ -216,6 +288,22 @@ declare global {
|
|
|
216
288
|
type Owned<T = unknown> = T;
|
|
217
289
|
type Shared<T = unknown> = T;
|
|
218
290
|
type Mutable<T = unknown> = T;
|
|
291
|
+
// SafeVariable: SEU-resistant storage. The transpiler lowers SafeVariable<number>
|
|
292
|
+
// to a C++ template with inverted-redundancy storage. Declared as an interface
|
|
293
|
+
// (not a type alias) so the TS type checker recognizes method calls.
|
|
294
|
+
// Arithmetic T only (integral or floating-point); string is rejected by a
|
|
295
|
+
// static_assert in the emitted C++ template.
|
|
296
|
+
interface SafeVariable<T = number> { set(value: T): void; get(): T; valid(): boolean; hasFault(): boolean; }
|
|
297
|
+
// SafeInt: chainable bounds-checked signed-integer arithmetic. The transpiler
|
|
298
|
+
// lowers SafeInt<number> to SafeInt<int32_t> (a C++ template with sticky-fault
|
|
299
|
+
// overflow detection). Signed integer T only — unsigned/bool/float/string are
|
|
300
|
+
// rejected by a static_assert in the emitted C++ template.
|
|
301
|
+
interface SafeInt<T = number> {
|
|
302
|
+
add(delta: T): SafeInt<T>; sub(delta: T): SafeInt<T>;
|
|
303
|
+
mul(factor: T): SafeInt<T>; divide(d: T): SafeInt<T>; mod(d: T): SafeInt<T>;
|
|
304
|
+
negate(): SafeInt<T>; absValue(): SafeInt<T>;
|
|
305
|
+
get(): T; hasFault(): boolean; valid(): boolean; reset(newValue: T): void;
|
|
306
|
+
}
|
|
219
307
|
|
|
220
308
|
type uint8_t = number;
|
|
221
309
|
type int8_t = number;
|
|
@@ -241,6 +329,20 @@ declare global {
|
|
|
241
329
|
error(...args: unknown[]): void;
|
|
242
330
|
}
|
|
243
331
|
const console: Console;
|
|
332
|
+
|
|
333
|
+
// Convenience helper for volatile variables in TypeCAD programs.
|
|
334
|
+
// The transpiler detects calls to volatile() and emits the C++ volatile qualifier.
|
|
335
|
+
declare function volatile<T>(value: T): T;
|
|
336
|
+
|
|
337
|
+
// JS-style timers (declared here so a project does not need "dom" in tsconfig
|
|
338
|
+
// lib to type setInterval/setTimeout). The transpiler rewrites these to
|
|
339
|
+
// __tc_setInterval/__tc_setTimeout and emits the timer_methods polyfill when
|
|
340
|
+
// used. Note: Timing.setInterval/setTimeout (from @typecad/hal) is the
|
|
341
|
+
// preferred single entry point and is typed via the @typecad/board import.
|
|
342
|
+
declare function setInterval(handler: () => void, timeout?: number): number;
|
|
343
|
+
declare function setTimeout(handler: () => void, timeout?: number): number;
|
|
344
|
+
declare function clearInterval(id: number): void;
|
|
345
|
+
declare function clearTimeout(id: number): void;
|
|
244
346
|
}
|
|
245
347
|
|
|
246
348
|
declare module '@typecad/board' {
|
|
@@ -286,6 +388,148 @@ while (true) {
|
|
|
286
388
|
}
|
|
287
389
|
`;
|
|
288
390
|
}
|
|
391
|
+
export function generateStarterTest(_options) {
|
|
392
|
+
return `// ---------------------------------------------------------------------------
|
|
393
|
+
// Hardware test — Basics
|
|
394
|
+
//
|
|
395
|
+
// Runs on the board via \`npm run test:hw\` (cuttlefish-test). Each test file is
|
|
396
|
+
// transpiled, flashed to the board, and its assertions are evaluated on the host
|
|
397
|
+
// over serial. Change the serial port in cuttlefish.config.ts (the \`test.port\`
|
|
398
|
+
// field) or override it with the CUTTLEFISH_PORT env var.
|
|
399
|
+
//
|
|
400
|
+
// API: describe(...).it(...).expect(value).<matcher>() chains. Import pin
|
|
401
|
+
// objects from '@typecad/board' to assert on real hardware I/O. Every file ends
|
|
402
|
+
// with done().
|
|
403
|
+
// ---------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
import { describe, done } from '@typecad/expect';
|
|
406
|
+
|
|
407
|
+
describe("Basics")
|
|
408
|
+
.it("adds two numbers")
|
|
409
|
+
.expect(
|
|
410
|
+
(() => {
|
|
411
|
+
const a = 1;
|
|
412
|
+
let b = 2;
|
|
413
|
+
return a + b;
|
|
414
|
+
})
|
|
415
|
+
).toBe(3)
|
|
416
|
+
.it("multiplies two numbers")
|
|
417
|
+
.expect(
|
|
418
|
+
(() => {
|
|
419
|
+
let a = 3;
|
|
420
|
+
let b = 4;
|
|
421
|
+
return a * b;
|
|
422
|
+
})
|
|
423
|
+
).toBe(12)
|
|
424
|
+
.it("reads an array element")
|
|
425
|
+
.expect(
|
|
426
|
+
(() => {
|
|
427
|
+
const data = new Uint8Array([0xAA, 0x10, 0x20]);
|
|
428
|
+
return data[1];
|
|
429
|
+
})
|
|
430
|
+
).toBe(0x10)
|
|
431
|
+
.it("clamps a value to a range")
|
|
432
|
+
.expect(
|
|
433
|
+
(() => {
|
|
434
|
+
const value = 2000;
|
|
435
|
+
return Math.max(0, Math.min(1023, value));
|
|
436
|
+
})
|
|
437
|
+
).toBe(1023);
|
|
438
|
+
|
|
439
|
+
done();
|
|
440
|
+
`;
|
|
441
|
+
}
|
|
442
|
+
export function generateStarterSim(options) {
|
|
443
|
+
const boardType = options.targetId;
|
|
444
|
+
return `// ---------------------------------------------------------------------------
|
|
445
|
+
// Hardware simulation — Button + LED
|
|
446
|
+
//
|
|
447
|
+
// Runs entirely on your computer with \`npm run simulate\` (vitest + the
|
|
448
|
+
// @typecad/simulator package). No board, serial port, or arduino-cli required.
|
|
449
|
+
// The simulator mirrors the pins/peripherals of your ${options.targetDisplayName}
|
|
450
|
+
// (${boardType}); you inject fake inputs and assert on the outputs in Node.
|
|
451
|
+
//
|
|
452
|
+
// This is the fast tier — iterate on logic here, then confirm on real hardware
|
|
453
|
+
// with \`npm run test:hw\` (which flashes tests/ to the board).
|
|
454
|
+
// ---------------------------------------------------------------------------
|
|
455
|
+
|
|
456
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
457
|
+
import {
|
|
458
|
+
createSimBoard,
|
|
459
|
+
type SimBoard,
|
|
460
|
+
type SimDigitalPin,
|
|
461
|
+
} from "@typecad/simulator";
|
|
462
|
+
|
|
463
|
+
// ===========================================================================
|
|
464
|
+
// FIRMWARE LOGIC
|
|
465
|
+
// ---------------------------------------------------------------------------
|
|
466
|
+
// Factor your firmware into a function that takes the simulated pins as
|
|
467
|
+
// arguments. In a real project this same logic runs on the board against real
|
|
468
|
+
// pins — here it runs against the sim board so you can test it without hardware.
|
|
469
|
+
// ===========================================================================
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Reads a button and reflects its state on an LED.
|
|
473
|
+
*
|
|
474
|
+
* To use your own logic: replace the body of this function with whatever your
|
|
475
|
+
* firmware does (read a sensor, drive a motor, print to serial, ...). As long
|
|
476
|
+
* as it only touches pins you pass in, the simulator can exercise it.
|
|
477
|
+
*/
|
|
478
|
+
function reflectButtonOnLed(button: SimDigitalPin, led: SimDigitalPin): void {
|
|
479
|
+
// The button pin is pulled HIGH (1) at rest and reads LOW (0) when pressed.
|
|
480
|
+
if (button.isLow()) {
|
|
481
|
+
led.high();
|
|
482
|
+
} else {
|
|
483
|
+
led.low();
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ===========================================================================
|
|
488
|
+
// TEST BENCH
|
|
489
|
+
// ---------------------------------------------------------------------------
|
|
490
|
+
// \`createSimBoard\` builds an in-memory version of your board. The pin numbers
|
|
491
|
+
// below match the physical pinout. Add the pins/peripherals your firmware uses:
|
|
492
|
+
// board.digital(n), board.analog(n), board.pwm(n), board.serial(n),
|
|
493
|
+
// board.i2c(n), board.spi(n), board.interrupt(n).
|
|
494
|
+
// ===========================================================================
|
|
495
|
+
|
|
496
|
+
function setupSim(): { board: SimBoard; button: SimDigitalPin; led: SimDigitalPin } {
|
|
497
|
+
// boardType mirrors the target chosen with \`cuttlefish create\`.
|
|
498
|
+
const board = createSimBoard({ boardType: "${boardType}" });
|
|
499
|
+
|
|
500
|
+
const button = board.digital(2).asInputPullUp(); // button on pin 2 (INPUT_PULLUP)
|
|
501
|
+
const led = board.digital(13).asOutput(false); // LED on pin 13
|
|
502
|
+
|
|
503
|
+
return { board, button, led };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
describe("Button + LED (simulator)", () => {
|
|
507
|
+
beforeEach(() => {
|
|
508
|
+
// A fresh board per test keeps state isolated. For a long-running sim you
|
|
509
|
+
// can call board.reset() between cycles instead.
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
it("keeps the LED off while the button is released", () => {
|
|
513
|
+
const { button, led } = setupSim();
|
|
514
|
+
|
|
515
|
+
// Button at rest: INPUT_PULLUP reads HIGH.
|
|
516
|
+
reflectButtonOnLed(button, led);
|
|
517
|
+
|
|
518
|
+
expect(led.getBitValue()).toBe(0);
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it("turns the LED on while the button is pressed", () => {
|
|
522
|
+
const { button, led } = setupSim();
|
|
523
|
+
|
|
524
|
+
// Simulate a press: drive the button pin LOW.
|
|
525
|
+
button.injectValue(0);
|
|
526
|
+
reflectButtonOnLed(button, led);
|
|
527
|
+
|
|
528
|
+
expect(led.getBitValue()).toBe(1);
|
|
529
|
+
});
|
|
530
|
+
});
|
|
531
|
+
`;
|
|
532
|
+
}
|
|
289
533
|
export function generateBoardForwardingFile(boardPackage) {
|
|
290
534
|
return `// ---------------------------------------------------------------------------
|
|
291
535
|
// .cuttlefish/board.ts — Dynamically generated forwarding board package
|
|
@@ -328,11 +572,13 @@ export default [
|
|
|
328
572
|
files: ["src/**/*.ts"],
|
|
329
573
|
languageOptions: {
|
|
330
574
|
parser: tsparser,
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
575
|
+
// NOTE: do NOT set parserOptions.project here. None of the rules below
|
|
576
|
+
// (no-restricted-syntax, the cuttlefish/* AST rules, no-explicit-any,
|
|
577
|
+
// no-eval, ...) consume type information, so enabling type-aware linting
|
|
578
|
+
// only forces ESLint to build a full TS type-program per file — ~3.4s of
|
|
579
|
+
// pure overhead on small projects with zero change to what is detected.
|
|
580
|
+
// If a future rule needs types, scope project to that rule only via
|
|
581
|
+
// parserOptions on a dedicated config block, not globally.
|
|
336
582
|
},
|
|
337
583
|
plugins: {
|
|
338
584
|
"@typescript-eslint": tseslint,
|
|
@@ -2,6 +2,7 @@ import * as readline from "node:readline/promises";
|
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import { KNOWN_TARGETS } from "./init-scaffold.js";
|
|
5
|
+
import { frameworksForTarget, frameworkCatalogEntry, FRAMEWORK_CATALOG, frameworkTargetProfile } from "./framework-catalog.js";
|
|
5
6
|
function validateProjectName(name) {
|
|
6
7
|
if (!name || name.trim().length === 0) {
|
|
7
8
|
return "Project name cannot be empty.";
|
|
@@ -84,34 +85,43 @@ export async function runInitWizard(partialOptions) {
|
|
|
84
85
|
targetId = await promptSelect(rl, "Target", targetOptions);
|
|
85
86
|
}
|
|
86
87
|
const target = KNOWN_TARGETS.find((t) => t.id === targetId);
|
|
87
|
-
// 3. Framework
|
|
88
|
+
// 3. Framework. Narrow to the frameworks compatible with the selected board
|
|
89
|
+
// (see framework-catalog), then let the user pick. The chosen package is
|
|
90
|
+
// installed into the new project by `cuttlefish create`, so we offer every
|
|
91
|
+
// compatible framework regardless of what is currently installed — no
|
|
92
|
+
// require.resolve discovery (which also avoided an ESM `require` pitfall
|
|
93
|
+
// where the lookup always failed and made the wizard dead-end).
|
|
88
94
|
let framework = target.framework;
|
|
89
95
|
let frameworkPackage = target.frameworkPackage;
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
if (
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
frameworkPackage = match?.pkg ?? `@typecad/framework-${partialOptions.framework}`;
|
|
101
|
-
console.log(`${chalk.cyan("?")} Framework: ${chalk.white(framework)}`);
|
|
102
|
-
}
|
|
103
|
-
else if (frameworkOptions.length === 1) {
|
|
104
|
-
framework = frameworkOptions[0].value;
|
|
105
|
-
frameworkPackage = frameworkOptions[0].pkg;
|
|
106
|
-
console.log(`${chalk.cyan("?")} Framework: ${chalk.white(frameworkOptions[0].label)}`);
|
|
107
|
-
}
|
|
108
|
-
else {
|
|
109
|
-
const selected = await promptSelect(rl, "Framework", frameworkOptions.map(f => ({ label: f.label, value: f.value })));
|
|
110
|
-
const match = frameworkOptions.find(f => f.value === selected);
|
|
111
|
-
framework = match.value;
|
|
112
|
-
frameworkPackage = match.pkg;
|
|
96
|
+
const compatible = frameworksForTarget(target).filter((f) => f.installable);
|
|
97
|
+
if (compatible.length === 0) {
|
|
98
|
+
// Defensive: every known target maps to at least one framework in the catalog.
|
|
99
|
+
throw new Error(`No installable frameworks are compatible with target '${target.id}'.`);
|
|
100
|
+
}
|
|
101
|
+
if (partialOptions?.framework) {
|
|
102
|
+
const requested = frameworkCatalogEntry(partialOptions.framework);
|
|
103
|
+
if (!requested) {
|
|
104
|
+
const available = FRAMEWORK_CATALOG.filter((f) => f.installable).map((f) => f.id).join(", ");
|
|
105
|
+
throw new Error(`Unknown framework '${partialOptions.framework}'. Available: ${available}`);
|
|
113
106
|
}
|
|
107
|
+
framework = requested.id;
|
|
108
|
+
frameworkPackage = requested.packageName;
|
|
109
|
+
console.log(`${chalk.cyan("?")} Framework: ${chalk.white(requested.label)}`);
|
|
110
|
+
}
|
|
111
|
+
else if (compatible.length === 1) {
|
|
112
|
+
framework = compatible[0].id;
|
|
113
|
+
frameworkPackage = compatible[0].packageName;
|
|
114
|
+
console.log(`${chalk.cyan("?")} Framework: ${chalk.white(compatible[0].label)}`);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
const selected = await promptSelect(rl, "Framework", compatible.map((f) => ({ label: f.label, value: f.id })));
|
|
118
|
+
const match = compatible.find((f) => f.id === selected);
|
|
119
|
+
framework = match.id;
|
|
120
|
+
frameworkPackage = match.packageName;
|
|
114
121
|
}
|
|
122
|
+
// Resolve the framework-specific build target + toolchain (e.g. a Zephyr
|
|
123
|
+
// board id + 'west' vs the Arduino FQBN + 'arduino-cli'). See framework-catalog.
|
|
124
|
+
const profile = frameworkTargetProfile(target, framework);
|
|
115
125
|
// 4. Baud rate (only for embedded)
|
|
116
126
|
let baudRate;
|
|
117
127
|
if (!target.isNative) {
|
|
@@ -148,12 +158,16 @@ export async function runInitWizard(partialOptions) {
|
|
|
148
158
|
isNative: target.isNative,
|
|
149
159
|
architecture: target.architecture,
|
|
150
160
|
boardPackage: target.boardPackage,
|
|
151
|
-
frameworkPackage,
|
|
152
|
-
framework,
|
|
153
|
-
buildTarget: target.buildTarget,
|
|
161
|
+
frameworkPackage: frameworkPackage ?? '',
|
|
162
|
+
framework: framework ?? '',
|
|
163
|
+
buildTarget: profile.buildTarget ?? target.buildTarget,
|
|
164
|
+
...(profile.toolchainType ? { toolchainType: profile.toolchainType } : {}),
|
|
154
165
|
mcu: target.mcu,
|
|
155
166
|
baudRate,
|
|
156
167
|
includeSketch,
|
|
168
|
+
...(target.frameworkData
|
|
169
|
+
? { frameworkData: target.frameworkData }
|
|
170
|
+
: {}),
|
|
157
171
|
};
|
|
158
172
|
}
|
|
159
173
|
catch (err) {
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type PackageManager } from "./framework-catalog.js";
|
|
2
|
+
export interface ProjectInstallResult {
|
|
3
|
+
pm: PackageManager;
|
|
4
|
+
}
|
|
5
|
+
interface InstallCommand {
|
|
6
|
+
bin: PackageManager;
|
|
7
|
+
args: string[];
|
|
8
|
+
cwd: string;
|
|
9
|
+
}
|
|
10
|
+
interface InstallRunResult {
|
|
11
|
+
/** Exit status; null when the process could not be launched (ENOENT, etc.). */
|
|
12
|
+
status: number | null;
|
|
13
|
+
/** Populated only when the binary could not be launched. */
|
|
14
|
+
launchError?: string;
|
|
15
|
+
}
|
|
16
|
+
type InstallRunner = (cmd: InstallCommand) => InstallRunResult;
|
|
17
|
+
/**
|
|
18
|
+
* FOR TESTS ONLY. Replaces the real spawn-based installer with `runner`.
|
|
19
|
+
* Pass `undefined` to restore the real executor.
|
|
20
|
+
*/
|
|
21
|
+
export declare function __setProjectInstallRunnerForTest(runner: InstallRunner | undefined): void;
|
|
22
|
+
/**
|
|
23
|
+
* Run `<pm> install` in `projectDir`. Throws on launch failure or non-zero exit
|
|
24
|
+
* so the create command can catch it and fall back to instructing the user to
|
|
25
|
+
* run the install manually (the scaffold itself already succeeded).
|
|
26
|
+
*/
|
|
27
|
+
export declare function installProjectDependencies(opts: {
|
|
28
|
+
projectDir: string;
|
|
29
|
+
/** Where to detect the user's preferred package manager. Defaults to cwd. */
|
|
30
|
+
cwd?: string;
|
|
31
|
+
}): ProjectInstallResult;
|
|
32
|
+
export {};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Installs all dependencies in a freshly scaffolded project directory, as the
|
|
2
|
+
// final step of `cuttlefish create` so the new project is ready to build with
|
|
3
|
+
// no separate `npm install` step. The package manager is detected from the
|
|
4
|
+
// INVOKING directory (process.cwd()) — a pnpm/yarn user gets their tool of
|
|
5
|
+
// choice even though the new project has no lockfile yet.
|
|
6
|
+
import { spawnSync } from "node:child_process";
|
|
7
|
+
import { detectPackageManager } from "./framework-catalog.js";
|
|
8
|
+
let testRunner;
|
|
9
|
+
/**
|
|
10
|
+
* FOR TESTS ONLY. Replaces the real spawn-based installer with `runner`.
|
|
11
|
+
* Pass `undefined` to restore the real executor.
|
|
12
|
+
*/
|
|
13
|
+
export function __setProjectInstallRunnerForTest(runner) {
|
|
14
|
+
testRunner = runner;
|
|
15
|
+
}
|
|
16
|
+
function runRealInstall(cmd) {
|
|
17
|
+
// stdio: "inherit" streams the package manager's own output to the terminal
|
|
18
|
+
// (install progress, deprecation warnings, etc.).
|
|
19
|
+
const result = spawnSync(cmd.bin, cmd.args, { cwd: cmd.cwd, stdio: "inherit" });
|
|
20
|
+
if (result.error) {
|
|
21
|
+
const errno = result.error.code;
|
|
22
|
+
return {
|
|
23
|
+
status: null,
|
|
24
|
+
launchError: errno === "ENOENT" ? `'${cmd.bin}' not found on PATH` : String(result.error),
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return { status: result.status };
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Run `<pm> install` in `projectDir`. Throws on launch failure or non-zero exit
|
|
31
|
+
* so the create command can catch it and fall back to instructing the user to
|
|
32
|
+
* run the install manually (the scaffold itself already succeeded).
|
|
33
|
+
*/
|
|
34
|
+
export function installProjectDependencies(opts) {
|
|
35
|
+
const pm = detectPackageManager(opts.cwd ?? process.cwd());
|
|
36
|
+
const cmd = { bin: pm, args: ["install"], cwd: opts.projectDir };
|
|
37
|
+
const run = testRunner ?? runRealInstall;
|
|
38
|
+
const result = run(cmd);
|
|
39
|
+
if (result.launchError) {
|
|
40
|
+
throw new Error(`Failed to run '${pm}': ${result.launchError}`);
|
|
41
|
+
}
|
|
42
|
+
if (result.status !== 0) {
|
|
43
|
+
throw new Error(`'${pm} install' exited with code ${result.status}.`);
|
|
44
|
+
}
|
|
45
|
+
return { pm };
|
|
46
|
+
}
|