@hla4ts/create-spacekit 0.1.0 → 0.1.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.
package/src/index.ts CHANGED
@@ -1,11 +1,11 @@
1
- export {
2
- DEFAULT_TEMPLATE,
3
- TEMPLATE_DESCRIPTIONS,
4
- listTemplates,
5
- sanitizePackageName,
6
- scaffoldApp,
7
- type ScaffoldAppOptions,
8
- type ScaffoldAppResult,
9
- type TemplateId,
10
- type TemplateValues,
11
- } from "./scaffold.ts";
1
+ export {
2
+ DEFAULT_TEMPLATE,
3
+ TEMPLATE_DESCRIPTIONS,
4
+ listTemplates,
5
+ sanitizePackageName,
6
+ scaffoldApp,
7
+ type ScaffoldAppOptions,
8
+ type ScaffoldAppResult,
9
+ type TemplateId,
10
+ type TemplateValues,
11
+ } from "./scaffold.ts";
@@ -1,56 +1,56 @@
1
- import { describe, expect, test } from "bun:test";
2
- import { promises as fs } from "node:fs";
3
- import os from "node:os";
4
- import path from "node:path";
5
- import { sanitizePackageName, scaffoldApp } from "./scaffold.ts";
6
-
1
+ import { describe, expect, test } from "bun:test";
2
+ import { promises as fs } from "node:fs";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { sanitizePackageName, scaffoldApp } from "./scaffold.ts";
6
+
7
7
  describe("@hla4ts/create-spacekit", () => {
8
- test("sanitizePackageName normalizes arbitrary folder names", () => {
9
- expect(sanitizePackageName("My Lunar Rover!")).toBe("my-lunar-rover");
10
- expect(sanitizePackageName(" ")).toBe("hla4ts-spacekit-app");
11
- });
12
-
13
- test("scaffoldApp creates a configurable lunar rover project", async () => {
14
- const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "hla4ts-scaffold-"));
15
- const targetDir = path.join(tempRoot, "mission-rover");
16
-
17
- await scaffoldApp({
18
- targetDir,
19
- values: {
20
- spacekitDependency: "workspace:*",
21
- referenceFrameMissingMode: "continue",
22
- federationName: "MoonOps",
23
- federateName: "Rover-Blue",
24
- federateType: "ScoutRover",
25
- rtiHost: "10.0.0.12",
26
- roverSpeedMps: "1.25",
27
- },
28
- });
29
-
30
- const packageJson = await fs.readFile(path.join(targetDir, "package.json"), "utf8");
31
- const envFile = await fs.readFile(path.join(targetDir, ".env"), "utf8");
32
- const indexFile = await fs.readFile(path.join(targetDir, "src", "index.ts"), "utf8");
33
-
34
- expect(packageJson).toContain('"name": "mission-rover"');
35
- expect(packageJson).toContain('"@hla4ts/spacekit": "workspace:*"');
36
- expect(envFile).toContain("FEDERATION_NAME=MoonOps");
37
- expect(envFile).toContain("FEDERATE_NAME=Rover-Blue");
38
- expect(envFile).toContain("ROVER_NAME=Rover-Blue");
39
- expect(envFile).toContain("ROVER_TYPE=ScoutRover");
40
- expect(envFile).toContain("REFERENCE_FRAME_MISSING_MODE=continue");
41
- expect(envFile).toContain("RTI_HOST=10.0.0.12");
42
- expect(envFile).toContain("ROVER_SPEED_MPS=1.25");
43
- expect(indexFile).toContain('import { PhysicalEntity, SpacekitApp');
44
- });
45
-
46
- test("scaffoldApp refuses non-empty target directories", async () => {
47
- const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "hla4ts-scaffold-"));
48
- const targetDir = path.join(tempRoot, "existing");
49
- await fs.mkdir(targetDir, { recursive: true });
50
- await fs.writeFile(path.join(targetDir, "keep.txt"), "existing", "utf8");
51
-
52
- await expect(scaffoldApp({ targetDir })).rejects.toThrow(
53
- "Refusing to scaffold into a non-empty directory"
54
- );
55
- });
56
- });
8
+ test("sanitizePackageName normalizes arbitrary folder names", () => {
9
+ expect(sanitizePackageName("My Lunar Rover!")).toBe("my-lunar-rover");
10
+ expect(sanitizePackageName(" ")).toBe("hla4ts-spacekit-app");
11
+ });
12
+
13
+ test("scaffoldApp creates a configurable lunar rover project", async () => {
14
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "hla4ts-scaffold-"));
15
+ const targetDir = path.join(tempRoot, "mission-rover");
16
+
17
+ await scaffoldApp({
18
+ targetDir,
19
+ values: {
20
+ spacekitDependency: "workspace:*",
21
+ referenceFrameMissingMode: "continue",
22
+ federationName: "MoonOps",
23
+ federateName: "Rover-Blue",
24
+ federateType: "ScoutRover",
25
+ rtiHost: "10.0.0.12",
26
+ roverSpeedMps: "1.25",
27
+ },
28
+ });
29
+
30
+ const packageJson = await fs.readFile(path.join(targetDir, "package.json"), "utf8");
31
+ const envFile = await fs.readFile(path.join(targetDir, ".env"), "utf8");
32
+ const indexFile = await fs.readFile(path.join(targetDir, "src", "index.ts"), "utf8");
33
+
34
+ expect(packageJson).toContain('"name": "mission-rover"');
35
+ expect(packageJson).toContain('"@hla4ts/spacekit": "workspace:*"');
36
+ expect(envFile).toContain("FEDERATION_NAME=MoonOps");
37
+ expect(envFile).toContain("FEDERATE_NAME=Rover-Blue");
38
+ expect(envFile).toContain("ROVER_NAME=Rover-Blue");
39
+ expect(envFile).toContain("ROVER_TYPE=ScoutRover");
40
+ expect(envFile).toContain("REFERENCE_FRAME_MISSING_MODE=continue");
41
+ expect(envFile).toContain("RTI_HOST=10.0.0.12");
42
+ expect(envFile).toContain("ROVER_SPEED_MPS=1.25");
43
+ expect(indexFile).toContain('import { PhysicalEntity, SpacekitApp');
44
+ });
45
+
46
+ test("scaffoldApp refuses non-empty target directories", async () => {
47
+ const tempRoot = await fs.mkdtemp(path.join(os.tmpdir(), "hla4ts-scaffold-"));
48
+ const targetDir = path.join(tempRoot, "existing");
49
+ await fs.mkdir(targetDir, { recursive: true });
50
+ await fs.writeFile(path.join(targetDir, "keep.txt"), "existing", "utf8");
51
+
52
+ await expect(scaffoldApp({ targetDir })).rejects.toThrow(
53
+ "Refusing to scaffold into a non-empty directory"
54
+ );
55
+ });
56
+ });
package/src/scaffold.ts CHANGED
@@ -1,199 +1,199 @@
1
- import { promises as fs } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
-
5
- export const DEFAULT_TEMPLATE = "lunar-rover";
6
-
7
- export const TEMPLATE_DESCRIPTIONS = {
8
- "lunar-rover":
9
- "Minimal Spacekit lunar rover with .env-driven RTI, federation, and timing config.",
10
- } as const;
11
-
12
- export type TemplateId = keyof typeof TEMPLATE_DESCRIPTIONS;
13
-
14
- export interface TemplateValues {
15
- appName: string;
16
- packageName: string;
17
- spacekitDependency: "^0.1.0",
18
- referenceFrameMissingMode: string;
19
- federationName: string;
20
- federateName: string;
21
- federateType: string;
22
- rtiHost: string;
23
- rtiPort: string;
24
- rtiUseTls: string;
25
- parentReferenceFrame: string;
26
- roverName: string;
27
- roverType: string;
28
- roverSpeedMps: string;
29
- lookaheadMicros: string;
30
- updatePeriodMicros: string;
31
- referenceFrameTimeoutMs: string;
32
- }
33
-
34
- export interface ScaffoldAppOptions {
35
- targetDir: string;
36
- template?: TemplateId;
37
- values?: Partial<TemplateValues>;
38
- }
39
-
40
- export interface ScaffoldAppResult {
41
- template: TemplateId;
42
- targetDir: string;
43
- packageName: string;
44
- filesWritten: number;
45
- }
46
-
47
- export async function scaffoldApp(options: ScaffoldAppOptions): Promise<ScaffoldAppResult> {
48
- const template = options.template ?? DEFAULT_TEMPLATE;
49
- assertTemplate(template);
50
-
51
- const targetDir = path.resolve(options.targetDir);
52
- await ensureEmptyTargetDirectory(targetDir);
53
-
54
- const appName = deriveAppName(targetDir);
55
- const values = {
56
- ...defaultTemplateValues(appName),
57
- ...(options.values ?? {}),
58
- };
59
- values.appName = values.appName.trim() || appName;
60
- values.packageName = sanitizePackageName(values.packageName || values.appName);
61
- values.roverName =
62
- options.values?.roverName === undefined
63
- ? values.federateName
64
- : values.roverName.trim() || values.federateName;
65
- values.roverType =
66
- options.values?.roverType === undefined
67
- ? values.federateType
68
- : values.roverType.trim() || values.federateType;
69
-
70
- const templateDir = resolveTemplateDir(template);
71
- const files = await collectTemplateFiles(templateDir);
72
-
73
- let filesWritten = 0;
74
- for (const sourceFile of files) {
75
- const relativePath = path.relative(templateDir, sourceFile);
76
- const destinationPath = path.join(targetDir, relativePath);
77
- await fs.mkdir(path.dirname(destinationPath), { recursive: true });
78
- const content = await fs.readFile(sourceFile, "utf8");
79
- const rendered = renderTemplate(content, values);
80
- await fs.writeFile(destinationPath, rendered, "utf8");
81
- filesWritten += 1;
82
- }
83
-
84
- return {
85
- template,
86
- targetDir,
87
- packageName: values.packageName,
88
- filesWritten,
89
- };
90
- }
91
-
92
- export function listTemplates(): Array<{ id: TemplateId; description: string }> {
93
- return (Object.entries(TEMPLATE_DESCRIPTIONS) as Array<[TemplateId, string]>).map(
94
- ([id, description]) => ({ id, description })
95
- );
96
- }
97
-
98
- export function sanitizePackageName(value: string): string {
99
- const normalized = value
100
- .trim()
101
- .toLowerCase()
102
- .replace(/[^a-z0-9]+/g, "-")
103
- .replace(/^-+|-+$/g, "");
104
-
105
- return normalized.length > 0 ? normalized : "hla4ts-spacekit-app";
106
- }
107
-
108
- function defaultTemplateValues(appName: string): TemplateValues {
109
- return {
110
- appName,
111
- packageName: sanitizePackageName(appName),
112
- spacekitDependency: "^0.1.0",
113
- referenceFrameMissingMode: "continue",
114
- federationName: "SpaceFederation",
115
- federateName: "LunarRover-1",
116
- federateType: "LunarRover",
117
- rtiHost: "localhost",
118
- rtiPort: "15164",
119
- rtiUseTls: "false",
120
- parentReferenceFrame: "AitkenBasinLocalFixed",
121
- roverName: "LunarRover-1",
122
- roverType: "LunarRover",
123
- roverSpeedMps: "0.5",
124
- lookaheadMicros: "1000000",
125
- updatePeriodMicros: "1000000",
126
- referenceFrameTimeoutMs: "30000",
127
- };
128
- }
129
-
130
- function assertTemplate(template: string): asserts template is TemplateId {
131
- if (!(template in TEMPLATE_DESCRIPTIONS)) {
132
- throw new Error(`Unknown template "${template}". Run --list-templates to inspect choices.`);
133
- }
134
- }
135
-
136
- async function ensureEmptyTargetDirectory(targetDir: string): Promise<void> {
137
- try {
138
- const stat = await fs.stat(targetDir);
139
- if (!stat.isDirectory()) {
140
- throw new Error(`Target path exists and is not a directory: ${targetDir}`);
141
- }
142
- const entries = await fs.readdir(targetDir);
143
- if (entries.length > 0) {
144
- throw new Error(`Refusing to scaffold into a non-empty directory: ${targetDir}`);
145
- }
146
- } catch (error) {
147
- if (isMissing(error)) {
148
- await fs.mkdir(targetDir, { recursive: true });
149
- return;
150
- }
151
- throw error;
152
- }
153
- }
154
-
155
- async function collectTemplateFiles(root: string): Promise<string[]> {
156
- const entries = await fs.readdir(root, { withFileTypes: true });
157
- const files: string[] = [];
158
-
159
- for (const entry of entries) {
160
- const fullPath = path.join(root, entry.name);
161
- if (entry.isDirectory()) {
162
- files.push(...(await collectTemplateFiles(fullPath)));
163
- continue;
164
- }
165
- if (entry.isFile()) {
166
- files.push(fullPath);
167
- }
168
- }
169
-
170
- return files.sort();
171
- }
172
-
173
- function renderTemplate(content: string, values: TemplateValues): string {
174
- return content.replace(/\{\{([A-Z_]+)\}\}/g, (_match, rawKey: string) => {
175
- const key = toTemplateValueKey(rawKey);
176
- return values[key] ?? _match;
177
- });
178
- }
179
-
180
- function toTemplateValueKey(rawKey: string): keyof TemplateValues {
181
- const key = rawKey.toLowerCase().replace(/_([a-z])/g, (_match, letter: string) =>
182
- letter.toUpperCase()
183
- );
184
- return key as keyof TemplateValues;
185
- }
186
-
187
- function deriveAppName(targetDir: string): string {
188
- const base = path.basename(targetDir);
189
- return base === "." || base.length === 0 ? "hla4ts-spacekit-app" : base;
190
- }
191
-
192
- function resolveTemplateDir(template: TemplateId): string {
193
- const currentFile = fileURLToPath(import.meta.url);
194
- return path.resolve(path.dirname(currentFile), "..", "templates", template);
195
- }
196
-
197
- function isMissing(error: unknown): boolean {
198
- return error instanceof Error && "code" in error && error.code === "ENOENT";
199
- }
1
+ import { promises as fs } from "node:fs";
2
+ import path from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+
5
+ export const DEFAULT_TEMPLATE = "lunar-rover";
6
+
7
+ export const TEMPLATE_DESCRIPTIONS = {
8
+ "lunar-rover":
9
+ "Minimal Spacekit lunar rover with .env-driven RTI, federation, and timing config.",
10
+ } as const;
11
+
12
+ export type TemplateId = keyof typeof TEMPLATE_DESCRIPTIONS;
13
+
14
+ export interface TemplateValues {
15
+ appName: string;
16
+ packageName: string;
17
+ spacekitDependency: string;
18
+ referenceFrameMissingMode: string;
19
+ federationName: string;
20
+ federateName: string;
21
+ federateType: string;
22
+ rtiHost: string;
23
+ rtiPort: string;
24
+ rtiUseTls: string;
25
+ parentReferenceFrame: string;
26
+ roverName: string;
27
+ roverType: string;
28
+ roverSpeedMps: string;
29
+ lookaheadMicros: string;
30
+ updatePeriodMicros: string;
31
+ referenceFrameTimeoutMs: string;
32
+ }
33
+
34
+ export interface ScaffoldAppOptions {
35
+ targetDir: string;
36
+ template?: TemplateId;
37
+ values?: Partial<TemplateValues>;
38
+ }
39
+
40
+ export interface ScaffoldAppResult {
41
+ template: TemplateId;
42
+ targetDir: string;
43
+ packageName: string;
44
+ filesWritten: number;
45
+ }
46
+
47
+ export async function scaffoldApp(options: ScaffoldAppOptions): Promise<ScaffoldAppResult> {
48
+ const template = options.template ?? DEFAULT_TEMPLATE;
49
+ assertTemplate(template);
50
+
51
+ const targetDir = path.resolve(options.targetDir);
52
+ await ensureEmptyTargetDirectory(targetDir);
53
+
54
+ const appName = deriveAppName(targetDir);
55
+ const values = {
56
+ ...defaultTemplateValues(appName),
57
+ ...(options.values ?? {}),
58
+ };
59
+ values.appName = values.appName.trim() || appName;
60
+ values.packageName = sanitizePackageName(values.packageName || values.appName);
61
+ values.roverName =
62
+ options.values?.roverName === undefined
63
+ ? values.federateName
64
+ : values.roverName.trim() || values.federateName;
65
+ values.roverType =
66
+ options.values?.roverType === undefined
67
+ ? values.federateType
68
+ : values.roverType.trim() || values.federateType;
69
+
70
+ const templateDir = resolveTemplateDir(template);
71
+ const files = await collectTemplateFiles(templateDir);
72
+
73
+ let filesWritten = 0;
74
+ for (const sourceFile of files) {
75
+ const relativePath = path.relative(templateDir, sourceFile);
76
+ const destinationPath = path.join(targetDir, relativePath);
77
+ await fs.mkdir(path.dirname(destinationPath), { recursive: true });
78
+ const content = await fs.readFile(sourceFile, "utf8");
79
+ const rendered = renderTemplate(content, values);
80
+ await fs.writeFile(destinationPath, rendered, "utf8");
81
+ filesWritten += 1;
82
+ }
83
+
84
+ return {
85
+ template,
86
+ targetDir,
87
+ packageName: values.packageName,
88
+ filesWritten,
89
+ };
90
+ }
91
+
92
+ export function listTemplates(): Array<{ id: TemplateId; description: string }> {
93
+ return (Object.entries(TEMPLATE_DESCRIPTIONS) as Array<[TemplateId, string]>).map(
94
+ ([id, description]) => ({ id, description })
95
+ );
96
+ }
97
+
98
+ export function sanitizePackageName(value: string): string {
99
+ const normalized = value
100
+ .trim()
101
+ .toLowerCase()
102
+ .replace(/[^a-z0-9]+/g, "-")
103
+ .replace(/^-+|-+$/g, "");
104
+
105
+ return normalized.length > 0 ? normalized : "hla4ts-spacekit-app";
106
+ }
107
+
108
+ function defaultTemplateValues(appName: string): TemplateValues {
109
+ return {
110
+ appName,
111
+ packageName: sanitizePackageName(appName),
112
+ spacekitDependency: "^0.1.1",
113
+ referenceFrameMissingMode: "continue",
114
+ federationName: "SpaceFederation",
115
+ federateName: "LunarRover-1",
116
+ federateType: "LunarRover",
117
+ rtiHost: "localhost",
118
+ rtiPort: "15164",
119
+ rtiUseTls: "false",
120
+ parentReferenceFrame: "AitkenBasinLocalFixed",
121
+ roverName: "LunarRover-1",
122
+ roverType: "LunarRover",
123
+ roverSpeedMps: "0.5",
124
+ lookaheadMicros: "1000000",
125
+ updatePeriodMicros: "1000000",
126
+ referenceFrameTimeoutMs: "30000",
127
+ };
128
+ }
129
+
130
+ function assertTemplate(template: string): asserts template is TemplateId {
131
+ if (!(template in TEMPLATE_DESCRIPTIONS)) {
132
+ throw new Error(`Unknown template "${template}". Run --list-templates to inspect choices.`);
133
+ }
134
+ }
135
+
136
+ async function ensureEmptyTargetDirectory(targetDir: string): Promise<void> {
137
+ try {
138
+ const stat = await fs.stat(targetDir);
139
+ if (!stat.isDirectory()) {
140
+ throw new Error(`Target path exists and is not a directory: ${targetDir}`);
141
+ }
142
+ const entries = await fs.readdir(targetDir);
143
+ if (entries.length > 0) {
144
+ throw new Error(`Refusing to scaffold into a non-empty directory: ${targetDir}`);
145
+ }
146
+ } catch (error) {
147
+ if (isMissing(error)) {
148
+ await fs.mkdir(targetDir, { recursive: true });
149
+ return;
150
+ }
151
+ throw error;
152
+ }
153
+ }
154
+
155
+ async function collectTemplateFiles(root: string): Promise<string[]> {
156
+ const entries = await fs.readdir(root, { withFileTypes: true });
157
+ const files: string[] = [];
158
+
159
+ for (const entry of entries) {
160
+ const fullPath = path.join(root, entry.name);
161
+ if (entry.isDirectory()) {
162
+ files.push(...(await collectTemplateFiles(fullPath)));
163
+ continue;
164
+ }
165
+ if (entry.isFile()) {
166
+ files.push(fullPath);
167
+ }
168
+ }
169
+
170
+ return files.sort();
171
+ }
172
+
173
+ function renderTemplate(content: string, values: TemplateValues): string {
174
+ return content.replace(/\{\{([A-Z_]+)\}\}/g, (_match, rawKey: string) => {
175
+ const key = toTemplateValueKey(rawKey);
176
+ return values[key] ?? _match;
177
+ });
178
+ }
179
+
180
+ function toTemplateValueKey(rawKey: string): keyof TemplateValues {
181
+ const key = rawKey.toLowerCase().replace(/_([a-z])/g, (_match, letter: string) =>
182
+ letter.toUpperCase()
183
+ );
184
+ return key as keyof TemplateValues;
185
+ }
186
+
187
+ function deriveAppName(targetDir: string): string {
188
+ const base = path.basename(targetDir);
189
+ return base === "." || base.length === 0 ? "hla4ts-spacekit-app" : base;
190
+ }
191
+
192
+ function resolveTemplateDir(template: TemplateId): string {
193
+ const currentFile = fileURLToPath(import.meta.url);
194
+ return path.resolve(path.dirname(currentFile), "..", "templates", template);
195
+ }
196
+
197
+ function isMissing(error: unknown): boolean {
198
+ return error instanceof Error && "code" in error && error.code === "ENOENT";
199
+ }
@@ -1,37 +1,37 @@
1
- # {{APP_NAME}}
2
-
1
+ # {{APP_NAME}}
2
+
3
3
  Minimal lunar rover example generated by `spacekit` from `@hla4ts/create-spacekit`.
