@gjsify/create-app 0.39.0 → 0.41.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.
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Package managers a scaffolded project can be installed with. npm/yarn/pnpm/
3
+ * gjsify all work on a Node host — templates declare every dependency explicitly,
4
+ * `@gjsify/rolldown-native` included, which the three would otherwise skip as an
5
+ * optional peer of `@gjsify/cli`. `gjsify` is the only one of the six that works
6
+ * on a host with no Node.js.
7
+ */
8
+ export declare const PACKAGE_MANAGERS: readonly ["npm", "yarn", "pnpm", "gjsify", "bun", "deno"];
9
+ export type PackageManager = (typeof PACKAGE_MANAGERS)[number];
10
+ /**
11
+ * The `install` argv per manager, each read off the tool's own `--help` rather
12
+ * than assumed: `bun install` and a BARE `deno install` mean "install what
13
+ * package.json lists" — `deno install --global` is the unrelated
14
+ * install-a-script mode.
15
+ */
16
+ export declare const INSTALL_ARGV: Record<PackageManager, readonly string[]>;
17
+ /** The command prefix that runs a package script under `manager`. */
18
+ export declare function runScriptCommand(manager: PackageManager): string;
19
+ /**
20
+ * Which managers can install a project the user intends to RUN on `<runtime>` —
21
+ * not taste, but the module layout each runtime resolves against. `gjs` is the
22
+ * Node-free column, where npm/yarn/pnpm are not discouraged but ABSENT; bun writes
23
+ * its own layout; `deno install` populates the `node_modules` that
24
+ * `--node-modules-dir=manual` (how `gjsify run --runtime deno` launches) reads
25
+ * without re-resolving. The KEYS double as "runtimes this scaffolder has an
26
+ * installer for", and the spec asserts they equal the CLI's `EXAMPLE_RUNTIMES`, so
27
+ * a runtime added upstream fails loudly instead of vanishing from every prompt.
28
+ */
29
+ export declare const RUNTIME_PACKAGE_MANAGERS: Readonly<Record<string, readonly PackageManager[]>>;
30
+ /** One-line label for each runtime, shown beside it in the picker. */
31
+ export declare const RUNTIME_DESCRIPTIONS: Readonly<Record<string, string>>;
32
+ /**
33
+ * The managers that install for `runtime`, `undefined` when unmapped — not a
34
+ * permissive fallback: unmapped means the template is newer than the scaffolder,
35
+ * and guessing an installer buries the cause.
36
+ */
37
+ export declare function packageManagersForRuntime(runtime: string): readonly PackageManager[] | undefined;
38
+ /** `true` when this scaffolder knows how to install for `runtime`. */
39
+ export declare function isKnownRuntime(runtime: string): boolean;
40
+ /**
41
+ * The JS runtime this scaffolder is itself executing on. A LOCAL implementation,
42
+ * kept for the two measured reasons in the file header and held to the canonical
43
+ * one by `runtimes.spec.ts`, which runs on all four runtimes and compares against
44
+ * `@gjsify/rolldown-plugin-gjsify/runtime`.
45
+ *
46
+ * Probe order is load-bearing: **Bun and Deno first**, because both fake
47
+ * `process.versions.node` for npm compatibility and GJS's `@gjsify/process` shim
48
+ * does too, making that read a false Node positive on three of the four hosts.
49
+ */
50
+ export declare function hostRuntime(): string;
51
+ /**
52
+ * Which runtime to start on: the host when the template supports it — "whatever
53
+ * you are already running" needs no second toolchain — else the template's own
54
+ * first declaration, which is the canonical one.
55
+ *
56
+ * `manager` OVERRIDES that, because a pinned manager already names a runtime.
57
+ * Without it, `create-app x -t cli -p bun` resolved to the host (`node`), found
58
+ * bun illegal there and refused a combination the user had spelled out. "First
59
+ * declared runtime this manager installs for" needs no per-manager table: bun and
60
+ * deno serve one each, npm/yarn/pnpm only node, and `gjsify` — legal on gjs and
61
+ * node — lands on gjs, the column it was written for.
62
+ *
63
+ * `undefined` for an empty list, or when nothing declared installs with
64
+ * `manager`; the caller decides whether that is an error.
65
+ */
66
+ export declare function defaultRuntimeFor(runtimes: readonly string[], manager?: PackageManager): string | undefined;
67
+ /**
68
+ * The script that launches the project on `runtime`. Template convention: `start`
69
+ * is the gjs entry (what `gjsify.main` points at), every other runtime a suffixed
70
+ * sibling — `start:node`, `start:bun`, `start:deno`.
71
+ */
72
+ export declare function startScriptFor(runtime: string): string;
@@ -0,0 +1,141 @@
1
+ // Runtime ↔ package-manager model for `gjsify create` / `npm create @gjsify/app`.
2
+ // gjsify targets four runtimes and each has its own installer, so assuming `npm`
3
+ // printed next steps a `deno` user cannot run and, with `--install`, ran the wrong
4
+ // installer for the layout their runtime resolves against.
5
+ //
6
+ // WHY THE RUNTIME LIST IS NOT IMPORTED FROM `@gjsify/cli`, which owns
7
+ // `EXAMPLE_RUNTIMES`: nothing here twins it. The runtimes OFFERED come from the
8
+ // template's own manifest, and the table below is a different fact (which
9
+ // installer serves which runtime) whose key set `runtimes.spec.ts` asserts
10
+ // against `EXAMPLE_RUNTIMES`. A runtime import was measured and rejected twice —
11
+ // `@gjsify/cli` as a `dependency` closes the loop cli → create-app → cli, and
12
+ // `@gjsify/workspace`'s `topologicalSort` throws on a prod cycle ("dependency
13
+ // cycle detected involving @gjsify/cli, @gjsify/create-app"), taking `build:infra`
14
+ // with it; while `@gjsify/rolldown-plugin-gjsify/runtime`, the cycle-free leaf
15
+ // owning `hostRuntime()`, drags typescript + deepkit + sass + lightningcss into a
16
+ // scaffolder whose whole runtime dependency set is `yargs`. Both are
17
+ // devDependencies, and the spec cross-checks against them.
18
+ /**
19
+ * Package managers a scaffolded project can be installed with. npm/yarn/pnpm/
20
+ * gjsify all work on a Node host — templates declare every dependency explicitly,
21
+ * `@gjsify/rolldown-native` included, which the three would otherwise skip as an
22
+ * optional peer of `@gjsify/cli`. `gjsify` is the only one of the six that works
23
+ * on a host with no Node.js.
24
+ */
25
+ export const PACKAGE_MANAGERS = ['npm', 'yarn', 'pnpm', 'gjsify', 'bun', 'deno'];
26
+ /**
27
+ * The `install` argv per manager, each read off the tool's own `--help` rather
28
+ * than assumed: `bun install` and a BARE `deno install` mean "install what
29
+ * package.json lists" — `deno install --global` is the unrelated
30
+ * install-a-script mode.
31
+ */
32
+ export const INSTALL_ARGV = {
33
+ npm: ['install', '--no-audit', '--no-fund'],
34
+ yarn: ['install'],
35
+ pnpm: ['install'],
36
+ gjsify: ['install'],
37
+ bun: ['install'],
38
+ deno: ['install'],
39
+ };
40
+ /**
41
+ * How each manager spells "run the package script `<name>`" — six managers, four
42
+ * spellings. Printing the one the user's manager accepts is why the choice is
43
+ * threaded this far: `npm run dev` is a dead end for someone who installed with
44
+ * deno.
45
+ */
46
+ const RUN_SCRIPT_PREFIX = {
47
+ npm: 'npm run',
48
+ yarn: 'yarn',
49
+ pnpm: 'pnpm',
50
+ gjsify: 'gjsify run',
51
+ bun: 'bun run',
52
+ deno: 'deno task',
53
+ };
54
+ /** The command prefix that runs a package script under `manager`. */
55
+ export function runScriptCommand(manager) {
56
+ return RUN_SCRIPT_PREFIX[manager];
57
+ }
58
+ /**
59
+ * Which managers can install a project the user intends to RUN on `<runtime>` —
60
+ * not taste, but the module layout each runtime resolves against. `gjs` is the
61
+ * Node-free column, where npm/yarn/pnpm are not discouraged but ABSENT; bun writes
62
+ * its own layout; `deno install` populates the `node_modules` that
63
+ * `--node-modules-dir=manual` (how `gjsify run --runtime deno` launches) reads
64
+ * without re-resolving. The KEYS double as "runtimes this scaffolder has an
65
+ * installer for", and the spec asserts they equal the CLI's `EXAMPLE_RUNTIMES`, so
66
+ * a runtime added upstream fails loudly instead of vanishing from every prompt.
67
+ */
68
+ export const RUNTIME_PACKAGE_MANAGERS = {
69
+ gjs: ['gjsify'],
70
+ node: ['npm', 'yarn', 'pnpm', 'gjsify'],
71
+ bun: ['bun'],
72
+ deno: ['deno'],
73
+ };
74
+ /** One-line label for each runtime, shown beside it in the picker. */
75
+ export const RUNTIME_DESCRIPTIONS = {
76
+ gjs: 'GNOME JavaScript — no Node.js needed at runtime.',
77
+ node: 'Node.js — gi:// via the @gjsify/node-gi bridge.',
78
+ bun: 'Bun — runs the same --app node bundle.',
79
+ deno: 'Deno — runs the same --app node bundle.',
80
+ };
81
+ /**
82
+ * The managers that install for `runtime`, `undefined` when unmapped — not a
83
+ * permissive fallback: unmapped means the template is newer than the scaffolder,
84
+ * and guessing an installer buries the cause.
85
+ */
86
+ export function packageManagersForRuntime(runtime) {
87
+ return RUNTIME_PACKAGE_MANAGERS[runtime];
88
+ }
89
+ /** `true` when this scaffolder knows how to install for `runtime`. */
90
+ export function isKnownRuntime(runtime) {
91
+ return packageManagersForRuntime(runtime) !== undefined;
92
+ }
93
+ /**
94
+ * The JS runtime this scaffolder is itself executing on. A LOCAL implementation,
95
+ * kept for the two measured reasons in the file header and held to the canonical
96
+ * one by `runtimes.spec.ts`, which runs on all four runtimes and compares against
97
+ * `@gjsify/rolldown-plugin-gjsify/runtime`.
98
+ *
99
+ * Probe order is load-bearing: **Bun and Deno first**, because both fake
100
+ * `process.versions.node` for npm compatibility and GJS's `@gjsify/process` shim
101
+ * does too, making that read a false Node positive on three of the four hosts.
102
+ */
103
+ export function hostRuntime() {
104
+ const g = globalThis;
105
+ if (typeof g.Bun !== 'undefined')
106
+ return 'bun';
107
+ if (typeof g.Deno !== 'undefined')
108
+ return 'deno';
109
+ if (typeof g.imports?.gi !== 'undefined')
110
+ return 'gjs';
111
+ return 'node';
112
+ }
113
+ /**
114
+ * Which runtime to start on: the host when the template supports it — "whatever
115
+ * you are already running" needs no second toolchain — else the template's own
116
+ * first declaration, which is the canonical one.
117
+ *
118
+ * `manager` OVERRIDES that, because a pinned manager already names a runtime.
119
+ * Without it, `create-app x -t cli -p bun` resolved to the host (`node`), found
120
+ * bun illegal there and refused a combination the user had spelled out. "First
121
+ * declared runtime this manager installs for" needs no per-manager table: bun and
122
+ * deno serve one each, npm/yarn/pnpm only node, and `gjsify` — legal on gjs and
123
+ * node — lands on gjs, the column it was written for.
124
+ *
125
+ * `undefined` for an empty list, or when nothing declared installs with
126
+ * `manager`; the caller decides whether that is an error.
127
+ */
128
+ export function defaultRuntimeFor(runtimes, manager) {
129
+ if (manager)
130
+ return runtimes.find((rt) => packageManagersForRuntime(rt)?.includes(manager));
131
+ const host = hostRuntime();
132
+ return runtimes.includes(host) ? host : runtimes[0];
133
+ }
134
+ /**
135
+ * The script that launches the project on `runtime`. Template convention: `start`
136
+ * is the gjs entry (what `gjsify.main` points at), every other runtime a suffixed
137
+ * sibling — `start:node`, `start:bun`, `start:deno`.
138
+ */
139
+ export function startScriptFor(runtime) {
140
+ return runtime === 'gjs' ? 'start' : `start:${runtime}`;
141
+ }
@@ -0,0 +1,63 @@
1
+ import { type PackageManager } from './runtimes.js';
2
+ /** What the caller should do about a choice. */
3
+ export type Selection<T> =
4
+ /** Settled, and the user already knows why (they said so on the command line). */
5
+ {
6
+ kind: 'value';
7
+ value: T;
8
+ }
9
+ /** Settled without being asked — print `notice` so it is not a silent default. */
10
+ | {
11
+ kind: 'auto';
12
+ value: T;
13
+ notice: string;
14
+ }
15
+ /** Ask, opening on `initial`. */
16
+ | {
17
+ kind: 'prompt';
18
+ choices: readonly T[];
19
+ initial: T;
20
+ }
21
+ /** Refuse, with a message that says what to pass instead. */
22
+ | {
23
+ kind: 'error';
24
+ message: string;
25
+ };
26
+ export interface RuntimeQuestion {
27
+ flag?: string;
28
+ /** The runtimes the chosen template declares AND this scaffolder can serve. */
29
+ offered: readonly string[];
30
+ /** `--package-manager`, when given — it narrows the runtime. */
31
+ manager?: PackageManager;
32
+ /** Whether stdin is a TTY, i.e. whether a prompt is possible at all. */
33
+ interactive: boolean;
34
+ }
35
+ /**
36
+ * Settle which runtime the project is set up for.
37
+ *
38
+ * `--runtime` is deliberately NOT mandatory on a non-TTY the way `--template` is:
39
+ * a template is a choice of WHAT gets written and has no defensible default, while
40
+ * the runtime changes not one scaffolded byte (every template ships a bundle for
41
+ * every runtime it declares) and only names the start script. So it defaults —
42
+ * announced, not assumed.
43
+ */
44
+ export declare function selectRuntime(question: RuntimeQuestion): Selection<string>;
45
+ export interface PackageManagerQuestion {
46
+ flag?: PackageManager;
47
+ /** The runtime already settled by {@link selectRuntime}. */
48
+ runtime: string;
49
+ /** Whether `--install` is set — i.e. whether this choice actually runs something. */
50
+ install: boolean;
51
+ interactive: boolean;
52
+ }
53
+ /**
54
+ * Settle which package manager installs the project.
55
+ *
56
+ * Two rules differ from the runtime above, both because a manager DOES something.
57
+ * A runtime offering exactly one is not a question — bun and deno install for
58
+ * themselves, the gjs column has no Node to run npm with — so it is taken and
59
+ * named rather than prompted. And with `--install` on a non-TTY, the flag is
60
+ * REQUIRED: this is the one default that would reach the user's disk, picking the
61
+ * installer that writes their node_modules and lockfile.
62
+ */
63
+ export declare function selectPackageManager(question: PackageManagerQuestion): Selection<PackageManager>;
package/lib/select.js ADDED
@@ -0,0 +1,113 @@
1
+ // The runtime / package-manager decision, as data. These functions decide;
2
+ // `index.ts` is the shell that prints, prompts and exits. Each answer depends on a
3
+ // flag, on what the template declares, on the other question's answer, and on
4
+ // whether there is a TTY — deciding that inline in the yargs handler made it
5
+ // reachable only by spawning the binary, so the half that matters most, the
6
+ // non-TTY one, was covered by nothing but an end-to-end scaffold.
7
+ import { defaultRuntimeFor, packageManagersForRuntime } from './runtimes.js';
8
+ /**
9
+ * Settle which runtime the project is set up for.
10
+ *
11
+ * `--runtime` is deliberately NOT mandatory on a non-TTY the way `--template` is:
12
+ * a template is a choice of WHAT gets written and has no defensible default, while
13
+ * the runtime changes not one scaffolded byte (every template ships a bundle for
14
+ * every runtime it declares) and only names the start script. So it defaults —
15
+ * announced, not assumed.
16
+ */
17
+ export function selectRuntime(question) {
18
+ const { flag, offered, manager, interactive } = question;
19
+ if (flag !== undefined) {
20
+ if (offered.length > 0 && !offered.includes(flag)) {
21
+ return {
22
+ kind: 'error',
23
+ message: `this template does not run on "${flag}". Declared runtimes: ${offered.join(', ')}.`,
24
+ };
25
+ }
26
+ return { kind: 'value', value: flag };
27
+ }
28
+ // One declared runtime is not a choice — take it without asking.
29
+ if (offered.length === 1)
30
+ return { kind: 'value', value: offered[0] };
31
+ // None at all means the template declares runtimes this build has no
32
+ // installer mapping for (`supportedRuntimes` drops the ones it cannot serve),
33
+ // i.e. the template is newer than the scaffolder. Refuse rather than fall
34
+ // back to the host: picking a runtime the template never claimed buries the
35
+ // real cause under whatever fails next. `--runtime` above still overrides,
36
+ // which is the escape hatch for a template that declares nothing at all.
37
+ if (offered.length === 0) {
38
+ return { kind: 'error', message: 'this template declares no runtime this scaffolder can install for.' };
39
+ }
40
+ const fallback = defaultRuntimeFor(offered, manager);
41
+ if (fallback === undefined) {
42
+ return {
43
+ kind: 'error',
44
+ message: `no runtime this template declares installs with "${manager}". ` +
45
+ `Declared runtimes: ${offered.join(', ')}.`,
46
+ };
47
+ }
48
+ // A pinned manager has ALREADY answered this question — bun installs for bun
49
+ // and nothing else — so prompting would pose a question with one honest
50
+ // answer. It is still reported, because the user did not name a runtime.
51
+ if (manager !== undefined) {
52
+ return {
53
+ kind: 'auto',
54
+ value: fallback,
55
+ notice: `Setting up for ${fallback} — the runtime ${manager} installs for.`,
56
+ };
57
+ }
58
+ if (interactive)
59
+ return { kind: 'prompt', choices: offered, initial: fallback };
60
+ return {
61
+ kind: 'auto',
62
+ value: fallback,
63
+ notice: `--runtime not given, using "${fallback}". Runs on: ${offered.join(', ')}.`,
64
+ };
65
+ }
66
+ /**
67
+ * Settle which package manager installs the project.
68
+ *
69
+ * Two rules differ from the runtime above, both because a manager DOES something.
70
+ * A runtime offering exactly one is not a question — bun and deno install for
71
+ * themselves, the gjs column has no Node to run npm with — so it is taken and
72
+ * named rather than prompted. And with `--install` on a non-TTY, the flag is
73
+ * REQUIRED: this is the one default that would reach the user's disk, picking the
74
+ * installer that writes their node_modules and lockfile.
75
+ */
76
+ export function selectPackageManager(question) {
77
+ const { flag, runtime, install, interactive } = question;
78
+ const managers = packageManagersForRuntime(runtime);
79
+ if (managers === undefined || managers.length === 0) {
80
+ return { kind: 'error', message: `no package manager known for runtime "${runtime}".` };
81
+ }
82
+ if (flag !== undefined) {
83
+ if (!managers.includes(flag)) {
84
+ return {
85
+ kind: 'error',
86
+ message: `"${flag}" cannot install a project you intend to run on ${runtime}. ` +
87
+ `Package managers for ${runtime}: ${managers.join(', ')}.`,
88
+ };
89
+ }
90
+ return { kind: 'value', value: flag };
91
+ }
92
+ if (managers.length === 1) {
93
+ return {
94
+ kind: 'auto',
95
+ value: managers[0],
96
+ notice: `Using ${managers[0]} — the only package manager that installs for ${runtime}.`,
97
+ };
98
+ }
99
+ if (interactive)
100
+ return { kind: 'prompt', choices: managers, initial: managers[0] };
101
+ if (install) {
102
+ return {
103
+ kind: 'error',
104
+ message: '--package-manager is required with --install in non-interactive mode. ' +
105
+ `Package managers for ${runtime}: ${managers.join(', ')}.`,
106
+ };
107
+ }
108
+ return {
109
+ kind: 'auto',
110
+ value: managers[0],
111
+ notice: `--package-manager not given, using "${managers[0]}" for ${runtime}.`,
112
+ };
113
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/create-app",
3
- "version": "0.39.0",
3
+ "version": "0.41.0",
4
4
  "description": "Create a new Gjsify project",
5
5
  "type": "module",
6
6
  "main": "lib/create.js",
@@ -18,10 +18,18 @@
18
18
  },
