create-gesso-app 0.1.0 → 0.2.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # create-gesso-app
2
2
 
3
+ ## 0.2.0
4
+
5
+ ### Patch Changes
6
+
7
+ - 6c078dd: **The Electrobun template's README no longer explains a `vendor/` that
8
+ is not there.** The CLI appends the vendoring section itself, and only
9
+ under `--local`, so the template carrying its own hardcoded copy meant a
10
+ registry-scaffolded project shipped with a section about a directory it
11
+ does not have, and a `--local` one got the section twice. Removed from
12
+ the template; the two other lines that assumed every project was
13
+ vendored now say which route they are about.
14
+
3
15
  ## 0.1.0
4
16
 
5
17
  First public release.
@@ -12,20 +12,24 @@ import { basename, isAbsolute, join, resolve } from "node:path";
12
12
  * the app, hands it a worker constructor and mounts it, and that is the
13
13
  * whole of the main thread's job.
14
14
  *
15
- * The one thing this CLI does that a public scaffolder would not is
16
- * vendor its own dependencies. No `gesso-*` package is on a registry,
17
- * so a generated `package.json` that named a version would produce a
18
- * project that cannot install. Instead the CLI packs the packages a
19
- * template needs out of this workspace with `pnpm pack`, drops the
20
- * tarballs into the new project's `vendor/` directory and writes
21
- * `file:` specifiers at them. That is the same route
22
- * `scripts/check-install.ts` takes, and it is the route that exercises
23
- * each package's `publishConfig`, which is the only thing that rewrites
24
- * `exports` from `src/*.ts` to `dist`. This ends the day the packages
25
- * are published to a registry.
15
+ * By default the generated `package.json` names the published
16
+ * `gesso-*` packages by version range and an install goes to the
17
+ * registry, which is what a scaffold anywhere else does.
18
+ *
19
+ * `--local` is the other route, and the one this CLI keeps that a
20
+ * public scaffolder would not: it packs the packages a template needs
21
+ * out of this workspace with `pnpm pack`, drops the tarballs into the
22
+ * new project's `vendor/` directory and writes `file:` specifiers at
23
+ * them. That is the only way to scaffold against changes that are not
24
+ * released yet, it is the same route `scripts/check-install.ts` takes,
25
+ * and it is the route that exercises each package's `publishConfig`,
26
+ * which is the only thing that rewrites `exports` from `src/*.ts` to
27
+ * `dist`. `pnpm check:scaffold` runs this way, so the gate tests the
28
+ * working tree rather than the last release.
26
29
  *
27
30
  * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app
28
- * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --no-build
31
+ * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --local
32
+ * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --local --no-build
29
33
  * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --template electrobun
30
34
  *
31
35
  * The two templates differ in what runs beneath the same application.
