@fougere/cli 0.7.0-alpha.0 → 0.8.1-alpha.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.
|
@@ -44,7 +44,9 @@ export default class NewCommand {
|
|
|
44
44
|
}
|
|
45
45
|
pw.createFlat(dir, name);
|
|
46
46
|
pw.addRootFrond(dir, template);
|
|
47
|
-
|
|
47
|
+
// `--local` points every dependency at this monorepo, so a version would be dead
|
|
48
|
+
// weight; without it, the version that wrote the files is the one they were written for.
|
|
49
|
+
if (raw.local) pw.linkLocal(dir); else pw.pinVersions(dir);
|
|
48
50
|
this.ui.info(`${template} at the root`);
|
|
49
51
|
this.ui.note([`cd ${name}`, INSTALL, `pnpm dev`].join('\n'), `${name} — one frond, at the root`);
|
|
50
52
|
this.ui.outro('Ready.');
|
|
@@ -54,7 +56,7 @@ export default class NewCommand {
|
|
|
54
56
|
pw.createWorkspace(dir, name);
|
|
55
57
|
|
|
56
58
|
if (raw.bare) {
|
|
57
|
-
if (raw.local) pw.linkLocal(dir);
|
|
59
|
+
if (raw.local) pw.linkLocal(dir); else pw.pinVersions(dir);
|
|
58
60
|
this.ui.note([`cd ${name}`, `fougere new # compose it (guided)`].join('\n'), `${name} — empty workspace`);
|
|
59
61
|
this.ui.outro('Ready.');
|
|
60
62
|
return;
|
|
@@ -72,7 +74,7 @@ export default class NewCommand {
|
|
|
72
74
|
|
|
73
75
|
// Every app depends on every frond — stated here, where both names are known.
|
|
74
76
|
pw.linkFronds(dir);
|
|
75
|
-
if (raw.local) pw.linkLocal(dir);
|
|
77
|
+
if (raw.local) pw.linkLocal(dir); else pw.pinVersions(dir);
|
|
76
78
|
this.ui.note([`cd ${name}`, INSTALL, `pnpm dev`].join('\n'), `${name} — ${fronds} frond(s), ${apps} app(s)`);
|
|
77
79
|
this.ui.outro('Ready.');
|
|
78
80
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { Shapes } from '@fougere/schema';
|
|
2
|
+
import safeRegex from 'safe-regex';
|
|
1
3
|
import {
|
|
2
4
|
buildGraph,
|
|
3
5
|
clusterEntities,
|
|
@@ -116,6 +118,34 @@ export default class CheckHandler {
|
|
|
116
118
|
}
|
|
117
119
|
}
|
|
118
120
|
|
|
121
|
+
/**
|
|
122
|
+
* A `pattern` that backtracks super-linearly. The pattern is fixed at declaration and
|
|
123
|
+
* the string tested against it is not: whoever calls the door chooses it, so the cost
|
|
124
|
+
* of one match is theirs to set. Judged HERE because a regex says the same thing on
|
|
125
|
+
* every run — checking it per row would pay for a verdict that never changes.
|
|
126
|
+
*
|
|
127
|
+
* `safe-regex` reads the star height, which is the common shape and not every one:
|
|
128
|
+
* `(a|a)*b` backtracks too and passes. Reported as a suspicion, never as a refusal.
|
|
129
|
+
*/
|
|
130
|
+
for (const frond of fronds) {
|
|
131
|
+
for (const entity of frond.entities) {
|
|
132
|
+
for (const [key, field] of Object.entries(entity.entityClass.getFields())) {
|
|
133
|
+
for (const pattern of Shapes.patterns(field.shape)) {
|
|
134
|
+
if (safeRegex(pattern)) continue;
|
|
135
|
+
findings.push({
|
|
136
|
+
severity: 'warning',
|
|
137
|
+
code: 'super-linear-pattern',
|
|
138
|
+
filePath: entity.filePath,
|
|
139
|
+
subject: `${entity.name}.${key}`,
|
|
140
|
+
message: `states \`pattern: ${JSON.stringify(pattern)}\`, whose nested repetition `
|
|
141
|
+
+ 'backtracks super-linearly. A value a caller chooses can hold the event loop '
|
|
142
|
+
+ 'for seconds — rewrite the repeated group, or bound the field with `max`.',
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
119
149
|
/**
|
|
120
150
|
* A warning, not a refusal: it resolves today and the app runs. What it costs is
|
|
121
151
|
* paid once, late — the day the frond it reaches into is deployed on its own.
|
|
@@ -39,7 +39,9 @@ export default class MigrateHandler {
|
|
|
39
39
|
if (steps.length === 0) return { chain: [], changes: [], refusals: [], ran: [] };
|
|
40
40
|
|
|
41
41
|
const config = await loadConfig(scan.root);
|
|
42
|
-
|
|
42
|
+
// `fougere migrate --root ../app` read that project's config and planned against a
|
|
43
|
+
// database beside the CLI's own directory — the loudest form of this bug.
|
|
44
|
+
const storage = resolveStorage(config.db ?? {}, undefined, scan.root);
|
|
43
45
|
if (!storage.db) {
|
|
44
46
|
return {
|
|
45
47
|
chain: versionsOf(steps),
|
|
@@ -30,6 +30,11 @@ function monorepoPackages(): string | undefined {
|
|
|
30
30
|
*/
|
|
31
31
|
const TEMPLATES = fileURLToPath(new URL('../../../templates/', import.meta.url));
|
|
32
32
|
|
|
33
|
+
/** The version that scaffolds is the version the templates were written for. */
|
|
34
|
+
const scaffoldVersion = (): string =>
|
|
35
|
+
(JSON.parse(readFileSync(fileURLToPath(new URL('../../../package.json', import.meta.url)), 'utf8')) as
|
|
36
|
+
{ version: string }).version;
|
|
37
|
+
|
|
33
38
|
// npm strips a literal .gitignore from published packages — it ships as
|
|
34
39
|
// _gitignore and the name is restored on copy.
|
|
35
40
|
function restoreGitignore(dir: string): void {
|
|
@@ -146,6 +151,44 @@ export default class ProjectWriter {
|
|
|
146
151
|
}
|
|
147
152
|
}
|
|
148
153
|
|
|
154
|
+
/**
|
|
155
|
+
* Every `@fougere/*` dependency, pinned to the version that scaffolded it.
|
|
156
|
+
*
|
|
157
|
+
* The templates say `latest`, which reads as "whatever is current" and is not: pnpm
|
|
158
|
+
* answers from a metadata cache, and a fresh project installed 0.6 while the registry
|
|
159
|
+
* said 0.7 — measured. A version is also what makes an install reproducible, which
|
|
160
|
+
* `latest` never was.
|
|
161
|
+
*
|
|
162
|
+
* Read off this CLI's own package, because the scaffold and the packages it names ship
|
|
163
|
+
* together: the version that wrote the file is the one it was written for.
|
|
164
|
+
*/
|
|
165
|
+
pinVersions(wsDir: string, conventions: Conventions = DEFAULT_CONVENTIONS): void {
|
|
166
|
+
const version = scaffoldVersion();
|
|
167
|
+
const manifests = [
|
|
168
|
+
join(wsDir, 'package.json'),
|
|
169
|
+
...['apps', conventions.fronds].flatMap((kind) => {
|
|
170
|
+
const dir = join(wsDir, kind);
|
|
171
|
+
if (!existsSync(dir)) return [];
|
|
172
|
+
|
|
173
|
+
return readdirSync(dir, { withFileTypes: true })
|
|
174
|
+
.filter((e) => e.isDirectory())
|
|
175
|
+
.map((e) => join(dir, e.name, 'package.json'));
|
|
176
|
+
}),
|
|
177
|
+
];
|
|
178
|
+
|
|
179
|
+
for (const path of manifests) {
|
|
180
|
+
if (!existsSync(path)) continue;
|
|
181
|
+
const pkg = JSON.parse(readFileSync(path, 'utf8')) as { dependencies?: Record<string, string> };
|
|
182
|
+
let moved = false;
|
|
183
|
+
for (const [name, range] of Object.entries(pkg.dependencies ?? {})) {
|
|
184
|
+
if (!name.startsWith('@fougere/') || range !== 'latest') continue;
|
|
185
|
+
pkg.dependencies![name] = version;
|
|
186
|
+
moved = true;
|
|
187
|
+
}
|
|
188
|
+
if (moved) writeFileSync(path, JSON.stringify(pkg, null, 2) + '\n');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
149
192
|
/**
|
|
150
193
|
* Dev mode: rewrite every `@fougere/*` dependency in the workspace to a
|
|
151
194
|
* `link:` into this monorepo, so `pnpm install` resolves offline (the
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// `safe-regex` ships no types, and `@types/safe-regex` describes its version 1 while the
|
|
2
|
+
// package here is version 2. The exported signature did not move between the two, so the
|
|
3
|
+
// shim states it rather than pinning a types package a major behind.
|
|
4
|
+
declare module 'safe-regex' {
|
|
5
|
+
export default function safeRegex(
|
|
6
|
+
pattern: string | RegExp,
|
|
7
|
+
options?: { limit?: number },
|
|
8
|
+
): boolean;
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fougere/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1-alpha.0",
|
|
4
4
|
"description": "The Fougere CLI — compose a workspace, serve a frond, call an operation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fougere",
|
|
@@ -41,12 +41,13 @@
|
|
|
41
41
|
"consola": "^3.4.2",
|
|
42
42
|
"jiti": "^2.4.2",
|
|
43
43
|
"picocolors": "^1.1.1",
|
|
44
|
-
"
|
|
45
|
-
"@fougere/
|
|
46
|
-
"@fougere/
|
|
47
|
-
"@fougere/
|
|
48
|
-
"@fougere/
|
|
49
|
-
"@fougere/
|
|
44
|
+
"safe-regex": "^2.1.1",
|
|
45
|
+
"@fougere/adapter-sql": "0.8.1-alpha.0",
|
|
46
|
+
"@fougere/defaults": "0.8.1-alpha.0",
|
|
47
|
+
"@fougere/core": "0.8.1-alpha.0",
|
|
48
|
+
"@fougere/transport-http": "0.8.1-alpha.0",
|
|
49
|
+
"@fougere/container": "0.8.1-alpha.0",
|
|
50
|
+
"@fougere/schema": "0.8.1-alpha.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"vitest": "^4.1.0"
|