@defold-typescript/cli 0.5.5 → 0.7.0

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.
@@ -1,4 +1,4 @@
1
- export type CliCommand = "init" | "build";
1
+ export type CliCommand = "init" | "build" | "setup-debug" | "defold" | "wall";
2
2
 
3
3
  export interface RenderResultInput {
4
4
  readonly command: CliCommand;
@@ -6,8 +6,17 @@ export interface RenderResultInput {
6
6
  readonly error?: string;
7
7
  readonly defoldVersion?: string;
8
8
  readonly apiSurface?: string | null;
9
- readonly scriptKind?: string | null;
10
9
  readonly materializedSurface?: string | null;
10
+ readonly directoryWalls?: readonly { readonly dir: string; readonly kind: string }[];
11
+ readonly eligible?: readonly { readonly dir: string; readonly kind: string }[];
12
+ readonly installCommand?: string;
13
+ readonly manualSteps?: readonly string[];
14
+ readonly actions?: Record<string, string>;
15
+ readonly addedTo?: string;
16
+ readonly removedFrom?: readonly string[];
17
+ readonly bootPath?: readonly string[];
18
+ readonly subcommand?: string;
19
+ readonly exitCode?: number;
11
20
  }
12
21
 
13
22
  export function renderResult(input: RenderResultInput): string {
@@ -19,11 +28,47 @@ export function renderResult(input: RenderResultInput): string {
19
28
  input.defoldVersion === undefined ? base : { ...base, defoldVersion: input.defoldVersion };
20
29
  const withSurface =
21
30
  "apiSurface" in input ? { ...withVersion, apiSurface: input.apiSurface } : withVersion;
22
- const withScriptKind =
23
- "scriptKind" in input ? { ...withSurface, scriptKind: input.scriptKind } : withSurface;
24
- const payload =
31
+ const withMaterialized =
25
32
  "materializedSurface" in input
26
- ? { ...withScriptKind, materializedSurface: input.materializedSurface }
27
- : withScriptKind;
33
+ ? { ...withSurface, materializedSurface: input.materializedSurface }
34
+ : withSurface;
35
+ const withWalls =
36
+ "directoryWalls" in input
37
+ ? { ...withMaterialized, directoryWalls: input.directoryWalls }
38
+ : withMaterialized;
39
+ const withEligible = "eligible" in input ? { ...withWalls, eligible: input.eligible } : withWalls;
40
+ const withInstall =
41
+ "installCommand" in input
42
+ ? { ...withEligible, installCommand: input.installCommand }
43
+ : withEligible;
44
+ const withManual =
45
+ "manualSteps" in input ? { ...withInstall, manualSteps: input.manualSteps } : withInstall;
46
+ const withActions = "actions" in input ? { ...withManual, actions: input.actions } : withManual;
47
+ const withAdded = "addedTo" in input ? { ...withActions, addedTo: input.addedTo } : withActions;
48
+ const withRemoved =
49
+ "removedFrom" in input ? { ...withAdded, removedFrom: input.removedFrom } : withAdded;
50
+ const withBoot = "bootPath" in input ? { ...withRemoved, bootPath: input.bootPath } : withRemoved;
51
+ const withSub = "subcommand" in input ? { ...withBoot, subcommand: input.subcommand } : withBoot;
52
+ const payload = "exitCode" in input ? { ...withSub, exitCode: input.exitCode } : withSub;
28
53
  return `${JSON.stringify(payload)}\n`;
29
54
  }
55
+
56
+ export type WatchEventName = "build" | "rebuild";
57
+
58
+ export interface RenderWatchEventInput {
59
+ readonly event: WatchEventName;
60
+ readonly written?: readonly string[];
61
+ readonly changed?: readonly string[];
62
+ readonly removed?: readonly string[];
63
+ readonly error?: string;
64
+ }
65
+
66
+ export function renderWatchEvent(input: RenderWatchEventInput): string {
67
+ const ok = input.error === undefined;
68
+ const base = ok
69
+ ? { command: "watch" as const, event: input.event, ok, written: input.written ?? [] }
70
+ : { command: "watch" as const, event: input.event, ok, error: input.error };
71
+ const withChanged = "changed" in input ? { ...base, changed: input.changed } : base;
72
+ const withRemoved = "removed" in input ? { ...withChanged, removed: input.removed } : withChanged;
73
+ return `${JSON.stringify(withRemoved)}\n`;
74
+ }
@@ -14,15 +14,23 @@ import {
14
14
  resolveTypesPackageRoot,
15
15
  } from "./api-registry";
16
16
  import type { SelectedApiSurface } from "./api-surface";
17
- import { excludedModulesForKind, type ScriptKind } from "./script-kind";
18
17
 
19
18
  const MATERIALIZED_ROOT = ".defold-types";
20
19
 
20
+ // The materialized surface must not mint its own copy of the branded engine
21
+ // primitives: `Hash` & co. are `unique symbol`-branded per declaration, so a
22
+ // copied `core-types.d.ts` is nominally distinct from the installed
23
+ // `@defold-typescript/types` a consumer imports from, and the two never unify
24
+ // (a consumer comparing `message_id === hash(...)` or assigning an imported
25
+ // `Hash` would get TS2367/TS2741). Re-export the package's copy instead so the
26
+ // ambient surface shares one brand. `engine-globals.d.ts` stays copied; its
27
+ // relative `./core-types` import resolves to this re-export.
28
+ const CORE_TYPES_REEXPORT = 'export * from "@defold-typescript/types/core-types";\n';
29
+
21
30
  export interface MaterializeApiSurfaceOptions {
22
31
  readonly cwd: string;
23
32
  readonly surface: SelectedApiSurface;
24
33
  readonly sourceGeneratedDir: string | null;
25
- readonly scriptKind?: ScriptKind | null;
26
34
  }
27
35
 
28
36
  export interface MaterializeApiSurfaceResult {
@@ -53,10 +61,7 @@ export function materializeApiSurface(
53
61
  const absDir = path.join(cwd, MATERIALIZED_ROOT, surfaceId);
54
62
  mkdirSync(absDir, { recursive: true });
55
63
 
56
- const excluded = excludedModulesForKind(opts.scriptKind ?? null);
57
- const sources = listDts(sourceGeneratedDir)
58
- .filter((file) => file !== "index.d.ts")
59
- .filter((file) => !excluded.has(file.replace(/\.d\.ts$/, "")));
64
+ const sources = listDts(sourceGeneratedDir).filter((file) => file !== "index.d.ts");
60
65
 
61
66
  // The `*-overloads` augmentations and the `core-types` they import live in the
62
67
  // types package `src/` (sibling of `generated/`), not among the generated
@@ -97,7 +102,7 @@ export function materializeApiSurface(
97
102
  );
98
103
  }
99
104
  if (includeCoreTypes) {
100
- writeFileSync(path.join(absDir, "core-types.d.ts"), readFileSync(coreTypesSrc, "utf8"));
105
+ writeFileSync(path.join(absDir, "core-types.d.ts"), CORE_TYPES_REEXPORT);
101
106
  }
102
107
  if (includeEngineGlobals) {
103
108
  writeFileSync(path.join(absDir, "engine-globals.d.ts"), readFileSync(engineGlobalsSrc, "utf8"));
@@ -194,10 +199,9 @@ export interface MaterializeRefDocSurfaceOptions {
194
199
  readonly cwd: string;
195
200
  readonly surfaceId: string;
196
201
  readonly resolveOpts?: RefDocResolveOptions;
197
- readonly scriptKind?: ScriptKind | null;
198
202
  // Registry override (defaults to the installed types package's
199
203
  // api-targets.json). Injected only by tests that need a multi-module ref-doc
200
- // target to observe kind narrowing.
204
+ // target.
201
205
  readonly registry?: readonly RegistryTarget[];
202
206
  }
203
207
 
@@ -221,7 +225,6 @@ export async function materializeRefDocSurface(
221
225
  if (target?.source?.kind !== "ref-doc") {
222
226
  return { materializedDir: null, active: null };
223
227
  }
224
- const excludeModules = [...excludedModulesForKind(opts.scriptKind ?? null)];
225
228
 
226
229
  const relDir = path.posix.join(MATERIALIZED_ROOT, surfaceId);
227
230
  const absDir = path.join(cwd, MATERIALIZED_ROOT, surfaceId);
@@ -233,9 +236,8 @@ export async function materializeRefDocSurface(
233
236
  await mod.materializeVersionedSurface(selfContained, {
234
237
  destDir: absDir,
235
238
  ...(resolveOpts ? { resolveOpts } : {}),
236
- ...(excludeModules.length > 0 ? { excludeModules } : {}),
237
239
  });
238
- copyFileSync(path.join(root, "src", "core-types.ts"), path.join(absDir, "core-types.d.ts"));
240
+ writeFileSync(path.join(absDir, "core-types.d.ts"), CORE_TYPES_REEXPORT);
239
241
  copyFileSync(
240
242
  path.join(root, "src", "engine-globals.d.ts"),
241
243
  path.join(absDir, "engine-globals.d.ts"),
@@ -4,25 +4,31 @@
4
4
  // the block without disturbing user-authored `[tools]`/`[tasks.*]` content.
5
5
  const MANAGED_MARKER = "# managed by @defold-typescript";
6
6
 
7
- // `bunx --no-install` resolves only the locally installed binary and never
8
- // fetches from the registry — that is the installed-version contract the types
9
- // pin upholds, while staying cross-platform (the bare `node_modules/.bin` path
10
- // is `.cmd`-shimmed on Windows). `:upgrade` is the deliberate `@latest` pull
11
- // that re-pins `@defold-typescript/types` via `init --force` + reinstall.
7
+ // Build/watch/setup-debug run `bunx @defold-typescript/cli <cmd>`: inside an
8
+ // installed project bunx resolves the `@defold-typescript/cli` pinned in
9
+ // `SCAFFOLD_DEV_DEPS`, so the task runs the version locked alongside
10
+ // `@defold-typescript/types`. `:upgrade` is the deliberate `@latest` pull that
11
+ // re-pins `@defold-typescript/types` via `init --force` + reinstall; it
12
+ // suppresses the install reminder because its second command already reinstalls.
12
13
  export const MISE_TASKS_TOML = `${MANAGED_MARKER}
13
14
  [tasks."defold-typescript:build"]
14
- description = "Build the TypeScript sources with the installed defold-typescript CLI"
15
- run = "bunx --no-install defold-typescript build"
15
+ description = "Build the TypeScript sources with the defold-typescript CLI"
16
+ run = "bunx @defold-typescript/cli build"
16
17
 
17
18
  ${MANAGED_MARKER}
18
19
  [tasks."defold-typescript:watch"]
19
- description = "Watch and rebuild the TypeScript sources with the installed defold-typescript CLI"
20
- run = "bunx --no-install defold-typescript watch"
20
+ description = "Watch and rebuild the TypeScript sources with the defold-typescript CLI"
21
+ run = "bunx @defold-typescript/cli watch"
22
+
23
+ ${MANAGED_MARKER}
24
+ [tasks."defold-typescript:setup-debug"]
25
+ description = "Wire the lldebugger game.project dependency and entry-script bootstrap with the defold-typescript CLI"
26
+ run = "bunx @defold-typescript/cli setup-debug"
21
27
 
22
28
  ${MANAGED_MARKER}
23
29
  [tasks."defold-typescript:upgrade"]
24
30
  description = "Upgrade the defold-typescript CLI to its latest release and re-pin the types dependency"
25
- run = ["bunx @defold-typescript/cli@latest init --force", "bun install"]
31
+ run = ["bunx @defold-typescript/cli@latest init --force --suppress-install-reminder", "bun install"]
26
32
  `;
27
33
 
28
34
  // Drop every managed block (marker line through the next blank line or EOF),
package/src/scan.ts CHANGED
@@ -1,10 +1,16 @@
1
1
  import { globSync, statSync } from "node:fs";
2
2
  import * as path from "node:path";
3
3
 
4
+ export function normalizeScannedPath(rel: string): string {
5
+ return rel.split(/[/\\]/).join("/");
6
+ }
7
+
4
8
  // `Bun.Glob` is undefined when the published bin runs under plain node, so the
5
9
  // scaffold/build path must use the cross-runtime `node:fs` glob instead. Bun's
6
10
  // `globSync` does not support `withFileTypes`, so directories are filtered out
7
11
  // with a stat to preserve the original `onlyFiles` contract.
8
12
  export function scanFilesSync(cwd: string, pattern: string): string[] {
9
- return globSync(pattern, { cwd }).filter((rel) => statSync(path.join(cwd, rel)).isFile());
13
+ return globSync(pattern, { cwd })
14
+ .map(normalizeScannedPath)
15
+ .filter((rel) => statSync(path.join(cwd, rel)).isFile());
10
16
  }
@@ -1,3 +1,4 @@
1
+ import * as path from "node:path";
1
2
  import { scanFilesSync } from "./scan";
2
3
 
3
4
  export type ScriptKind = "script" | "gui-script" | "render-script";
@@ -45,6 +46,36 @@ export function detectScriptKinds(cwd: string): Set<ScriptKind> {
45
46
  return kinds;
46
47
  }
47
48
 
49
+ export function groupScriptKindsByDirectory(cwd: string): Map<string, Set<ScriptKind>> {
50
+ const byDir = new Map<string, Set<ScriptKind>>();
51
+ for (const [ext, kind] of Object.entries(KIND_BY_EXT)) {
52
+ for (const match of scanFilesSync(cwd, `**/*${ext}`)) {
53
+ if (isSkipped(match) || isGeneratedScript(match)) {
54
+ continue;
55
+ }
56
+ const dir = path.posix.dirname(match.split(path.sep).join("/"));
57
+ let set = byDir.get(dir);
58
+ if (set === undefined) {
59
+ set = new Set<ScriptKind>();
60
+ byDir.set(dir, set);
61
+ }
62
+ set.add(kind);
63
+ }
64
+ }
65
+ return byDir;
66
+ }
67
+
68
+ export function selectDirectoryWalls(cwd: string): Map<string, ScriptKind> {
69
+ const walls = new Map<string, ScriptKind>();
70
+ for (const [dir, kinds] of groupScriptKindsByDirectory(cwd)) {
71
+ const kind = selectScriptKind(kinds);
72
+ if (kind !== null) {
73
+ walls.set(dir, kind);
74
+ }
75
+ }
76
+ return walls;
77
+ }
78
+
48
79
  export function selectScriptKind(kinds: Set<ScriptKind>): ScriptKind | null {
49
80
  if (kinds.size !== 1) {
50
81
  return null;
@@ -59,22 +90,3 @@ export function selectScriptKindEntrypoint(kinds: Set<ScriptKind>): string {
59
90
  const kind = selectScriptKind(kinds);
60
91
  return kind === null ? DEFAULT_TYPES_ENTRYPOINT : `${DEFAULT_TYPES_ENTRYPOINT}/${kind}`;
61
92
  }
62
-
63
- // The one restricted namespace each kind allows; mirrors `regen.ts`'s
64
- // RESTRICTED_NAMESPACES (gui -> gui_script, render -> render_script). `script`
65
- // allows neither.
66
- const RESTRICTED_MODULES: Record<ScriptKind, string> = {
67
- script: "",
68
- "gui-script": "gui",
69
- "render-script": "render",
70
- };
71
-
72
- const ALL_RESTRICTED_MODULES: readonly string[] = ["gui", "render"];
73
-
74
- export function excludedModulesForKind(kind: ScriptKind | null): Set<string> {
75
- if (kind === null) {
76
- return new Set();
77
- }
78
- const allowed = RESTRICTED_MODULES[kind];
79
- return new Set(ALL_RESTRICTED_MODULES.filter((mod) => mod !== allowed));
80
- }