19
19
  "bin": "./lib/index.js",
20
20
  "scripts": {
21
- "clear": "gjsify clear lib tsconfig.tsbuildinfo",
21
+ "clear": "gjsify clear lib tsconfig.tsbuildinfo test.gjs.mjs test.node.mjs",
22
22
  "check": "gjsify tsc --noEmit",
23
23
  "build": "node scripts/process-template.mjs && gjsify tsc && node scripts/set-bin-mode.mjs",
24
- "test": "echo 'nothing to do'"
24
+ "build:test": "gjsify run build:test:gjs && gjsify run build:test:node",
25
+ "build:test:gjs": "gjsify build src/test.mts --app gjs --outfile test.gjs.mjs",
26
+ "build:test:node": "gjsify build src/test.mts --app node --outfile test.node.mjs",
27
+ "test": "gjsify run build:test && gjsify run test:gjs && gjsify run test:node",
28
+ "test:gjs": "gjsify run test.gjs.mjs",
29
+ "test:node": "node test.node.mjs",
30
+ "test:bun": "bun test.node.mjs",
31
+ "test:deno": "deno run -A --no-lock --node-modules-dir=none test.node.mjs",
32
+ "test:cross-runtime": "gjsify run build:test && gjsify run test:gjs && gjsify run test:node && gjsify run test:bun && gjsify run test:deno"
25
33
  },
26
34
  "keywords": [
27
35
  "gjs",
@@ -38,6 +46,9 @@
38
46
  "yargs": "^18.0.0"
39
47
  },
40
48
  "devDependencies": {
49
+ "@gjsify/cli": "^0.41.0",
50
+ "@gjsify/rolldown-plugin-gjsify": "^0.41.0",
51
+ "@gjsify/unit": "^0.41.0",
41
52
  "@types/node": "^25.9.2",
42
53
  "@types/yargs": "^17.0.35",
43
54
  "typescript": "^6.0.3"