create-grist-widget 0.3.23 → 0.3.25
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 +4 -2
- package/bin/create-grist-widget.mjs +47 -26
- package/package.json +1 -1
- package/src/package-manager.mjs +19 -0
- package/template/README.md +5 -3
- package/template/package.json +1 -1
- package/template/tsconfig.app.json +6 -21
- package/template/tsconfig.node.json +7 -20
package/README.md
CHANGED
|
@@ -31,8 +31,10 @@ prompt that asks for both.
|
|
|
31
31
|
| `list [--json]` | Print the available starting examples |
|
|
32
32
|
|
|
33
33
|
Common flags for `create`/`init`: `--yes` skips interactive prompts and uses
|
|
34
|
-
flags/defaults only; `--no-install` skips the automatic dependency install
|
|
35
|
-
|
|
34
|
+
flags/defaults only; `--no-install` skips the automatic dependency install
|
|
35
|
+
(which uses pnpm, whichever package manager runs the CLI: the widget's
|
|
36
|
+
workflows only read `pnpm-lock.yaml`); `--pm <name>` installs with another
|
|
37
|
+
package manager instead.
|
|
36
38
|
`init` additionally takes `--name <name>` (default: the current directory's
|
|
37
39
|
name) and `--force` (overwrite files that already exist and differ from the
|
|
38
40
|
template — see below).
|
|
@@ -26,12 +26,13 @@ import {
|
|
|
26
26
|
} from "node:fs"
|
|
27
27
|
import path from "node:path"
|
|
28
28
|
import { fileURLToPath } from "node:url"
|
|
29
|
-
import { execFileSync } from "node:child_process"
|
|
29
|
+
import { execFileSync, execSync } from "node:child_process"
|
|
30
30
|
import * as clack from "@clack/prompts"
|
|
31
31
|
|
|
32
32
|
import { createComposer } from "../vendor/compose-example.mjs"
|
|
33
33
|
import { checkGitGate } from "../src/git-gate.mjs"
|
|
34
34
|
import { addedAgeExceptions } from "../src/age-exceptions.mjs"
|
|
35
|
+
import { installPlan } from "../src/package-manager.mjs"
|
|
35
36
|
|
|
36
37
|
const PKG_ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
37
38
|
const TEMPLATE_DIR = path.join(PKG_ROOT, "template")
|
|
@@ -62,14 +63,6 @@ function titleCase(name) {
|
|
|
62
63
|
.join(" ")
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
function detectPackageManager() {
|
|
66
|
-
// e.g. "pnpm/9.1.0 npm-node/..." -> "pnpm". Set by every package manager's
|
|
67
|
-
// own `npm create`-equivalent invocation. Falls back to npm, matching
|
|
68
|
-
// what a bare `node bin/create-grist-widget.mjs` (no wrapping pm) should do.
|
|
69
|
-
const ua = process.env.npm_config_user_agent || ""
|
|
70
|
-
return ua.split("/")[0] || "npm"
|
|
71
|
-
}
|
|
72
|
-
|
|
73
66
|
function parseArgs(argv) {
|
|
74
67
|
const out = { _: [] }
|
|
75
68
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -102,8 +95,8 @@ Options (create / init):
|
|
|
102
95
|
(see "list" for ids)
|
|
103
96
|
--yes Skip interactive prompts, use defaults/flags only
|
|
104
97
|
--no-install Don't run the package manager install automatically
|
|
105
|
-
--pm <name>
|
|
106
|
-
|
|
98
|
+
--pm <name> Install with this package manager instead of pnpm (the
|
|
99
|
+
widget's workflows use pnpm and only read its lockfile)
|
|
107
100
|
--name <name> (init only) Widget name — default: the current directory's name
|
|
108
101
|
--force (init only) Overwrite files that already exist and differ
|
|
109
102
|
from the template
|
|
@@ -169,33 +162,59 @@ function gitInitCommit(target) {
|
|
|
169
162
|
}
|
|
170
163
|
}
|
|
171
164
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
165
|
+
// Package managers are .cmd shims on Windows, which only a shell starts.
|
|
166
|
+
function runCommand(cmd, args, options) {
|
|
167
|
+
if (process.platform === "win32") return execSync([cmd, ...args].join(" "), options)
|
|
168
|
+
return execFileSync(cmd, args, options)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function hasCommand(cmd) {
|
|
175
172
|
try {
|
|
176
|
-
|
|
173
|
+
runCommand(cmd, ["--version"], { stdio: "ignore" })
|
|
177
174
|
return true
|
|
178
175
|
} catch {
|
|
179
|
-
console.warn(`\nCould not run "${manager} install" automatically — run it yourself.`)
|
|
180
176
|
return false
|
|
181
177
|
}
|
|
182
178
|
}
|
|
183
179
|
|
|
180
|
+
/** Returns whether the install ran, and why not when it was skipped. */
|
|
181
|
+
function maybeInstall(target, { noInstall, pm }) {
|
|
182
|
+
const plan = installPlan({ noInstall, pm, hasPnpm: () => hasCommand("pnpm") })
|
|
183
|
+
if (plan.skip) return { installed: false, skipped: plan.skip }
|
|
184
|
+
try {
|
|
185
|
+
runCommand(plan.manager, ["install"], { cwd: target, stdio: "inherit" })
|
|
186
|
+
return { installed: true }
|
|
187
|
+
} catch {
|
|
188
|
+
console.warn(`\nCould not run "${plan.manager} install" automatically — run it yourself.`)
|
|
189
|
+
return { installed: false }
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
184
193
|
/**
|
|
185
|
-
* Install (unless --no-install), returning which
|
|
186
|
-
* entries pnpm added to pnpm-workspace.yaml along
|
|
187
|
-
* first commit, so the lockfile -- and those
|
|
188
|
-
* later install rejects that lockfile -- are
|
|
194
|
+
* Install (unless --no-install, or pnpm is missing), returning which
|
|
195
|
+
* `minimumReleaseAgeExclude` entries pnpm added to pnpm-workspace.yaml along
|
|
196
|
+
* the way. Runs before the first commit, so the lockfile -- and those
|
|
197
|
+
* entries, without which every later install rejects that lockfile -- are
|
|
198
|
+
* part of it.
|
|
189
199
|
*/
|
|
190
200
|
function installBeforeFirstCommit(target, args) {
|
|
191
201
|
const workspaceFile = path.join(target, "pnpm-workspace.yaml")
|
|
192
202
|
const read = () => (existsSync(workspaceFile) ? readFileSync(workspaceFile, "utf8") : "")
|
|
193
203
|
const before = read()
|
|
194
|
-
const installed = maybeInstall(target, {
|
|
204
|
+
const { installed, skipped } = maybeInstall(target, {
|
|
195
205
|
noInstall: Boolean(args["no-install"]),
|
|
196
206
|
pm: typeof args.pm === "string" ? args.pm : null,
|
|
197
207
|
})
|
|
198
|
-
return { installed, ageExceptions: installed ? addedAgeExceptions(before, read()) : [] }
|
|
208
|
+
return { installed, skipped, ageExceptions: installed ? addedAgeExceptions(before, read()) : [] }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function printNoPnpmNote(skipped) {
|
|
212
|
+
if (skipped !== "no-pnpm") return
|
|
213
|
+
console.log(
|
|
214
|
+
"\nNote: dependencies were not installed. This widget uses pnpm (its workflows install with it," +
|
|
215
|
+
"\nand only read pnpm-lock.yaml), and pnpm isn't installed. Install it (https://pnpm.io/installation)," +
|
|
216
|
+
"\nthen run `pnpm install` and commit pnpm-lock.yaml."
|
|
217
|
+
)
|
|
199
218
|
}
|
|
200
219
|
|
|
201
220
|
function printAgeExceptionsNote(ageExceptions) {
|
|
@@ -312,13 +331,14 @@ async function cmdCreate(argv) {
|
|
|
312
331
|
|
|
313
332
|
// Install first, so the first commit includes the lockfile (pinned, with
|
|
314
333
|
// integrity hashes; pnpm then freezes installs in CI by itself).
|
|
315
|
-
const { installed, ageExceptions } = installBeforeFirstCommit(target, args)
|
|
334
|
+
const { installed, skipped, ageExceptions } = installBeforeFirstCommit(target, args)
|
|
316
335
|
const committed = gitInitCommit(target)
|
|
317
336
|
|
|
318
337
|
printAgeExceptionsNote(ageExceptions)
|
|
338
|
+
printNoPnpmNote(skipped)
|
|
319
339
|
console.log(`\nCreated ${rawName}/${exampleId ? ` (starting from "${exampleId}")` : ""}\n`)
|
|
320
340
|
console.log(` cd ${rawName}`)
|
|
321
|
-
if (!installed) console.log(" pnpm install
|
|
341
|
+
if (!installed) console.log(" pnpm install")
|
|
322
342
|
console.log(" pnpm dev")
|
|
323
343
|
if (committed) {
|
|
324
344
|
console.log("\nGit is initialized on main (first commit done) with a dev branch ready.")
|
|
@@ -413,12 +433,13 @@ async function cmdInit(argv) {
|
|
|
413
433
|
// or a real git clone where .git already carries whatever history it has).
|
|
414
434
|
const gitAlreadyExists = existsSync(path.join(cwd, ".git"))
|
|
415
435
|
// Install first, so a first commit includes the lockfile (see cmdCreate).
|
|
416
|
-
const { installed, ageExceptions } = installBeforeFirstCommit(cwd, args)
|
|
436
|
+
const { installed, skipped, ageExceptions } = installBeforeFirstCommit(cwd, args)
|
|
417
437
|
const committed = gitAlreadyExists ? false : gitInitCommit(cwd)
|
|
418
438
|
|
|
419
439
|
printAgeExceptionsNote(ageExceptions)
|
|
440
|
+
printNoPnpmNote(skipped)
|
|
420
441
|
console.log(`\nInitialized ${rawName}/${exampleId ? ` (starting from "${exampleId}")` : ""} in the current directory.\n`)
|
|
421
|
-
if (!installed) console.log(" pnpm install
|
|
442
|
+
if (!installed) console.log(" pnpm install")
|
|
422
443
|
console.log(" pnpm dev")
|
|
423
444
|
if (gitAlreadyExists) {
|
|
424
445
|
console.log("\nExisting git repository detected — no commit was made automatically.")
|
package/package.json
CHANGED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Which package manager installs a new widget's dependencies.
|
|
2
|
+
//
|
|
3
|
+
// The scaffold is a pnpm project: its workflows install with pnpm, and
|
|
4
|
+
// pnpm-workspace.yaml holds settings only pnpm reads (build-script
|
|
5
|
+
// approvals, release-age exceptions). So the CLI installs with pnpm
|
|
6
|
+
// whichever package manager runs it -- `npm create grist-widget` runs it
|
|
7
|
+
// under npm. Installing with npm would commit a package-lock.json that the
|
|
8
|
+
// workflows never read, and skip pnpm's 24 h release-age check.
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @param {{ noInstall?: boolean, pm?: string | null, hasPnpm: () => boolean }} options
|
|
12
|
+
* `pm` is an explicit `--pm`; `hasPnpm` is only called when pnpm is the pick.
|
|
13
|
+
* @returns {{ manager: string } | { skip: "no-install" | "no-pnpm" }}
|
|
14
|
+
*/
|
|
15
|
+
export function installPlan({ noInstall, pm, hasPnpm }) {
|
|
16
|
+
if (noInstall) return { skip: "no-install" }
|
|
17
|
+
if (pm) return { manager: pm }
|
|
18
|
+
return hasPnpm() ? { manager: "pnpm" } : { skip: "no-pnpm" }
|
|
19
|
+
}
|
package/template/README.md
CHANGED
|
@@ -38,9 +38,11 @@ This is the **source** template. It is consumed two ways:
|
|
|
38
38
|
standalone `pnpm install` (pnpm 11) exits cleanly; inside the monorepo it's
|
|
39
39
|
ignored (the root workspace governs).
|
|
40
40
|
|
|
41
|
-
A scaffold's first commit includes `pnpm-lock.yaml
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
A scaffold's first commit includes `pnpm-lock.yaml`: the CLI installs with
|
|
42
|
+
pnpm, even under `npm create`, unless it was run with `--no-install` or
|
|
43
|
+
without pnpm installed. Versions are pinned with their integrity hashes, CI
|
|
44
|
+
installs exactly that, and Dependabot updates it. A `package-lock.json` isn't
|
|
45
|
+
used: the workflows install with pnpm. pnpm 11 won't install a version
|
|
44
46
|
published less than 24 hours ago. So a widget scaffolded on the day of an SDK
|
|
45
47
|
release also gets one `minimumReleaseAgeExclude` entry in
|
|
46
48
|
`pnpm-workspace.yaml`, for that SDK version only, written by pnpm itself.
|
package/template/package.json
CHANGED
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
// Compiler settings come from grist-widget-sdk and update with it. This
|
|
3
|
+
// file keeps what is specific to this widget: its files, paths and types.
|
|
4
|
+
"extends": "grist-widget-sdk/tsconfig/app.json",
|
|
2
5
|
"compilerOptions": {
|
|
6
|
+
// Also set in the base. Repeated so that a missing base (a grist-widget-sdk
|
|
7
|
+
// too old to ship it) fails the typecheck without writing .js files into src/.
|
|
8
|
+
"noEmit": true,
|
|
3
9
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
4
|
-
"target": "ES2022",
|
|
5
|
-
"useDefineForClassFields": true,
|
|
6
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
7
|
-
"module": "ESNext",
|
|
8
10
|
"types": ["vite/client"],
|
|
9
|
-
"skipLibCheck": true,
|
|
10
|
-
|
|
11
|
-
/* Bundler mode */
|
|
12
|
-
"moduleResolution": "bundler",
|
|
13
|
-
"allowImportingTsExtensions": true,
|
|
14
|
-
"verbatimModuleSyntax": true,
|
|
15
|
-
"moduleDetection": "force",
|
|
16
|
-
"noEmit": true,
|
|
17
|
-
"jsx": "react-jsx",
|
|
18
|
-
|
|
19
|
-
/* Linting */
|
|
20
|
-
"strict": true,
|
|
21
|
-
"noUnusedLocals": true,
|
|
22
|
-
"noUnusedParameters": true,
|
|
23
|
-
"erasableSyntaxOnly": true,
|
|
24
|
-
"noFallthroughCasesInSwitch": true,
|
|
25
|
-
"noUncheckedSideEffectImports": true,
|
|
26
11
|
"paths": {
|
|
27
12
|
"@/*": ["./src/*"]
|
|
28
13
|
}
|
|
@@ -1,26 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
+
// Compiler settings come from grist-widget-sdk and update with it. This
|
|
3
|
+
// file keeps what is specific to this widget: its files and types.
|
|
4
|
+
"extends": "grist-widget-sdk/tsconfig/node.json",
|
|
2
5
|
"compilerOptions": {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"lib": ["ES2023"],
|
|
6
|
-
"module": "ESNext",
|
|
7
|
-
"types": ["node"],
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
|
|
10
|
-
/* Bundler mode */
|
|
11
|
-
"moduleResolution": "bundler",
|
|
12
|
-
"allowImportingTsExtensions": true,
|
|
13
|
-
"verbatimModuleSyntax": true,
|
|
14
|
-
"moduleDetection": "force",
|
|
6
|
+
// Also set in the base. Repeated so that a missing base (a grist-widget-sdk
|
|
7
|
+
// too old to ship it) fails the typecheck without writing a .js file.
|
|
15
8
|
"noEmit": true,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
"strict": true,
|
|
19
|
-
"noUnusedLocals": true,
|
|
20
|
-
"noUnusedParameters": true,
|
|
21
|
-
"erasableSyntaxOnly": true,
|
|
22
|
-
"noFallthroughCasesInSwitch": true,
|
|
23
|
-
"noUncheckedSideEffectImports": true
|
|
9
|
+
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
10
|
+
"types": ["node"]
|
|
24
11
|
},
|
|
25
12
|
"include": ["vite.config.ts"]
|
|
26
13
|
}
|