@@ -280,9 +284,10 @@ function copyTemplate(from, options) {
280
284
  * `overrides` carries the same specifiers, because the packages declare
281
285
  * each other by version range: `gesso-framework` and
282
286
  * `gesso-components` do, and so does `gesso-electrobun`. Without it an
283
- * installer is free to go looking for `gesso-core@^0.1.0` on a registry
284
- * that has never heard of it, and whether it does so depends on what
285
- * else is in the tree.
287
+ * installer scaffolded with `--local` is free to resolve
288
+ * `gesso-core@^0.1.0` off the registry rather than from the tarball
289
+ * beside it, and whether it does so depends on what else is in the
290
+ * tree. Without `--local` there are no specifiers and no `overrides`.
286
291
  */
287
292
  function writeManifest(options, specifiers) {
288
293
  const path = join(options.target, "package.json");
@@ -301,27 +306,28 @@ function writeManifest(options, specifiers) {
301
306
  * Says the same thing to pnpm that `overrides` says to npm.
302
307
  *
303
308
  * npm was chosen because pnpm did not work, and the reason is still
304
- * exactly right: `pnpm pack` rewrites `workspace:^`
305
- * into `^0.1.0`, so the packed `gesso-framework` asks for
306
- * `gesso-core@^0.1.0` and pnpm 11 goes to a registry that has never
307
- * heard of it. What that record then rejected was shipping a
309
+ * exactly right: `pnpm pack` rewrites `workspace:^` into `^0.1.0`, so
310
+ * the packed `gesso-framework` asks for `gesso-core@^0.1.0` and pnpm 11
311
+ * resolves it off the registry rather than from the tarball beside it,
312
+ * which is a different copy of the package than the one this project
313
+ * was told to use. What that record then rejected was shipping a
308
314
  * `pnpm-workspace.yaml` in a project that is not a workspace.
309
315
  *
310
316
  * That trade has moved. The file is five lines, it is the only place
311
317
  * pnpm 11 reads `overrides` from, and the alternative is a scaffold
312
318
  * that fails for the package manager this repository itself uses. It
313
- * says what it is for and it is deleted along with `vendor/` the day
314
- * the packages are published.
319
+ * says what it is for, and it is written only under `--local`.
315
320
  */
316
321
  function writePnpmOverrides(options, specifiers) {
317
322
  const lines = [
318
323
  "# The tarballs in vendor/ again, for pnpm.",
319
324
  "#",
320
- "# Gesso is not published, so the packed gesso-* packages ask each",
321
- "# other for version ranges no registry can answer. npm reads the",
322
- "# `overrides` in package.json; pnpm 11 reads only this file. Both",
323
- "# go away when the packages are on a registry: delete vendor/, this",
324
- "# file and `overrides`, and put version ranges back.",
325
+ "# This project was scaffolded with --local, so the packed gesso-*",
326
+ "# packages ask each other for version ranges the registry would",
327
+ "# answer with a different copy. npm reads the `overrides` in",
328
+ "# package.json; pnpm 11 reads only this file. To move to the",
329
+ "# published packages: delete vendor/, this file and `overrides`,",
330
+ "# and put version ranges back.",
325
331
  "overrides:",
326
332
  ...[...specifiers].map(([pkg, specifier]) => ` '${pkg}': '${specifier}'`),
327
333
  ""
@@ -1 +1 @@
1
- {"version":3,"file":"create-gesso-app.mjs","names":[],"sources":["../bin/create-gesso-app.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `create-gesso-app`: scaffolds a Gesso application (the\n * last item).\n *\n * What it writes is the configuration this framework is for. The\n * application runs in a render worker; the page's own script creates\n * the app, hands it a worker constructor and mounts it, and that is the\n * whole of the main thread's job.\n *\n * The one thing this CLI does that a public scaffolder would not is\n * vendor its own dependencies. No `gesso-*` package is on a registry,\n * so a generated `package.json` that named a version would produce a\n * project that cannot install. Instead the CLI packs the packages a\n * template needs out of this workspace with `pnpm pack`, drops the\n * tarballs into the new project's `vendor/` directory and writes\n * `file:` specifiers at them. That is the same route\n * `scripts/check-install.ts` takes, and it is the route that exercises\n * each package's `publishConfig`, which is the only thing that rewrites\n * `exports` from `src/*.ts` to `dist`. This ends the day the packages\n * are published to a registry.\n *\n * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app\n * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --no-build\n * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --template electrobun\n *\n * The two templates differ in what runs beneath the same application.\n * `web` is a Vite project a browser loads. `electrobun` is a native\n * window whose application layer is a separate process, and it is set\n * up by Hutch rather than by npm: the Electrobun SDK is projected into\n * a project by `hutch electrobun prepare` rather than installed from a\n * registry, so the generated project depends on the toolchain being\n * present and says so.\n */\nimport { execFileSync } from 'node:child_process';\nimport {\n cpSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n renameSync,\n rmSync,\n statSync,\n writeFileSync\n} from 'node:fs';\nimport { basename, isAbsolute, join, resolve } from 'node:path';\n\n/** Where the packed tarballs land inside the generated project. */\nconst VENDOR_DIR = 'vendor';\n/** Template files whose names cannot be checked into this repository as-is. */\nconst RENAMED = new Map([['_gitignore', '.gitignore']]);\n\ninterface Template {\n /** Workspace packages to pack into the project, in dependency order. */\n readonly vendored: readonly string[];\n /** Files carrying `{{name}}`, relative to the project directory. */\n readonly substitute: readonly string[];\n /** What to tell the person once the project is written. */\n readonly next: (name: string, where: string, local: boolean) => string;\n}\n\n/**\n * The templates, and the two things that differ between them: what a\n * project depends on, and how a person starts it.\n *\n * `devtools` and `vite-plugin` joined the web template's first three\n * when the feedback loop was wired in by default:\n * the plugin is what writes the worker construction and the\n * hot-replacement wiring, and it loads the overlay from devtools the\n * first time the worker throws. Both are development dependencies of\n * the generated project and neither is reachable from a production\n * build.\n */\nconst TEMPLATES: Record<string, Template> = {\n web: {\n vendored: ['core', 'framework', 'components', 'devtools', 'vite-plugin'],\n substitute: ['index.html', 'README.md'],\n next: (name, where, local) =>\n [\n `\\nCreated ${name} in ${where}.\\n`,\n ` cd ${where}`,\n ' pnpm install # or npm install',\n ' pnpm dev\\n',\n ...(local\n ? [\n 'Gesso came from this workspace rather than the registry, packed into',\n 'vendor/. The packed packages ask each other for version ranges, so npm',\n 'is pointed at the tarballs by `overrides` in package.json and pnpm by',\n \"`overrides` in pnpm-workspace.yaml. The project's README says how to\",\n 'move to the published packages.\\n'\n ]\n : [])\n ].join('\\n')\n },\n electrobun: {\n vendored: ['core', 'framework', 'components', 'electrobun'],\n substitute: ['electrobun.config.ts', 'src/main/index.ts', 'src/view/index.html', 'README.md'],\n next: (name, where) => `\nCreated ${name} in ${where}.\n\n cd ${where}\n hutch install\n hutch run dev\n\nHutch rather than npm, and this is the part to read before running it.\nElectrobun 2.x is a toolchain a launcher downloads, not a package a\nregistry serves: \\`hutch electrobun prepare\\` projects the SDK into the\nproject's own .hutch/devkit, which is where vite.config.ts and\ntsconfig.json look for it, and every script in hutch.config.ts runs that\nfirst. \\`hutch install\\` runs npm underneath. If \\`hutch\\` is not on your\npath yet, the project's README says how to get it.\n\n\\`pnpm check:scaffold:electrobun\\` installs, typechecks and builds a\nproject like this one without opening it. The window itself is checked\nby running it; the README records the last time that was done.\n`\n }\n};\n\nconst packageRoot = join(import.meta.dirname, '..');\nconst workspaceRoot = join(packageRoot, '..', '..');\n\ninterface Options {\n readonly target: string;\n readonly name: string;\n readonly template: string;\n readonly force: boolean;\n readonly build: boolean;\n /**\n * Install the packages out of this workspace instead of the registry.\n *\n * What every scaffold did before the packages were published, kept\n * because it is the only way to scaffold a project against changes\n * that are not released yet. `pnpm check:scaffold` runs this way, so\n * the gate tests the working tree rather than the last release.\n */\n readonly local: boolean;\n}\n\nconst USAGE = `Usage: create-gesso-app <directory> [options]\n\nCreates a Gesso application in <directory>: a project whose interface is\nbuilt, laid out and painted in a render worker.\n\nOptions:\n --name <name> Package name for the new project. Defaults to the\n directory's own name.\n --template <name> \"web\" for a Vite project a browser loads, or\n \"electrobun\" for a native window with its state in a\n main process. Defaults to \"web\".\n --force Write into a directory that already has files in it.\n --local Install the Gesso packages from this workspace, packed\n into the project, instead of from the registry. Only\n works inside a Gesso checkout.\n --no-build With --local, pack without rebuilding first. Only safe\n when dist/ is already current.\n -h, --help Print this.\n`;\n\nfunction fail(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\nfunction parseArgs(argv: readonly string[]): Options {\n let target: string | undefined;\n let name: string | undefined;\n let template = 'web';\n let force = false;\n let build = true;\n let local = false;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n switch (arg) {\n case '-h':\n case '--help':\n console.log(USAGE);\n process.exit(0);\n break;\n case '--force':\n force = true;\n break;\n case '--local':\n local = true;\n break;\n case '--no-build':\n build = false;\n break;\n case '--name':\n name = argv[++i];\n break;\n case '--template':\n template = argv[++i] ?? '';\n break;\n default:\n if (arg.startsWith('-')) {\n fail(`Unknown option ${arg}.\\n\\n${USAGE}`);\n }\n if (target !== undefined) {\n fail(`Two directories were given, ${target} and ${arg}.\\n\\n${USAGE}`);\n }\n target = arg;\n }\n }\n\n if (target === undefined) {\n fail(`A directory to create is required.\\n\\n${USAGE}`);\n }\n if (name !== undefined && !/^[a-z0-9][a-z0-9._-]*$/.test(name)) {\n fail(`\"${name}\" is not a usable package name: use lowercase letters, digits, dots, dashes and underscores.`);\n }\n\n const absolute = isAbsolute(target) ? target : resolve(process.cwd(), target);\n return { target: absolute, name: name ?? basename(absolute), template, force, build, local };\n}\n\n/** Looks a template up, and lists the ones that exist when it is not one. */\nfunction templateOf(name: string): Template {\n const template = TEMPLATES[name];\n if (template === undefined || !existsSync(join(packageRoot, 'templates', name))) {\n fail(`There is no \"${name}\" template. Available: ${Object.keys(TEMPLATES).join(', ')}.`);\n }\n return template;\n}\n\nfunction prepareTarget(options: Options): void {\n if (!existsSync(options.target)) {\n mkdirSync(options.target, { recursive: true });\n return;\n }\n const existing = readdirSync(options.target);\n if (existing.length > 0 && !options.force) {\n fail(`${options.target} is not empty (${existing.length} entries). Pass --force to write into it anyway.`);\n }\n}\n\n/**\n * Whether a package's `dist` is older than anything in its `src`.\n *\n * A newest-mtime comparison rather than a build system: it is wrong\n * only in the direction of building something that did not need it,\n * and a missing `dist` always builds. `--no-build` skips the question\n * entirely for the case where the caller knows.\n */\nfunction needsBuild(pkg: string): boolean {\n const dir = join(workspaceRoot, 'packages', pkg);\n const dist = join(dir, 'dist');\n if (!existsSync(dist)) {\n return true;\n }\n return newestChange(join(dir, 'src')) > newestChange(dist);\n}\n\n/** The most recent modification time anywhere under a directory. */\nfunction newestChange(dir: string): number {\n let newest = 0;\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const path = join(dir, entry.name);\n newest = Math.max(newest, entry.isDirectory() ? newestChange(path) : statSync(path).mtimeMs);\n }\n return newest;\n}\n\n/**\n * Packs the workspace packages into the new project and returns the\n * `file:` specifier for each.\n *\n * The specifiers are relative to the project directory, so the whole\n * directory can be moved or copied and still install.\n */\nfunction vendorPackages(options: Options, template: Template): Map<string, string> {\n if (options.build) {\n // Only the packages that go into the project, and only the ones\n // whose `dist` is older than their `src`. Building the whole\n // workspace took the better part of a minute for a scaffold that\n // does not install two of the packages it built, and every second\n // of it was spent between a person typing a command and seeing\n // anything happen.\n const stale = template.vendored.filter(needsBuild);\n if (stale.length > 0) {\n console.log(`building ${stale.join(', ')}…`);\n const filters = stale.flatMap(pkg => ['--filter', `./packages/${pkg}`]);\n execFileSync('pnpm', [...filters, 'build'], { cwd: workspaceRoot, stdio: 'ignore' });\n }\n }\n\n const into = join(options.target, VENDOR_DIR);\n rmSync(into, { recursive: true, force: true });\n mkdirSync(into, { recursive: true });\n\n console.log('packing them into the new project…');\n const specifiers = new Map<string, string>();\n for (const pkg of template.vendored) {\n const before = new Set(readdirSync(into));\n execFileSync('pnpm', ['pack', '--pack-destination', into], {\n cwd: join(workspaceRoot, 'packages', pkg),\n stdio: 'ignore'\n });\n const created = readdirSync(into).filter(entry => !before.has(entry) && entry.endsWith('.tgz'));\n if (created.length !== 1) {\n fail(`pnpm pack in packages/${pkg} produced ${created.length} tarballs, expected 1.`);\n }\n specifiers.set(`gesso-${pkg}`, `file:${VENDOR_DIR}/${created[0]}`);\n }\n return specifiers;\n}\n\n/** Copies the template, renaming the files that had to be disguised. */\nfunction copyTemplate(from: string, options: Options): void {\n // `force` here is not the option of the same name: whether writing\n // into an occupied directory is allowed was settled by `prepareTarget`,\n // and a template file always wins over whatever it lands on.\n cpSync(from, options.target, { recursive: true, force: true });\n for (const [disguised, real] of RENAMED) {\n const path = join(options.target, disguised);\n if (existsSync(path)) {\n renameSync(path, join(options.target, real));\n }\n }\n}\n\n/**\n * Writes the project's name and its dependency specifiers.\n *\n * `overrides` carries the same specifiers, because the packages declare\n * each other by version range: `gesso-framework` and\n * `gesso-components` do, and so does `gesso-electrobun`. Without it an\n * installer is free to go looking for `gesso-core@^0.1.0` on a registry\n * that has never heard of it, and whether it does so depends on what\n * else is in the tree.\n */\nfunction writeManifest(options: Options, specifiers: ReadonlyMap<string, string>): void {\n const path = join(options.target, 'package.json');\n const manifest = JSON.parse(readFileSync(path, 'utf8')) as {\n name: string;\n dependencies: Record<string, string>;\n devDependencies?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n manifest.name = options.name;\n for (const [pkg, specifier] of specifiers) {\n // The plugin and the overlay are development dependencies and the\n // runtime packages are not, so the specifier goes wherever the\n // template already declared the package.\n const where =\n manifest.dependencies[pkg] !== undefined\n ? manifest.dependencies\n : manifest.devDependencies?.[pkg] !== undefined\n ? manifest.devDependencies\n : undefined;\n if (where === undefined) {\n fail(`The ${options.template} template does not depend on ${pkg}, so there is nowhere to vendor it.`);\n }\n where[pkg] = specifier;\n }\n if (specifiers.size > 0) {\n manifest.overrides = Object.fromEntries(specifiers);\n } else {\n delete manifest.overrides;\n }\n writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\\n`);\n}\n\n/**\n * Says the same thing to pnpm that `overrides` says to npm.\n *\n * npm was chosen because pnpm did not work, and the reason is still\n * exactly right: `pnpm pack` rewrites `workspace:^`\n * into `^0.1.0`, so the packed `gesso-framework` asks for\n * `gesso-core@^0.1.0` and pnpm 11 goes to a registry that has never\n * heard of it. What that record then rejected was shipping a\n * `pnpm-workspace.yaml` in a project that is not a workspace.\n *\n * That trade has moved. The file is five lines, it is the only place\n * pnpm 11 reads `overrides` from, and the alternative is a scaffold\n * that fails for the package manager this repository itself uses. It\n * says what it is for and it is deleted along with `vendor/` the day\n * the packages are published.\n */\nfunction writePnpmOverrides(options: Options, specifiers: ReadonlyMap<string, string>): void {\n const lines = [\n '# The tarballs in vendor/ again, for pnpm.',\n '#',\n '# Gesso is not published, so the packed gesso-* packages ask each',\n '# other for version ranges no registry can answer. npm reads the',\n '# `overrides` in package.json; pnpm 11 reads only this file. Both',\n '# go away when the packages are on a registry: delete vendor/, this',\n '# file and `overrides`, and put version ranges back.',\n 'overrides:',\n ...[...specifiers].map(([pkg, specifier]) => ` '${pkg}': '${specifier}'`),\n ''\n ];\n writeFileSync(join(options.target, 'pnpm-workspace.yaml'), lines.join('\\n'));\n}\n\n/**\n * Substitutes the template's placeholders in the files that carry them.\n *\n * The list is the template's rather than a walk of the tree, so that a\n * placeholder added to a file nobody listed fails visibly in the\n * generated project instead of being quietly left as `{{name}}`\n * somewhere it is never read.\n */\nfunction substitute(options: Options, template: Template): void {\n for (const relative of template.substitute) {\n const path = join(options.target, relative);\n if (!existsSync(path)) {\n fail(`The ${options.template} template says ${relative} carries {{name}}, and it was not written.`);\n }\n writeFileSync(path, readFileSync(path, 'utf8').replaceAll('{{name}}', options.name));\n }\n}\n\n/**\n * Appends the `vendor/` explanation to the generated README.\n *\n * It lives here rather than in the template because a project made\n * without `--local` has no `vendor/`, and a README explaining a\n * directory that is not there is worse than one that says nothing.\n */\nfunction explainVendoring(options: Options): void {\n const path = join(options.target, 'README.md');\n if (!existsSync(path)) {\n return;\n }\n const section = [\n '',\n '## Why `vendor/` exists, and how to remove it',\n '',\n 'This project was scaffolded with `--local`, so it installs Gesso from',\n 'a checkout rather than from the registry: the packages were packed',\n 'into `vendor/` and the manifest points at the tarballs.',\n '',\n 'The packed packages declare each other by version range, so without',\n 'help a package manager is free to go looking for `gesso-core@^0.1.0`',\n 'on the registry and get a different copy than the one beside it.',\n '`overrides` in `package.json` is what tells npm; `overrides` in',\n '`pnpm-workspace.yaml` is what tells pnpm, which reads it nowhere else.',\n '',\n 'To pick up a further change, run `create-gesso-app --local` over this',\n 'directory again with `--force`.',\n '',\n 'To move to the published packages: delete `vendor/`, delete',\n '`pnpm-workspace.yaml`, delete `overrides`, and put version ranges back',\n 'in `dependencies`.',\n ''\n ].join('\\n');\n writeFileSync(path, readFileSync(path, 'utf8').trimEnd() + '\\n' + section);\n}\n\nfunction main(): void {\n const options = parseArgs(process.argv.slice(2));\n if (options.local && !existsSync(join(workspaceRoot, 'packages', 'core', 'package.json'))) {\n fail(\n '--local packs the packages out of the Gesso workspace, and this is not one.\\n' +\n 'Run it from inside a checkout, or drop --local to install from the registry.'\n );\n }\n\n const template = templateOf(options.template);\n prepareTarget(options);\n copyTemplate(join(packageRoot, 'templates', options.template), options);\n // Without --local the template's own version ranges are the answer,\n // and the manifest needs nothing but its name.\n const specifiers = options.local ? vendorPackages(options, template) : new Map<string, string>();\n writeManifest(options, specifiers);\n if (options.local) {\n writePnpmOverrides(options, specifiers);\n explainVendoring(options);\n }\n substitute(options, template);\n\n const where = options.target.startsWith(process.cwd())\n ? options.target.slice(process.cwd().length + 1)\n : options.target;\n console.log(template.next(options.name, where, options.local));\n}\n\nmain();\n"],"mappings":"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"}
1
+ {"version":3,"file":"create-gesso-app.mjs","names":[],"sources":["../bin/create-gesso-app.ts"],"sourcesContent":["#!/usr/bin/env node\n/**\n * `create-gesso-app`: scaffolds a Gesso application (the\n * last item).\n *\n * What it writes is the configuration this framework is for. The\n * application runs in a render worker; the page's own script creates\n * the app, hands it a worker constructor and mounts it, and that is the\n * whole of the main thread's job.\n *\n * By default the generated `package.json` names the published\n * `gesso-*` packages by version range and an install goes to the\n * registry, which is what a scaffold anywhere else does.\n *\n * `--local` is the other route, and the one this CLI keeps that a\n * public scaffolder would not: it packs the packages a template needs\n * out of this workspace with `pnpm pack`, drops the tarballs into the\n * new project's `vendor/` directory and writes `file:` specifiers at\n * them. That is the only way to scaffold against changes that are not\n * released yet, it is the same route `scripts/check-install.ts` takes,\n * and it is the route that exercises each package's `publishConfig`,\n * which is the only thing that rewrites `exports` from `src/*.ts` to\n * `dist`. `pnpm check:scaffold` runs this way, so the gate tests the\n * working tree rather than the last release.\n *\n * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app\n * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --local\n * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --local --no-build\n * node packages/create-gesso-app/bin/create-gesso-app.ts ../my-app --template electrobun\n *\n * The two templates differ in what runs beneath the same application.\n * `web` is a Vite project a browser loads. `electrobun` is a native\n * window whose application layer is a separate process, and it is set\n * up by Hutch rather than by npm: the Electrobun SDK is projected into\n * a project by `hutch electrobun prepare` rather than installed from a\n * registry, so the generated project depends on the toolchain being\n * present and says so.\n */\nimport { execFileSync } from 'node:child_process';\nimport {\n cpSync,\n existsSync,\n mkdirSync,\n readFileSync,\n readdirSync,\n renameSync,\n rmSync,\n statSync,\n writeFileSync\n} from 'node:fs';\nimport { basename, isAbsolute, join, resolve } from 'node:path';\n\n/** Where the packed tarballs land inside the generated project. */\nconst VENDOR_DIR = 'vendor';\n/** Template files whose names cannot be checked into this repository as-is. */\nconst RENAMED = new Map([['_gitignore', '.gitignore']]);\n\ninterface Template {\n /** Workspace packages to pack into the project, in dependency order. */\n readonly vendored: readonly string[];\n /** Files carrying `{{name}}`, relative to the project directory. */\n readonly substitute: readonly string[];\n /** What to tell the person once the project is written. */\n readonly next: (name: string, where: string, local: boolean) => string;\n}\n\n/**\n * The templates, and the two things that differ between them: what a\n * project depends on, and how a person starts it.\n *\n * `devtools` and `vite-plugin` joined the web template's first three\n * when the feedback loop was wired in by default:\n * the plugin is what writes the worker construction and the\n * hot-replacement wiring, and it loads the overlay from devtools the\n * first time the worker throws. Both are development dependencies of\n * the generated project and neither is reachable from a production\n * build.\n */\nconst TEMPLATES: Record<string, Template> = {\n web: {\n vendored: ['core', 'framework', 'components', 'devtools', 'vite-plugin'],\n substitute: ['index.html', 'README.md'],\n next: (name, where, local) =>\n [\n `\\nCreated ${name} in ${where}.\\n`,\n ` cd ${where}`,\n ' pnpm install # or npm install',\n ' pnpm dev\\n',\n ...(local\n ? [\n 'Gesso came from this workspace rather than the registry, packed into',\n 'vendor/. The packed packages ask each other for version ranges, so npm',\n 'is pointed at the tarballs by `overrides` in package.json and pnpm by',\n \"`overrides` in pnpm-workspace.yaml. The project's README says how to\",\n 'move to the published packages.\\n'\n ]\n : [])\n ].join('\\n')\n },\n electrobun: {\n vendored: ['core', 'framework', 'components', 'electrobun'],\n substitute: ['electrobun.config.ts', 'src/main/index.ts', 'src/view/index.html', 'README.md'],\n next: (name, where) => `\nCreated ${name} in ${where}.\n\n cd ${where}\n hutch install\n hutch run dev\n\nHutch rather than npm, and this is the part to read before running it.\nElectrobun 2.x is a toolchain a launcher downloads, not a package a\nregistry serves: \\`hutch electrobun prepare\\` projects the SDK into the\nproject's own .hutch/devkit, which is where vite.config.ts and\ntsconfig.json look for it, and every script in hutch.config.ts runs that\nfirst. \\`hutch install\\` runs npm underneath. If \\`hutch\\` is not on your\npath yet, the project's README says how to get it.\n\n\\`pnpm check:scaffold:electrobun\\` installs, typechecks and builds a\nproject like this one without opening it. The window itself is checked\nby running it; the README records the last time that was done.\n`\n }\n};\n\nconst packageRoot = join(import.meta.dirname, '..');\nconst workspaceRoot = join(packageRoot, '..', '..');\n\ninterface Options {\n readonly target: string;\n readonly name: string;\n readonly template: string;\n readonly force: boolean;\n readonly build: boolean;\n /**\n * Install the packages out of this workspace instead of the registry.\n *\n * What every scaffold did before the packages were published, kept\n * because it is the only way to scaffold a project against changes\n * that are not released yet. `pnpm check:scaffold` runs this way, so\n * the gate tests the working tree rather than the last release.\n */\n readonly local: boolean;\n}\n\nconst USAGE = `Usage: create-gesso-app <directory> [options]\n\nCreates a Gesso application in <directory>: a project whose interface is\nbuilt, laid out and painted in a render worker.\n\nOptions:\n --name <name> Package name for the new project. Defaults to the\n directory's own name.\n --template <name> \"web\" for a Vite project a browser loads, or\n \"electrobun\" for a native window with its state in a\n main process. Defaults to \"web\".\n --force Write into a directory that already has files in it.\n --local Install the Gesso packages from this workspace, packed\n into the project, instead of from the registry. Only\n works inside a Gesso checkout.\n --no-build With --local, pack without rebuilding first. Only safe\n when dist/ is already current.\n -h, --help Print this.\n`;\n\nfunction fail(message: string): never {\n console.error(message);\n process.exit(1);\n}\n\nfunction parseArgs(argv: readonly string[]): Options {\n let target: string | undefined;\n let name: string | undefined;\n let template = 'web';\n let force = false;\n let build = true;\n let local = false;\n\n for (let i = 0; i < argv.length; i++) {\n const arg = argv[i];\n switch (arg) {\n case '-h':\n case '--help':\n console.log(USAGE);\n process.exit(0);\n break;\n case '--force':\n force = true;\n break;\n case '--local':\n local = true;\n break;\n case '--no-build':\n build = false;\n break;\n case '--name':\n name = argv[++i];\n break;\n case '--template':\n template = argv[++i] ?? '';\n break;\n default:\n if (arg.startsWith('-')) {\n fail(`Unknown option ${arg}.\\n\\n${USAGE}`);\n }\n if (target !== undefined) {\n fail(`Two directories were given, ${target} and ${arg}.\\n\\n${USAGE}`);\n }\n target = arg;\n }\n }\n\n if (target === undefined) {\n fail(`A directory to create is required.\\n\\n${USAGE}`);\n }\n if (name !== undefined && !/^[a-z0-9][a-z0-9._-]*$/.test(name)) {\n fail(`\"${name}\" is not a usable package name: use lowercase letters, digits, dots, dashes and underscores.`);\n }\n\n const absolute = isAbsolute(target) ? target : resolve(process.cwd(), target);\n return { target: absolute, name: name ?? basename(absolute), template, force, build, local };\n}\n\n/** Looks a template up, and lists the ones that exist when it is not one. */\nfunction templateOf(name: string): Template {\n const template = TEMPLATES[name];\n if (template === undefined || !existsSync(join(packageRoot, 'templates', name))) {\n fail(`There is no \"${name}\" template. Available: ${Object.keys(TEMPLATES).join(', ')}.`);\n }\n return template;\n}\n\nfunction prepareTarget(options: Options): void {\n if (!existsSync(options.target)) {\n mkdirSync(options.target, { recursive: true });\n return;\n }\n const existing = readdirSync(options.target);\n if (existing.length > 0 && !options.force) {\n fail(`${options.target} is not empty (${existing.length} entries). Pass --force to write into it anyway.`);\n }\n}\n\n/**\n * Whether a package's `dist` is older than anything in its `src`.\n *\n * A newest-mtime comparison rather than a build system: it is wrong\n * only in the direction of building something that did not need it,\n * and a missing `dist` always builds. `--no-build` skips the question\n * entirely for the case where the caller knows.\n */\nfunction needsBuild(pkg: string): boolean {\n const dir = join(workspaceRoot, 'packages', pkg);\n const dist = join(dir, 'dist');\n if (!existsSync(dist)) {\n return true;\n }\n return newestChange(join(dir, 'src')) > newestChange(dist);\n}\n\n/** The most recent modification time anywhere under a directory. */\nfunction newestChange(dir: string): number {\n let newest = 0;\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n const path = join(dir, entry.name);\n newest = Math.max(newest, entry.isDirectory() ? newestChange(path) : statSync(path).mtimeMs);\n }\n return newest;\n}\n\n/**\n * Packs the workspace packages into the new project and returns the\n * `file:` specifier for each.\n *\n * The specifiers are relative to the project directory, so the whole\n * directory can be moved or copied and still install.\n */\nfunction vendorPackages(options: Options, template: Template): Map<string, string> {\n if (options.build) {\n // Only the packages that go into the project, and only the ones\n // whose `dist` is older than their `src`. Building the whole\n // workspace took the better part of a minute for a scaffold that\n // does not install two of the packages it built, and every second\n // of it was spent between a person typing a command and seeing\n // anything happen.\n const stale = template.vendored.filter(needsBuild);\n if (stale.length > 0) {\n console.log(`building ${stale.join(', ')}…`);\n const filters = stale.flatMap(pkg => ['--filter', `./packages/${pkg}`]);\n execFileSync('pnpm', [...filters, 'build'], { cwd: workspaceRoot, stdio: 'ignore' });\n }\n }\n\n const into = join(options.target, VENDOR_DIR);\n rmSync(into, { recursive: true, force: true });\n mkdirSync(into, { recursive: true });\n\n console.log('packing them into the new project…');\n const specifiers = new Map<string, string>();\n for (const pkg of template.vendored) {\n const before = new Set(readdirSync(into));\n execFileSync('pnpm', ['pack', '--pack-destination', into], {\n cwd: join(workspaceRoot, 'packages', pkg),\n stdio: 'ignore'\n });\n const created = readdirSync(into).filter(entry => !before.has(entry) && entry.endsWith('.tgz'));\n if (created.length !== 1) {\n fail(`pnpm pack in packages/${pkg} produced ${created.length} tarballs, expected 1.`);\n }\n specifiers.set(`gesso-${pkg}`, `file:${VENDOR_DIR}/${created[0]}`);\n }\n return specifiers;\n}\n\n/** Copies the template, renaming the files that had to be disguised. */\nfunction copyTemplate(from: string, options: Options): void {\n // `force` here is not the option of the same name: whether writing\n // into an occupied directory is allowed was settled by `prepareTarget`,\n // and a template file always wins over whatever it lands on.\n cpSync(from, options.target, { recursive: true, force: true });\n for (const [disguised, real] of RENAMED) {\n const path = join(options.target, disguised);\n if (existsSync(path)) {\n renameSync(path, join(options.target, real));\n }\n }\n}\n\n/**\n * Writes the project's name and its dependency specifiers.\n *\n * `overrides` carries the same specifiers, because the packages declare\n * each other by version range: `gesso-framework` and\n * `gesso-components` do, and so does `gesso-electrobun`. Without it an\n * installer scaffolded with `--local` is free to resolve\n * `gesso-core@^0.1.0` off the registry rather than from the tarball\n * beside it, and whether it does so depends on what else is in the\n * tree. Without `--local` there are no specifiers and no `overrides`.\n */\nfunction writeManifest(options: Options, specifiers: ReadonlyMap<string, string>): void {\n const path = join(options.target, 'package.json');\n const manifest = JSON.parse(readFileSync(path, 'utf8')) as {\n name: string;\n dependencies: Record<string, string>;\n devDependencies?: Record<string, string>;\n overrides?: Record<string, string>;\n };\n manifest.name = options.name;\n for (const [pkg, specifier] of specifiers) {\n // The plugin and the overlay are development dependencies and the\n // runtime packages are not, so the specifier goes wherever the\n // template already declared the package.\n const where =\n manifest.dependencies[pkg] !== undefined\n ? manifest.dependencies\n : manifest.devDependencies?.[pkg] !== undefined\n ? manifest.devDependencies\n : undefined;\n if (where === undefined) {\n fail(`The ${options.template} template does not depend on ${pkg}, so there is nowhere to vendor it.`);\n }\n where[pkg] = specifier;\n }\n if (specifiers.size > 0) {\n manifest.overrides = Object.fromEntries(specifiers);\n } else {\n delete manifest.overrides;\n }\n writeFileSync(path, `${JSON.stringify(manifest, null, 2)}\\n`);\n}\n\n/**\n * Says the same thing to pnpm that `overrides` says to npm.\n *\n * npm was chosen because pnpm did not work, and the reason is still\n * exactly right: `pnpm pack` rewrites `workspace:^` into `^0.1.0`, so\n * the packed `gesso-framework` asks for `gesso-core@^0.1.0` and pnpm 11\n * resolves it off the registry rather than from the tarball beside it,\n * which is a different copy of the package than the one this project\n * was told to use. What that record then rejected was shipping a\n * `pnpm-workspace.yaml` in a project that is not a workspace.\n *\n * That trade has moved. The file is five lines, it is the only place\n * pnpm 11 reads `overrides` from, and the alternative is a scaffold\n * that fails for the package manager this repository itself uses. It\n * says what it is for, and it is written only under `--local`.\n */\nfunction writePnpmOverrides(options: Options, specifiers: ReadonlyMap<string, string>): void {\n const lines = [\n '# The tarballs in vendor/ again, for pnpm.',\n '#',\n '# This project was scaffolded with --local, so the packed gesso-*',\n '# packages ask each other for version ranges the registry would',\n '# answer with a different copy. npm reads the `overrides` in',\n '# package.json; pnpm 11 reads only this file. To move to the',\n '# published packages: delete vendor/, this file and `overrides`,',\n '# and put version ranges back.',\n 'overrides:',\n ...[...specifiers].map(([pkg, specifier]) => ` '${pkg}': '${specifier}'`),\n ''\n ];\n writeFileSync(join(options.target, 'pnpm-workspace.yaml'), lines.join('\\n'));\n}\n\n/**\n * Substitutes the template's placeholders in the files that carry them.\n *\n * The list is the template's rather than a walk of the tree, so that a\n * placeholder added to a file nobody listed fails visibly in the\n * generated project instead of being quietly left as `{{name}}`\n * somewhere it is never read.\n */\nfunction substitute(options: Options, template: Template): void {\n for (const relative of template.substitute) {\n const path = join(options.target, relative);\n if (!existsSync(path)) {\n fail(`The ${options.template} template says ${relative} carries {{name}}, and it was not written.`);\n }\n writeFileSync(path, readFileSync(path, 'utf8').replaceAll('{{name}}', options.name));\n }\n}\n\n/**\n * Appends the `vendor/` explanation to the generated README.\n *\n * It lives here rather than in the template because a project made\n * without `--local` has no `vendor/`, and a README explaining a\n * directory that is not there is worse than one that says nothing.\n */\nfunction explainVendoring(options: Options): void {\n const path = join(options.target, 'README.md');\n if (!existsSync(path)) {\n return;\n }\n const section = [\n '',\n '## Why `vendor/` exists, and how to remove it',\n '',\n 'This project was scaffolded with `--local`, so it installs Gesso from',\n 'a checkout rather than from the registry: the packages were packed',\n 'into `vendor/` and the manifest points at the tarballs.',\n '',\n 'The packed packages declare each other by version range, so without',\n 'help a package manager is free to go looking for `gesso-core@^0.1.0`',\n 'on the registry and get a different copy than the one beside it.',\n '`overrides` in `package.json` is what tells npm; `overrides` in',\n '`pnpm-workspace.yaml` is what tells pnpm, which reads it nowhere else.',\n '',\n 'To pick up a further change, run `create-gesso-app --local` over this',\n 'directory again with `--force`.',\n '',\n 'To move to the published packages: delete `vendor/`, delete',\n '`pnpm-workspace.yaml`, delete `overrides`, and put version ranges back',\n 'in `dependencies`.',\n ''\n ].join('\\n');\n writeFileSync(path, readFileSync(path, 'utf8').trimEnd() + '\\n' + section);\n}\n\nfunction main(): void {\n const options = parseArgs(process.argv.slice(2));\n if (options.local && !existsSync(join(workspaceRoot, 'packages', 'core', 'package.json'))) {\n fail(\n '--local packs the packages out of the Gesso workspace, and this is not one.\\n' +\n 'Run it from inside a checkout, or drop --local to install from the registry.'\n );\n }\n\n const template = templateOf(options.template);\n prepareTarget(options);\n copyTemplate(join(packageRoot, 'templates', options.template), options);\n // Without --local the template's own version ranges are the answer,\n // and the manifest needs nothing but its name.\n const specifiers = options.local ? vendorPackages(options, template) : new Map<string, string>();\n writeManifest(options, specifiers);\n if (options.local) {\n writePnpmOverrides(options, specifiers);\n explainVendoring(options);\n }\n substitute(options, template);\n\n const where = options.target.startsWith(process.cwd())\n ? options.target.slice(process.cwd().length + 1)\n : options.target;\n console.log(template.next(options.name, where, options.local));\n}\n\nmain();\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-gesso-app",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Scaffolds a Gesso application: a Vite project whose interface runs in a render worker.",
5
5
  "license": "MIT",
6
6
  "author": "Kevin Baker",
@@ -40,16 +40,17 @@ lands under that directory instead.
40
40
  ## Running it
41
41
 
42
42
  ```sh
43
- hutch install # dependencies, including the vendored Gesso packages
43
+ hutch install # dependencies
44
44
  hutch run dev # prepare, build the window's assets, open the window
45
45
  ```
46
46
 
47
47
  `hutch install` runs npm underneath, because `hutch.config.ts` says
48
- `packageManager: 'npm'`: Hutch's own resolver reads a relative `file:`
49
- override against the package that asked for it rather than against this
50
- directory, and stops with `FileNotFound` on the second vendored package.
51
- npm reads `overrides` from here, which is what the tarballs in `vendor/`
52
- need. You will see a `package-lock.json`, and no `hutch.lock`.
48
+ `packageManager: 'npm'`. That matters if this project was scaffolded
49
+ with `--local`: Hutch's own resolver reads a relative `file:` override
50
+ against the package that asked for it rather than against this
51
+ directory, and stops with `FileNotFound` on the second packed package,
52
+ while npm reads `overrides` from here, which is what tarballs in
53
+ `vendor/` need. You will see a `package-lock.json`, and no `hutch.lock`.
53
54
 
54
55
  `hutch run build` makes a distributable build under `build/stable-*`,
55
56
  and `hutch run typecheck` checks the types without building. Every
@@ -160,41 +161,15 @@ Electrobun bundle it, which is the ordinary arrangement. Gesso's own
160
161
  Electrobun applications do not do this: they pre-bundle their main
161
162
  process with esbuild first, because they import the framework out of a
162
163
  workspace rather than out of `node_modules`, and a bundler cannot
163
- resolve a package that was never installed. Here the packages are
164
- installed, in `vendor/`, so the ordinary arrangement is the right one.
164
+ resolve a package that was never installed. Here they are installed
165
+ into `node_modules` like anything else, so the ordinary arrangement is
166
+ the right one.
165
167
 
166
168
  If a future toolchain ever fails to resolve `gesso-electrobun` from the
167
169
  main process, that is the escape hatch: bundle `src/main/index.ts` to a
168
170
  plain `.js` file with `electrobun/main` left external, and point the
169
171
  `cottontail.entrypoint` at the bundle instead.
170
172
 
171
- ## Why `vendor/` exists, and how to remove it
172
-
173
- Gesso is not published to a registry yet. A `package.json` naming a
174
- version of `gesso-core` would produce a project that cannot install, so
175
- `create-gesso-app` packed the packages out of its own workspace, put the
176
- tarballs in `vendor/` and pointed `dependencies` and `overrides` at
177
- them:
178
-
179
- ```json
180
- "gesso-core": "file:vendor/gesso-core-0.1.0.tgz"
181
- ```
182
-
183
- `overrides` is there because the packages declare each other by version
184
- range, and without it an installer is free to go looking for
185
- `gesso-core@^0.1.0` on a registry that has never heard of it.
186
-
187
- To pick up a change made in the Gesso workspace, run `create-gesso-app`
188
- again over this directory with `--force`, or repack by hand:
189
-
190
- ```sh
191
- cd path/to/gesso && pnpm --filter './packages/*' build
192
- cd packages/core && pnpm pack --pack-destination path/to/this/project/vendor
193
- ```
194
-
195
- When the packages are published this all goes away: delete `vendor/`,
196
- delete `overrides`, and put version ranges back in `dependencies`.
197
-
198
173
  ## Where to go next
199
174
 
200
175
  - `src/render/App.tsx` is commented with what each part of it is doing.