4
-
5
- This app uses `@hla4ts/spacekit` directly:
6
-
7
- - `SpacekitApp.run()` owns the join/bootstrap/tick loop
8
- - `PhysicalEntity` publishes a single rover object
9
- - `.env` contains the RTI, federation, rover, and timing defaults
10
-
11
- ## Prerequisites
12
-
13
- - Bun installed
14
- - an HLA 4 RTI reachable at the configured host/port
15
- - SpaceMaster and a root reference frame publisher if you are running a SEE late-join federation
16
-
17
- ## Configure
18
-
19
- Edit `.env` if needed:
20
-
21
- - `RTI_HOST`, `RTI_PORT`, `RTI_USE_TLS`
22
- - `FEDERATION_NAME`, `FEDERATE_NAME`, `FEDERATE_TYPE`
23
- - `ROVER_NAME`, `ROVER_TYPE`, `PARENT_REFERENCE_FRAME`
24
- - `ROVER_SPEED_MPS`, `LOOKAHEAD_MICROS`, `UPDATE_PERIOD_MICROS`
25
- - `REFERENCE_FRAME_MISSING_MODE` (`continue` by default)
26
-
27
- ## Run
28
-
29
- ```bash
30
- bun install
31
- bun run start
32
- ```
33
-
34
- The rover moves forward along the X axis at the configured speed and logs each
35
- published tick. By default the generated app uses `continue` mode for missing
36
- reference frames, so it warns and keeps running if the configured parent frame
37
- has not been published yet.
4
+
5
+ This app uses `@hla4ts/spacekit` directly:
6
+
7
+ - `SpacekitApp.run()` owns the join/bootstrap/tick loop
8
+ - `PhysicalEntity` publishes a single rover object
9
+ - `.env` contains the RTI, federation, rover, and timing defaults
10
+
11
+ ## Prerequisites
12
+
13
+ - Bun installed
14
+ - an HLA 4 RTI reachable at the configured host/port
15
+ - SpaceMaster and a root reference frame publisher if you are running a SEE late-join federation
16
+
17
+ ## Configure
18
+
19
+ Edit `.env` if needed:
20
+
21
+ - `RTI_HOST`, `RTI_PORT`, `RTI_USE_TLS`
22
+ - `FEDERATION_NAME`, `FEDERATE_NAME`, `FEDERATE_TYPE`
23
+ - `ROVER_NAME`, `ROVER_TYPE`, `PARENT_REFERENCE_FRAME`
24
+ - `ROVER_SPEED_MPS`, `LOOKAHEAD_MICROS`, `UPDATE_PERIOD_MICROS`
25
+ - `REFERENCE_FRAME_MISSING_MODE` (`continue` by default)
26
+
27
+ ## Run
28
+
29
+ ```bash
30
+ bun install
31
+ bun run start
32
+ ```
33
+
34
+ The rover moves forward along the X axis at the configured speed and logs each
35
+ published tick. By default the generated app uses `continue` mode for missing
36
+ reference frames, so it warns and keeps running if the configured parent frame
37
+ has not been published yet.
@@ -1,17 +1,17 @@
1
- {
2
- "name": "{{PACKAGE_NAME}}",
3
- "version": "0.1.0",
4
- "private": true,
5
- "type": "module",
6
- "scripts": {
7
- "start": "bun run src/index.ts",
8
- "typecheck": "tsc --noEmit"
9
- },
10
- "dependencies": {
11
- "@hla4ts/spacekit": "{{SPACEKIT_DEPENDENCY}}"
12
- },
13
- "devDependencies": {
14
- "@types/bun": "latest",
15
- "typescript": "^5.9.0"
16
- }
17
- }
1
+ {
2
+ "name": "{{PACKAGE_NAME}}",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "start": "bun run src/index.ts",
8
+ "typecheck": "tsc --noEmit"
9
+ },
10
+ "dependencies": {
11
+ "@hla4ts/spacekit": "{{SPACEKIT_DEPENDENCY}}"
12
+ },
13
+ "devDependencies": {
14
+ "@types/bun": "latest",
15
+ "typescript": "^5.9.0"
16
+ }
17
+ }