@three-flatland/bake 0.1.0-alpha.1 → 0.1.0-alpha.2
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 +1 -1
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +63 -73
- package/dist/cli.js.map +1 -1
- package/dist/devtimeWarn.d.ts +20 -0
- package/dist/devtimeWarn.d.ts.map +1 -0
- package/dist/devtimeWarn.js +36 -16
- package/dist/devtimeWarn.js.map +1 -1
- package/dist/discovery.d.ts +21 -0
- package/dist/discovery.d.ts.map +1 -0
- package/dist/discovery.js +106 -98
- package/dist/discovery.js.map +1 -1
- package/dist/index.d.ts +6 -162
- package/dist/index.js +6 -16
- package/dist/node.d.ts +8 -35
- package/dist/node.js +7 -8
- package/dist/sidecar.d.ts +52 -0
- package/dist/sidecar.d.ts.map +1 -0
- package/dist/sidecar.js +141 -96
- package/dist/sidecar.js.map +1 -1
- package/dist/types.d.ts +104 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +4 -1
- package/dist/writeSidecar.d.ts +22 -0
- package/dist/writeSidecar.d.ts.map +1 -0
- package/dist/writeSidecar.js +69 -45
- package/dist/writeSidecar.js.map +1 -1
- package/package.json +24 -5
- package/dist/index.js.map +0 -1
- package/dist/node.js.map +0 -1
- package/dist/types.js.map +0 -1
package/README.md
CHANGED
|
@@ -123,7 +123,7 @@ Not a dev-iteration knob — the default path (probe → generate on miss + warn
|
|
|
123
123
|
|
|
124
124
|
## Documentation
|
|
125
125
|
|
|
126
|
-
Full docs, interactive examples, and API reference at **[
|
|
126
|
+
Full docs, interactive examples, and API reference at **[tjw.dev/three-flatland](https://tjw.dev/three-flatland/)**
|
|
127
127
|
|
|
128
128
|
## License
|
|
129
129
|
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import "node:path";
|
|
3
|
+
import "node:url";
|
|
4
|
+
import.meta.url;
|
|
5
|
+
import { discoverBakers } from "./discovery.js";
|
|
2
6
|
import { pathToFileURL } from "node:url";
|
|
3
7
|
import { existsSync } from "node:fs";
|
|
4
|
-
|
|
5
|
-
|
|
8
|
+
//#region src/cli.ts
|
|
9
|
+
/**
|
|
10
|
+
* `flatland-bake` — the unified bake entry point. Discovers bakers
|
|
11
|
+
* contributed by installed packages (via a `flatland.bake` field in
|
|
12
|
+
* their package.json), then dispatches `flatland-bake <name> [args]` to
|
|
13
|
+
* the matching baker's `run()`. `--list` enumerates available bakers and
|
|
14
|
+
* `--help` prints usage. See {@link discoverBakers} and the {@link Baker}
|
|
15
|
+
* contract for how a package registers a subcommand.
|
|
16
|
+
*/
|
|
17
|
+
const USAGE = `flatland-bake — unified bake entry point
|
|
6
18
|
|
|
7
19
|
Usage:
|
|
8
20
|
flatland-bake <name> [args...] Run a registered baker
|
|
@@ -13,80 +25,58 @@ Bakers are contributed by packages via a \`flatland.bake\` field in
|
|
|
13
25
|
package.json. Install a package that provides one (e.g. @three-flatland/slug)
|
|
14
26
|
and its subcommand will appear in --list.`;
|
|
15
27
|
async function main(argv) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const baker = await loadBaker(match);
|
|
38
|
-
if (!baker) return 1;
|
|
39
|
-
try {
|
|
40
|
-
return await baker.run(rest);
|
|
41
|
-
} catch (err) {
|
|
42
|
-
process.stderr.write(
|
|
43
|
-
`[flatland-bake] baker "${match.name}" threw: ${err instanceof Error ? err.message : String(err)}
|
|
44
|
-
`
|
|
45
|
-
);
|
|
46
|
-
return 1;
|
|
47
|
-
}
|
|
28
|
+
const [first, ...rest] = argv;
|
|
29
|
+
if (!first || first === "--help" || first === "-h") {
|
|
30
|
+
process.stdout.write(USAGE + "\n");
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
const { bakers, conflicts } = discoverBakers();
|
|
34
|
+
for (const warning of conflicts) process.stderr.write(`[flatland-bake] warn: ${warning}\n`);
|
|
35
|
+
if (first === "--list") return printList(bakers);
|
|
36
|
+
const match = bakers.find((b) => b.name === first);
|
|
37
|
+
if (!match) {
|
|
38
|
+
process.stderr.write(`[flatland-bake] unknown baker "${first}". Run \`flatland-bake --list\` to see what's available.\n`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
const baker = await loadBaker(match);
|
|
42
|
+
if (!baker) return 1;
|
|
43
|
+
try {
|
|
44
|
+
return await baker.run(rest);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
process.stderr.write(`[flatland-bake] baker "${match.name}" threw: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
47
|
+
return 1;
|
|
48
|
+
}
|
|
48
49
|
}
|
|
49
50
|
function printList(bakers) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
`);
|
|
62
|
-
}
|
|
63
|
-
return 0;
|
|
51
|
+
if (bakers.length === 0) {
|
|
52
|
+
process.stdout.write("No bakers registered. Install a package that contributes one (e.g. @three-flatland/slug).\n");
|
|
53
|
+
return 0;
|
|
54
|
+
}
|
|
55
|
+
const nameWidth = Math.max(...bakers.map((b) => b.name.length));
|
|
56
|
+
process.stdout.write("Registered bakers:\n");
|
|
57
|
+
for (const b of bakers) {
|
|
58
|
+
const pad = " ".repeat(nameWidth - b.name.length);
|
|
59
|
+
process.stdout.write(` ${b.name}${pad} ${b.description} (${b.packageName})\n`);
|
|
60
|
+
}
|
|
61
|
+
return 0;
|
|
64
62
|
}
|
|
65
63
|
async function loadBaker(reg) {
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
process.stderr.write(
|
|
77
|
-
`[flatland-bake] baker "${reg.name}" from "${reg.packageName}" did not default-export a Baker.
|
|
78
|
-
`
|
|
79
|
-
);
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
return baker;
|
|
64
|
+
if (!existsSync(reg.resolvedEntry)) {
|
|
65
|
+
process.stderr.write(`[flatland-bake] baker "${reg.name}" from "${reg.packageName}" points to missing entry: ${reg.resolvedEntry}\n`);
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const baker = (await import(pathToFileURL(reg.resolvedEntry).href)).default;
|
|
69
|
+
if (!baker || typeof baker.run !== "function") {
|
|
70
|
+
process.stderr.write(`[flatland-bake] baker "${reg.name}" from "${reg.packageName}" did not default-export a Baker.\n`);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
return baker;
|
|
83
74
|
}
|
|
84
|
-
main(process.argv.slice(2)).then(
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
);
|
|
75
|
+
main(process.argv.slice(2)).then((code) => process.exit(code), (err) => {
|
|
76
|
+
process.stderr.write(`[flatland-bake] fatal: ${String(err)}\n`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
});
|
|
79
|
+
//#endregion
|
|
80
|
+
export {};
|
|
81
|
+
|
|
92
82
|
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `flatland-bake` — the unified bake entry point. Discovers bakers\n * contributed by installed packages (via a `flatland.bake` field in\n * their package.json), then dispatches `flatland-bake <name> [args]` to\n * the matching baker's `run()`. `--list` enumerates available bakers and\n * `--help` prints usage. See {@link discoverBakers} and the {@link Baker}\n * contract for how a package registers a subcommand.\n */\nimport { pathToFileURL } from 'node:url'\nimport { existsSync } from 'node:fs'\nimport { discoverBakers } from './discovery.js'\nimport type { Baker, BakerRegistration } from './types.js'\n\nconst USAGE = `flatland-bake — unified bake entry point\n\nUsage:\n flatland-bake <name> [args...] Run a registered baker\n flatland-bake --list List registered bakers\n flatland-bake --help Show this usage\n\nBakers are contributed by packages via a \\`flatland.bake\\` field in\npackage.json. Install a package that provides one (e.g. @three-flatland/slug)\nand its subcommand will appear in --list.`\n\nasync function main(argv: string[]): Promise<number> {\n const [first, ...rest] = argv\n\n if (!first || first === '--help' || first === '-h') {\n process.stdout.write(USAGE + '\\n')\n return 0\n }\n\n const { bakers, conflicts } = discoverBakers()\n for (const warning of conflicts) {\n process.stderr.write(`[flatland-bake] warn: ${warning}\\n`)\n }\n\n if (first === '--list') {\n return printList(bakers)\n }\n\n const match = bakers.find((b) => b.name === first)\n if (!match) {\n process.stderr.write(\n `[flatland-bake] unknown baker \"${first}\". Run \\`flatland-bake --list\\` to see what's available.\\n`\n )\n return 1\n }\n\n const baker = await loadBaker(match)\n if (!baker) return 1\n\n try {\n return await baker.run(rest)\n } catch (err) {\n process.stderr.write(\n `[flatland-bake] baker \"${match.name}\" threw: ${err instanceof Error ? err.message : String(err)}\\n`\n )\n return 1\n }\n}\n\nfunction printList(bakers: BakerRegistration[]): number {\n if (bakers.length === 0) {\n process.stdout.write(
|
|
1
|
+
{"version":3,"file":"cli.js","names":[],"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `flatland-bake` — the unified bake entry point. Discovers bakers\n * contributed by installed packages (via a `flatland.bake` field in\n * their package.json), then dispatches `flatland-bake <name> [args]` to\n * the matching baker's `run()`. `--list` enumerates available bakers and\n * `--help` prints usage. See {@link discoverBakers} and the {@link Baker}\n * contract for how a package registers a subcommand.\n */\nimport { pathToFileURL } from 'node:url'\nimport { existsSync } from 'node:fs'\nimport { discoverBakers } from './discovery.js'\nimport type { Baker, BakerRegistration } from './types.js'\n\nconst USAGE = `flatland-bake — unified bake entry point\n\nUsage:\n flatland-bake <name> [args...] Run a registered baker\n flatland-bake --list List registered bakers\n flatland-bake --help Show this usage\n\nBakers are contributed by packages via a \\`flatland.bake\\` field in\npackage.json. Install a package that provides one (e.g. @three-flatland/slug)\nand its subcommand will appear in --list.`\n\nasync function main(argv: string[]): Promise<number> {\n const [first, ...rest] = argv\n\n if (!first || first === '--help' || first === '-h') {\n process.stdout.write(USAGE + '\\n')\n return 0\n }\n\n const { bakers, conflicts } = discoverBakers()\n for (const warning of conflicts) {\n process.stderr.write(`[flatland-bake] warn: ${warning}\\n`)\n }\n\n if (first === '--list') {\n return printList(bakers)\n }\n\n const match = bakers.find((b) => b.name === first)\n if (!match) {\n process.stderr.write(\n `[flatland-bake] unknown baker \"${first}\". Run \\`flatland-bake --list\\` to see what's available.\\n`\n )\n return 1\n }\n\n const baker = await loadBaker(match)\n if (!baker) return 1\n\n try {\n return await baker.run(rest)\n } catch (err) {\n process.stderr.write(\n `[flatland-bake] baker \"${match.name}\" threw: ${err instanceof Error ? err.message : String(err)}\\n`\n )\n return 1\n }\n}\n\nfunction printList(bakers: BakerRegistration[]): number {\n if (bakers.length === 0) {\n process.stdout.write('No bakers registered. Install a package that contributes one (e.g. @three-flatland/slug).\\n')\n return 0\n }\n\n const nameWidth = Math.max(...bakers.map((b) => b.name.length))\n process.stdout.write('Registered bakers:\\n')\n for (const b of bakers) {\n const pad = ' '.repeat(nameWidth - b.name.length)\n process.stdout.write(` ${b.name}${pad} ${b.description} (${b.packageName})\\n`)\n }\n return 0\n}\n\nasync function loadBaker(reg: BakerRegistration): Promise<Baker | null> {\n if (!existsSync(reg.resolvedEntry)) {\n process.stderr.write(\n `[flatland-bake] baker \"${reg.name}\" from \"${reg.packageName}\" points to missing entry: ${reg.resolvedEntry}\\n`\n )\n return null\n }\n\n const mod = (await import(pathToFileURL(reg.resolvedEntry).href)) as {\n default?: Baker\n }\n const baker = mod.default\n if (!baker || typeof baker.run !== 'function') {\n process.stderr.write(\n `[flatland-bake] baker \"${reg.name}\" from \"${reg.packageName}\" did not default-export a Baker.\\n`\n )\n return null\n }\n return baker\n}\n\nmain(process.argv.slice(2)).then(\n (code) => process.exit(code),\n (err) => {\n process.stderr.write(`[flatland-bake] fatal: ${String(err)}\\n`)\n process.exit(1)\n }\n)\n"],"mappings":";;;;;;;;;;;;;;;;AAcA,MAAM,QAAQ;;;;;;;;;;AAWd,eAAe,KAAK,MAAiC;CACnD,MAAM,CAAC,OAAO,GAAG,QAAQ;CAEzB,IAAI,CAAC,SAAS,UAAU,YAAY,UAAU,MAAM;EAClD,QAAQ,OAAO,MAAM,QAAQ,IAAI;EACjC,OAAO;CACT;CAEA,MAAM,EAAE,QAAQ,cAAc,eAAe;CAC7C,KAAK,MAAM,WAAW,WACpB,QAAQ,OAAO,MAAM,yBAAyB,QAAQ,GAAG;CAG3D,IAAI,UAAU,UACZ,OAAO,UAAU,MAAM;CAGzB,MAAM,QAAQ,OAAO,MAAM,MAAM,EAAE,SAAS,KAAK;CACjD,IAAI,CAAC,OAAO;EACV,QAAQ,OAAO,MACb,kCAAkC,MAAM,2DAC1C;EACA,OAAO;CACT;CAEA,MAAM,QAAQ,MAAM,UAAU,KAAK;CACnC,IAAI,CAAC,OAAO,OAAO;CAEnB,IAAI;EACF,OAAO,MAAM,MAAM,IAAI,IAAI;CAC7B,SAAS,KAAK;EACZ,QAAQ,OAAO,MACb,0BAA0B,MAAM,KAAK,WAAW,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE,GACnG;EACA,OAAO;CACT;AACF;AAEA,SAAS,UAAU,QAAqC;CACtD,IAAI,OAAO,WAAW,GAAG;EACvB,QAAQ,OAAO,MAAM,6FAA6F;EAClH,OAAO;CACT;CAEA,MAAM,YAAY,KAAK,IAAI,GAAG,OAAO,KAAK,MAAM,EAAE,KAAK,MAAM,CAAC;CAC9D,QAAQ,OAAO,MAAM,sBAAsB;CAC3C,KAAK,MAAM,KAAK,QAAQ;EACtB,MAAM,MAAM,IAAI,OAAO,YAAY,EAAE,KAAK,MAAM;EAChD,QAAQ,OAAO,MAAM,KAAK,EAAE,OAAO,IAAI,IAAI,EAAE,YAAY,KAAK,EAAE,YAAY,IAAI;CAClF;CACA,OAAO;AACT;AAEA,eAAe,UAAU,KAA+C;CACtE,IAAI,CAAC,WAAW,IAAI,aAAa,GAAG;EAClC,QAAQ,OAAO,MACb,0BAA0B,IAAI,KAAK,UAAU,IAAI,YAAY,6BAA6B,IAAI,cAAc,GAC9G;EACA,OAAO;CACT;CAKA,MAAM,SAAQ,MAHK,OAAO,cAAc,IAAI,aAAa,CAAC,CAAC,MAAA,CAGzC;CAClB,IAAI,CAAC,SAAS,OAAO,MAAM,QAAQ,YAAY;EAC7C,QAAQ,OAAO,MACb,0BAA0B,IAAI,KAAK,UAAU,IAAI,YAAY,oCAC/D;EACA,OAAO;CACT;CACA,OAAO;AACT;AAEA,KAAK,QAAQ,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,MACzB,SAAS,QAAQ,KAAK,IAAI,IAC1B,QAAQ;CACP,QAAQ,OAAO,MAAM,0BAA0B,OAAO,GAAG,EAAE,GAAG;CAC9D,QAAQ,KAAK,CAAC;AAChB,CACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
//#region src/devtimeWarn.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Emit a console warning gated on `NODE_ENV !== 'production'`. Deduped
|
|
6
|
+
* per `(category, url)` — the same warning never fires twice.
|
|
7
|
+
*
|
|
8
|
+
* Shared by every sidecar-using loader so warnings surface uniformly
|
|
9
|
+
* across the ecosystem (normals, fonts, atlases, …).
|
|
10
|
+
*
|
|
11
|
+
* @param category short tag identifying what system warned (e.g. 'normal')
|
|
12
|
+
* @param url absolute URL or path of the asset
|
|
13
|
+
* @param message the message shown to the user
|
|
14
|
+
*/
|
|
15
|
+
declare function devtimeWarn(category: string, url: string, message: string): void;
|
|
16
|
+
/** Clear the devtime-warning dedupe cache. Intended for tests. */
|
|
17
|
+
declare function _resetDevtimeWarnings(): void;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { _resetDevtimeWarnings, devtimeWarn };
|
|
20
|
+
//# sourceMappingURL=devtimeWarn.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"devtimeWarn.d.ts","names":[],"sources":["../src/devtimeWarn.ts"],"mappings":";;;;;;;;;;;;;;iBAagB,YAAY,kBAAkB,aAAa;;iBAa3C"}
|
package/dist/devtimeWarn.js
CHANGED
|
@@ -1,24 +1,44 @@
|
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
//#region src/devtimeWarn.ts
|
|
1
5
|
const _warned = /* @__PURE__ */ new Map();
|
|
6
|
+
/**
|
|
7
|
+
* Emit a console warning gated on `NODE_ENV !== 'production'`. Deduped
|
|
8
|
+
* per `(category, url)` — the same warning never fires twice.
|
|
9
|
+
*
|
|
10
|
+
* Shared by every sidecar-using loader so warnings surface uniformly
|
|
11
|
+
* across the ecosystem (normals, fonts, atlases, …).
|
|
12
|
+
*
|
|
13
|
+
* @param category short tag identifying what system warned (e.g. 'normal')
|
|
14
|
+
* @param url absolute URL or path of the asset
|
|
15
|
+
* @param message the message shown to the user
|
|
16
|
+
*/
|
|
2
17
|
function devtimeWarn(category, url, message) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
18
|
+
if (isProduction()) return;
|
|
19
|
+
let set = _warned.get(category);
|
|
20
|
+
if (!set) {
|
|
21
|
+
set = /* @__PURE__ */ new Set();
|
|
22
|
+
_warned.set(category, set);
|
|
23
|
+
}
|
|
24
|
+
if (set.has(url)) return;
|
|
25
|
+
set.add(url);
|
|
26
|
+
console.warn(`[${category}] ${message}`);
|
|
12
27
|
}
|
|
28
|
+
/** Clear the devtime-warning dedupe cache. Intended for tests. */
|
|
13
29
|
function _resetDevtimeWarnings() {
|
|
14
|
-
|
|
30
|
+
_warned.clear();
|
|
15
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Type-agnostic NODE_ENV check. Reads `globalThis.process.env.NODE_ENV`
|
|
34
|
+
* without forcing consumers to depend on `@types/node` — browser-only
|
|
35
|
+
* packages (e.g. mini-breakout, example apps) inline-consume this
|
|
36
|
+
* module through the source export and would otherwise hit TS2591.
|
|
37
|
+
*/
|
|
16
38
|
function isProduction() {
|
|
17
|
-
|
|
18
|
-
return proc?.env?.["NODE_ENV"] === "production";
|
|
39
|
+
return globalThis.process?.env?.["NODE_ENV"] === "production";
|
|
19
40
|
}
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
};
|
|
41
|
+
//#endregion
|
|
42
|
+
export { _resetDevtimeWarnings, devtimeWarn };
|
|
43
|
+
|
|
24
44
|
//# sourceMappingURL=devtimeWarn.js.map
|
package/dist/devtimeWarn.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/devtimeWarn.ts"],"sourcesContent":["const _warned = new Map<string, Set<string>>()\n\n/**\n * Emit a console warning gated on `NODE_ENV !== 'production'`. Deduped\n * per `(category, url)` — the same warning never fires twice.\n *\n * Shared by every sidecar-using loader so warnings surface uniformly\n * across the ecosystem (normals, fonts, atlases, …).\n *\n * @param category short tag identifying what system warned (e.g. 'normal')\n * @param url absolute URL or path of the asset\n * @param message the message shown to the user\n */\nexport function devtimeWarn(category: string, url: string, message: string): void {\n if (isProduction()) return\n let set = _warned.get(category)\n if (!set) {\n set = new Set()\n _warned.set(category, set)\n }\n if (set.has(url)) return\n set.add(url)\n console.warn(`[${category}] ${message}`)\n}\n\n/** Clear the devtime-warning dedupe cache. Intended for tests. */\nexport function _resetDevtimeWarnings(): void {\n _warned.clear()\n}\n\n/**\n * Type-agnostic NODE_ENV check. Reads `globalThis.process.env.NODE_ENV`\n * without forcing consumers to depend on `@types/node` — browser-only\n * packages (e.g. mini-breakout, example apps) inline-consume this\n * module through the source export and would otherwise hit TS2591.\n */\nfunction isProduction(): boolean {\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n return proc?.env?.['NODE_ENV'] === 'production'\n}\n"],"mappings":"AAAA,MAAM,
|
|
1
|
+
{"version":3,"file":"devtimeWarn.js","names":[],"sources":["../src/devtimeWarn.ts"],"sourcesContent":["const _warned = new Map<string, Set<string>>()\n\n/**\n * Emit a console warning gated on `NODE_ENV !== 'production'`. Deduped\n * per `(category, url)` — the same warning never fires twice.\n *\n * Shared by every sidecar-using loader so warnings surface uniformly\n * across the ecosystem (normals, fonts, atlases, …).\n *\n * @param category short tag identifying what system warned (e.g. 'normal')\n * @param url absolute URL or path of the asset\n * @param message the message shown to the user\n */\nexport function devtimeWarn(category: string, url: string, message: string): void {\n if (isProduction()) return\n let set = _warned.get(category)\n if (!set) {\n set = new Set()\n _warned.set(category, set)\n }\n if (set.has(url)) return\n set.add(url)\n console.warn(`[${category}] ${message}`)\n}\n\n/** Clear the devtime-warning dedupe cache. Intended for tests. */\nexport function _resetDevtimeWarnings(): void {\n _warned.clear()\n}\n\n/**\n * Type-agnostic NODE_ENV check. Reads `globalThis.process.env.NODE_ENV`\n * without forcing consumers to depend on `@types/node` — browser-only\n * packages (e.g. mini-breakout, example apps) inline-consume this\n * module through the source export and would otherwise hit TS2591.\n */\nfunction isProduction(): boolean {\n const proc = (globalThis as { process?: { env?: Record<string, string | undefined> } }).process\n return proc?.env?.['NODE_ENV'] === 'production'\n}\n"],"mappings":";;;;AAAA,MAAM,0BAAU,IAAI,IAAyB;;;;;;;;;;;;AAa7C,SAAgB,YAAY,UAAkB,KAAa,SAAuB;CAChF,IAAI,aAAa,GAAG;CACpB,IAAI,MAAM,QAAQ,IAAI,QAAQ;CAC9B,IAAI,CAAC,KAAK;EACR,sBAAM,IAAI,IAAI;EACd,QAAQ,IAAI,UAAU,GAAG;CAC3B;CACA,IAAI,IAAI,IAAI,GAAG,GAAG;CAClB,IAAI,IAAI,GAAG;CACX,QAAQ,KAAK,IAAI,SAAS,IAAI,SAAS;AACzC;;AAGA,SAAgB,wBAA8B;CAC5C,QAAQ,MAAM;AAChB;;;;;;;AAQA,SAAS,eAAwB;CAE/B,OADc,WAA0E,SAC3E,MAAM,gBAAgB;AACrC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import __tsdown_shims_path from 'node:path';
|
|
2
|
+
import __tsdown_shims_url from 'node:url';
|
|
3
|
+
import { BakerRegistration } from "./types.js";
|
|
4
|
+
//#region src/discovery.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Discover registered bakers by walking `node_modules` near the current
|
|
7
|
+
* working directory. Supports both flat and pnpm-symlinked layouts: every
|
|
8
|
+
* `package.json` that declares a `flatland.bake` (or legacy
|
|
9
|
+
* `flatland.bakers`) field is picked up.
|
|
10
|
+
*
|
|
11
|
+
* Conflicts (multiple packages registering the same baker name) are reported;
|
|
12
|
+
* the first match wins and the rest are returned as warnings so the caller
|
|
13
|
+
* can decide whether to fail or log.
|
|
14
|
+
*/
|
|
15
|
+
declare function discoverBakers(cwd?: string): {
|
|
16
|
+
bakers: BakerRegistration[];
|
|
17
|
+
conflicts: string[];
|
|
18
|
+
};
|
|
19
|
+
//#endregion
|
|
20
|
+
export { discoverBakers };
|
|
21
|
+
//# sourceMappingURL=discovery.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discovery.d.ts","names":[],"sources":["../src/discovery.ts"],"mappings":";;;;;;;;;;;;;;iBAmBgB,eAAe;EAC7B,QAAQ;EACR"}
|
package/dist/discovery.js
CHANGED
|
@@ -1,111 +1,119 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import "node:path";
|
|
2
|
+
import "node:url";
|
|
3
|
+
import.meta.url;
|
|
4
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
5
|
+
import { join, resolve } from "node:path";
|
|
6
|
+
//#region src/discovery.ts
|
|
7
|
+
/**
|
|
8
|
+
* Discover registered bakers by walking `node_modules` near the current
|
|
9
|
+
* working directory. Supports both flat and pnpm-symlinked layouts: every
|
|
10
|
+
* `package.json` that declares a `flatland.bake` (or legacy
|
|
11
|
+
* `flatland.bakers`) field is picked up.
|
|
12
|
+
*
|
|
13
|
+
* Conflicts (multiple packages registering the same baker name) are reported;
|
|
14
|
+
* the first match wins and the rest are returned as warnings so the caller
|
|
15
|
+
* can decide whether to fail or log.
|
|
16
|
+
*/
|
|
3
17
|
function discoverBakers(cwd = process.cwd()) {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return { bakers: Array.from(bakers.values()), conflicts };
|
|
18
|
+
const seenPackages = /* @__PURE__ */ new Set();
|
|
19
|
+
const bakers = /* @__PURE__ */ new Map();
|
|
20
|
+
const conflicts = [];
|
|
21
|
+
const selfDir = findPackageRoot(cwd);
|
|
22
|
+
if (selfDir) readPackage(selfDir, bakers, conflicts, seenPackages);
|
|
23
|
+
for (const nodeModulesDir of findNodeModulesDirs(cwd)) scanNodeModules(nodeModulesDir, bakers, conflicts, seenPackages);
|
|
24
|
+
return {
|
|
25
|
+
bakers: Array.from(bakers.values()),
|
|
26
|
+
conflicts
|
|
27
|
+
};
|
|
15
28
|
}
|
|
29
|
+
/** Walk upward from `start` until a directory with package.json is found. */
|
|
16
30
|
function findPackageRoot(start) {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
let dir = resolve(start);
|
|
32
|
+
while (true) {
|
|
33
|
+
if (existsSync(join(dir, "package.json"))) return dir;
|
|
34
|
+
const parent = resolve(dir, "..");
|
|
35
|
+
if (parent === dir) return null;
|
|
36
|
+
dir = parent;
|
|
37
|
+
}
|
|
24
38
|
}
|
|
39
|
+
/** Walk upward from `cwd` collecting each `node_modules` directory we find. */
|
|
25
40
|
function findNodeModulesDirs(cwd) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
return dirs;
|
|
41
|
+
const dirs = [];
|
|
42
|
+
let dir = resolve(cwd);
|
|
43
|
+
while (true) {
|
|
44
|
+
const nm = join(dir, "node_modules");
|
|
45
|
+
if (existsSync(nm) && statSync(nm).isDirectory()) dirs.push(nm);
|
|
46
|
+
const parent = resolve(dir, "..");
|
|
47
|
+
if (parent === dir) break;
|
|
48
|
+
dir = parent;
|
|
49
|
+
}
|
|
50
|
+
return dirs;
|
|
38
51
|
}
|
|
39
52
|
function scanNodeModules(nodeModulesDir, bakers, conflicts, seenPackages) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
} else {
|
|
60
|
-
readPackage(entryPath, bakers, conflicts, seenPackages);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
53
|
+
let entries;
|
|
54
|
+
try {
|
|
55
|
+
entries = readdirSync(nodeModulesDir);
|
|
56
|
+
} catch {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
for (const entry of entries) {
|
|
60
|
+
if (entry.startsWith(".")) continue;
|
|
61
|
+
const entryPath = join(nodeModulesDir, entry);
|
|
62
|
+
if (entry.startsWith("@")) {
|
|
63
|
+
let scoped;
|
|
64
|
+
try {
|
|
65
|
+
scoped = readdirSync(entryPath);
|
|
66
|
+
} catch {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
for (const scopedName of scoped) readPackage(join(entryPath, scopedName), bakers, conflicts, seenPackages);
|
|
70
|
+
} else readPackage(entryPath, bakers, conflicts, seenPackages);
|
|
71
|
+
}
|
|
63
72
|
}
|
|
64
73
|
function readPackage(packageDir, bakers, conflicts, seenPackages) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
bakers.set(decl.name, registration);
|
|
95
|
-
}
|
|
74
|
+
const pkgPath = join(packageDir, "package.json");
|
|
75
|
+
if (!existsSync(pkgPath)) return;
|
|
76
|
+
let pkg;
|
|
77
|
+
try {
|
|
78
|
+
pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
79
|
+
} catch {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
const name = pkg.name ?? packageDir;
|
|
83
|
+
if (seenPackages.has(name)) return;
|
|
84
|
+
seenPackages.add(name);
|
|
85
|
+
const manifest = resolveManifest(pkg.flatland, name, conflicts);
|
|
86
|
+
if (!manifest || manifest.length === 0) return;
|
|
87
|
+
for (const decl of manifest) {
|
|
88
|
+
const resolvedEntry = resolve(packageDir, decl.entry);
|
|
89
|
+
const registration = {
|
|
90
|
+
name: decl.name,
|
|
91
|
+
description: decl.description,
|
|
92
|
+
entry: decl.entry,
|
|
93
|
+
packageName: name,
|
|
94
|
+
resolvedEntry
|
|
95
|
+
};
|
|
96
|
+
const existing = bakers.get(decl.name);
|
|
97
|
+
if (existing) {
|
|
98
|
+
conflicts.push(`baker "${decl.name}" is registered by both "${existing.packageName}" and "${name}" — using "${existing.packageName}"`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
bakers.set(decl.name, registration);
|
|
102
|
+
}
|
|
96
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Prefer `flatland.bake` (current); fall back to `flatland.bakers`
|
|
106
|
+
* (legacy) with a one-time deprecation warning per package.
|
|
107
|
+
*/
|
|
97
108
|
function resolveManifest(manifest, packageName, conflicts) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
return manifest.bakers;
|
|
105
|
-
}
|
|
106
|
-
return void 0;
|
|
109
|
+
if (!manifest) return void 0;
|
|
110
|
+
if (manifest.bake && manifest.bake.length > 0) return manifest.bake;
|
|
111
|
+
if (manifest.bakers && manifest.bakers.length > 0) {
|
|
112
|
+
conflicts.push(`"${packageName}" uses deprecated \`flatland.bakers\` — rename to \`flatland.bake\` (the legacy key is accepted for one release)`);
|
|
113
|
+
return manifest.bakers;
|
|
114
|
+
}
|
|
107
115
|
}
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
116
|
+
//#endregion
|
|
117
|
+
export { discoverBakers };
|
|
118
|
+
|
|
111
119
|
//# sourceMappingURL=discovery.js.map
|
package/dist/discovery.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/discovery.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport type {
|
|
1
|
+
{"version":3,"file":"discovery.js","names":[],"sources":["../src/discovery.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport type { BakerRegistration, FlatlandManifest, FlatlandManifestEntry } from './types.js'\n\ninterface PackageJson {\n name?: string\n flatland?: FlatlandManifest\n}\n\n/**\n * Discover registered bakers by walking `node_modules` near the current\n * working directory. Supports both flat and pnpm-symlinked layouts: every\n * `package.json` that declares a `flatland.bake` (or legacy\n * `flatland.bakers`) field is picked up.\n *\n * Conflicts (multiple packages registering the same baker name) are reported;\n * the first match wins and the rest are returned as warnings so the caller\n * can decide whether to fail or log.\n */\nexport function discoverBakers(cwd: string = process.cwd()): {\n bakers: BakerRegistration[]\n conflicts: string[]\n} {\n const seenPackages = new Set<string>()\n const bakers = new Map<string, BakerRegistration>()\n const conflicts: string[] = []\n\n // CWD-package self-discovery: if the user is iterating inside a package\n // that declares a baker, let them invoke it without symlinking into\n // node_modules first. Registers the baker before node_modules scans so a\n // local version always wins against an older installed copy.\n const selfDir = findPackageRoot(cwd)\n if (selfDir) {\n readPackage(selfDir, bakers, conflicts, seenPackages)\n }\n\n for (const nodeModulesDir of findNodeModulesDirs(cwd)) {\n scanNodeModules(nodeModulesDir, bakers, conflicts, seenPackages)\n }\n\n return { bakers: Array.from(bakers.values()), conflicts }\n}\n\n/** Walk upward from `start` until a directory with package.json is found. */\nfunction findPackageRoot(start: string): string | null {\n let dir = resolve(start)\n while (true) {\n if (existsSync(join(dir, 'package.json'))) return dir\n const parent = resolve(dir, '..')\n if (parent === dir) return null\n dir = parent\n }\n}\n\n/** Walk upward from `cwd` collecting each `node_modules` directory we find. */\nfunction findNodeModulesDirs(cwd: string): string[] {\n const dirs: string[] = []\n let dir = resolve(cwd)\n while (true) {\n const nm = join(dir, 'node_modules')\n if (existsSync(nm) && statSync(nm).isDirectory()) {\n dirs.push(nm)\n }\n const parent = resolve(dir, '..')\n if (parent === dir) break\n dir = parent\n }\n return dirs\n}\n\nfunction scanNodeModules(\n nodeModulesDir: string,\n bakers: Map<string, BakerRegistration>,\n conflicts: string[],\n seenPackages: Set<string>\n): void {\n let entries: string[]\n try {\n entries = readdirSync(nodeModulesDir)\n } catch {\n return\n }\n\n for (const entry of entries) {\n if (entry.startsWith('.')) continue\n const entryPath = join(nodeModulesDir, entry)\n\n if (entry.startsWith('@')) {\n // Scoped packages: @scope/name\n let scoped: string[]\n try {\n scoped = readdirSync(entryPath)\n } catch {\n continue\n }\n for (const scopedName of scoped) {\n readPackage(join(entryPath, scopedName), bakers, conflicts, seenPackages)\n }\n } else {\n readPackage(entryPath, bakers, conflicts, seenPackages)\n }\n }\n}\n\nfunction readPackage(\n packageDir: string,\n bakers: Map<string, BakerRegistration>,\n conflicts: string[],\n seenPackages: Set<string>\n): void {\n const pkgPath = join(packageDir, 'package.json')\n if (!existsSync(pkgPath)) return\n\n let pkg: PackageJson\n try {\n pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as PackageJson\n } catch {\n return\n }\n\n const name = pkg.name ?? packageDir\n if (seenPackages.has(name)) return\n seenPackages.add(name)\n\n const manifest = resolveManifest(pkg.flatland, name, conflicts)\n if (!manifest || manifest.length === 0) return\n\n for (const decl of manifest) {\n const resolvedEntry = resolve(packageDir, decl.entry)\n const registration: BakerRegistration = {\n name: decl.name,\n description: decl.description,\n entry: decl.entry,\n packageName: name,\n resolvedEntry,\n }\n\n const existing = bakers.get(decl.name)\n if (existing) {\n conflicts.push(\n `baker \"${decl.name}\" is registered by both \"${existing.packageName}\" and \"${name}\" — using \"${existing.packageName}\"`\n )\n continue\n }\n bakers.set(decl.name, registration)\n }\n}\n\n/**\n * Prefer `flatland.bake` (current); fall back to `flatland.bakers`\n * (legacy) with a one-time deprecation warning per package.\n */\nfunction resolveManifest(\n manifest: FlatlandManifest | undefined,\n packageName: string,\n conflicts: string[]\n): FlatlandManifestEntry[] | undefined {\n if (!manifest) return undefined\n if (manifest.bake && manifest.bake.length > 0) return manifest.bake\n if (manifest.bakers && manifest.bakers.length > 0) {\n conflicts.push(\n `\"${packageName}\" uses deprecated \\`flatland.bakers\\` — rename to \\`flatland.bake\\` (the legacy key is accepted for one release)`\n )\n return manifest.bakers\n }\n return undefined\n}\n"],"mappings":"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"}
|