@aventara/cli 1.0.0-rc.11 → 1.0.0-rc.13
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 +203 -0
- package/README.md +3 -3
- package/dist/apply/conflict.confirmer.js +4 -1
- package/dist/apply/lint-tooling.stripper.js +32 -0
- package/dist/apply/manifest.merger.js +65 -22
- package/dist/apply/pnpm-workspace.reader.js +179 -0
- package/dist/catalog/range.reader.js +0 -7
- package/dist/cli.js +16 -0
- package/dist/command/command.parser.js +7 -0
- package/dist/plan/nest-host.planner.js +2 -2
- package/dist/plan/next-host.planner.js +1 -0
- package/dist/plan/project.planner.js +53 -5
- package/dist/project/pnpm-workspace.locator.js +113 -0
- package/dist/project/project.inspector.js +7 -2
- package/dist/release/newest-release.reader.js +63 -0
- package/dist/release/npm-registry.resolver.js +120 -0
- package/dist/run/init.orchestrator.js +10 -1
- package/dist/run/new.orchestrator.js +51 -9
- package/dist/run/skill.orchestrator.js +18 -1
- package/dist/skill/agent-skill.generated.js +8 -6
- package/dist/skill/agent-skill.planner.js +14 -2
- package/dist/templates/prisma7/prisma7-nest.templates.js +3 -4
- package/dist/templates/prisma7/prisma7-next.templates.js +3 -4
- package/dist/templates/prisma7/prisma7-orm.templates.js +1 -1
- package/package.json +1 -1
|
@@ -16,6 +16,12 @@ const OPTIONS = [
|
|
|
16
16
|
commands: ["new"],
|
|
17
17
|
summary: "create no git repository (passed to nest new)",
|
|
18
18
|
},
|
|
19
|
+
{
|
|
20
|
+
flag: "--skip-lint",
|
|
21
|
+
key: "skipLint",
|
|
22
|
+
commands: ["new"],
|
|
23
|
+
summary: "leave out nest new's linter and formatter (oxlint, prettier, their files and scripts)",
|
|
24
|
+
},
|
|
19
25
|
{
|
|
20
26
|
flag: "--yes",
|
|
21
27
|
alias: "-y",
|
|
@@ -236,6 +242,7 @@ export function parseCliCommand(argv) {
|
|
|
236
242
|
given,
|
|
237
243
|
skipInstall: options.skipInstall === true,
|
|
238
244
|
skipGit: options.skipGit === true,
|
|
245
|
+
...(options.skipLint === true ? { skipLint: true } : {}),
|
|
239
246
|
yes: options.yes === true,
|
|
240
247
|
...(options.verbose === true ? { verbose: true } : {}),
|
|
241
248
|
...(host === undefined ? {} : { host }),
|
|
@@ -38,9 +38,8 @@ const E2E_SCRIPTS = {
|
|
|
38
38
|
};
|
|
39
39
|
const NEST = hostOf("nest");
|
|
40
40
|
export function frontendInitCommand(packageManager, cliVersion) {
|
|
41
|
-
const tag = /^\d+\.\d+\.\d+-([0-9A-Za-z-]+)/.exec(cliVersion)?.[1];
|
|
42
41
|
const runner = packageManager === "pnpm" ? "pnpm dlx" : "npx";
|
|
43
|
-
return `${runner} @aventara/client
|
|
42
|
+
return `${runner} @aventara/client@${cliVersion} init`;
|
|
44
43
|
}
|
|
45
44
|
export function planNestHost(builder, input) {
|
|
46
45
|
const { inspection, scaffold, packageManager } = input;
|
|
@@ -102,6 +101,7 @@ export function planNestHost(builder, input) {
|
|
|
102
101
|
`pre${script}`,
|
|
103
102
|
`${packageManager} run ${PREPARE_SCRIPT}`,
|
|
104
103
|
])),
|
|
104
|
+
typecheck: "tsc --noEmit",
|
|
105
105
|
rewrites: inspection.usesNestConfig
|
|
106
106
|
? {}
|
|
107
107
|
: {
|
|
@@ -122,6 +122,7 @@ export function planNextHost(builder, input) {
|
|
|
122
122
|
},
|
|
123
123
|
build: "aventara-build --client",
|
|
124
124
|
scripts: { prebuild: `${input.packageManager} run ${PREPARE_SCRIPT}` },
|
|
125
|
+
typecheck: "next typegen && tsc --noEmit",
|
|
125
126
|
rewrites: {},
|
|
126
127
|
gitignore: [
|
|
127
128
|
`/${layout.generated}/`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { appendEnvEntryIfAbsent, mergeEnvFile, mergeGitignore, mergePackageManifest, mergePnpmWorkspace, } from "../apply/manifest.merger.js";
|
|
1
|
+
import { allowBuildsEntries, allowBuildsGuidance, appendEnvEntryIfAbsent, mergeEnvFile, mergeGitignore, mergePackageManifest, mergePnpmWorkspace, } from "../apply/manifest.merger.js";
|
|
2
2
|
import { planAgentSkill, SKILL_DIRECTORY, } from "../skill/agent-skill.planner.js";
|
|
3
3
|
import { PREPARE_SCRIPT, } from "./host-plan.interface.js";
|
|
4
4
|
import { planNestHost } from "./nest-host.planner.js";
|
|
@@ -16,6 +16,25 @@ function writeOf(path, before, after) {
|
|
|
16
16
|
},
|
|
17
17
|
];
|
|
18
18
|
}
|
|
19
|
+
export function rootAllowBuilds(before, workspace, builds) {
|
|
20
|
+
if (!workspace.matched || builds.length === 0) {
|
|
21
|
+
return { kind: "none" };
|
|
22
|
+
}
|
|
23
|
+
const merged = mergePnpmWorkspace(before, builds);
|
|
24
|
+
if (merged.unsafe !== undefined) {
|
|
25
|
+
return {
|
|
26
|
+
kind: "print",
|
|
27
|
+
instruction: `The workspace root's ${workspace.file} was left as it is (${merged.unsafe}); add whichever of these are missing under your \`allowBuilds:\` (an explicit \`false\` is yours to keep):\n${allowBuildsEntries(builds)}`,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return merged.text === before
|
|
31
|
+
? { kind: "none" }
|
|
32
|
+
: {
|
|
33
|
+
kind: "edit",
|
|
34
|
+
text: merged.text,
|
|
35
|
+
conflict: `the workspace root's ${workspace.file} (its allowBuilds gains ${merged.added.join(", ")}, for their install scripts)`,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
19
38
|
export function planInit(input) {
|
|
20
39
|
const { inspection, hosted, packageManager } = input;
|
|
21
40
|
const { scaffold } = hosted;
|
|
@@ -93,10 +112,15 @@ export function planInit(input) {
|
|
|
93
112
|
postinstall: `${packageManager} run ${PREPARE_SCRIPT}`,
|
|
94
113
|
...host.scripts,
|
|
95
114
|
},
|
|
115
|
+
defaults: { typecheck: host.typecheck },
|
|
96
116
|
rewrites: host.rewrites,
|
|
97
117
|
}, replace));
|
|
98
118
|
const env = input.read(".env");
|
|
99
119
|
const { envEntry } = host;
|
|
120
|
+
const writesEnv = scaffoldWritesEnv ||
|
|
121
|
+
(envEntry !== undefined &&
|
|
122
|
+
env !== undefined &&
|
|
123
|
+
appendEnvEntryIfAbsent(env, envEntry.key, envEntry.value) !== env);
|
|
100
124
|
if (scaffoldWritesEnv || (envEntry !== undefined && env !== undefined)) {
|
|
101
125
|
builder.both(".env", env, (replace) => {
|
|
102
126
|
const merged = scaffoldWritesEnv
|
|
@@ -114,15 +138,39 @@ export function planInit(input) {
|
|
|
114
138
|
builder.both(".gitignore", gitignore, () => mergeGitignore(gitignore, [
|
|
115
139
|
...scaffold.gitignore,
|
|
116
140
|
...host.gitignore,
|
|
117
|
-
...(
|
|
141
|
+
...(writesEnv ? [".env"] : []),
|
|
118
142
|
]));
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
143
|
+
const { workspace } = input;
|
|
144
|
+
if (packageManager === "pnpm" && workspace !== undefined) {
|
|
145
|
+
const before = input.read(workspace.file);
|
|
146
|
+
const edit = rootAllowBuilds(before, workspace, scaffold.nativeBuilds);
|
|
147
|
+
if (edit.kind === "edit") {
|
|
148
|
+
builder.both(workspace.file, before, (replace) => ({
|
|
149
|
+
text: replace ? edit.text : (before ?? ""),
|
|
150
|
+
conflicts: [edit.conflict],
|
|
151
|
+
}));
|
|
152
|
+
}
|
|
153
|
+
else if (edit.kind === "print") {
|
|
154
|
+
builder.instruct(edit.instruction);
|
|
155
|
+
}
|
|
156
|
+
if (!workspace.matched) {
|
|
157
|
+
warnings.push(`${workspace.member} is not matched by the packages in ${workspace.file}, so pnpm install at the root does not install it; add it there`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else if (packageManager === "pnpm" && scaffold.nativeBuilds.length > 0) {
|
|
161
|
+
const workspaceFile = input.read("pnpm-workspace.yaml");
|
|
162
|
+
const merged = mergePnpmWorkspace(workspaceFile, scaffold.nativeBuilds);
|
|
163
|
+
if (merged.unsafe === undefined) {
|
|
164
|
+
builder.both("pnpm-workspace.yaml", workspaceFile, () => merged);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
builder.instruct(`pnpm-workspace.yaml was left as it is (${merged.unsafe}); ${allowBuildsGuidance(workspaceFile, scaffold.nativeBuilds)}`);
|
|
168
|
+
}
|
|
122
169
|
}
|
|
123
170
|
const skill = planAgentSkill({
|
|
124
171
|
read: input.read,
|
|
125
172
|
coreVersion: input.aventaraVersion,
|
|
173
|
+
packageManager,
|
|
126
174
|
force: false,
|
|
127
175
|
});
|
|
128
176
|
keeping.push(...skill.writes);
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
const WORKSPACE_FILE = "pnpm-workspace.yaml";
|
|
4
|
+
function packagesOf(text) {
|
|
5
|
+
const lines = text.split("\n");
|
|
6
|
+
const at = lines.findIndex((line) => /^packages:\s*(\[.*\])?\s*$/.test(line));
|
|
7
|
+
if (at === -1) {
|
|
8
|
+
return [];
|
|
9
|
+
}
|
|
10
|
+
const unquote = (value) => value.trim().replace(/^["']|["']$/g, "");
|
|
11
|
+
const flow = /^packages:\s*\[(.*)\]\s*$/.exec(lines[at])?.[1];
|
|
12
|
+
if (flow !== undefined) {
|
|
13
|
+
return flow
|
|
14
|
+
.split(",")
|
|
15
|
+
.map(unquote)
|
|
16
|
+
.filter((value) => value !== "");
|
|
17
|
+
}
|
|
18
|
+
const globs = [];
|
|
19
|
+
for (let line = at + 1; line < lines.length; line += 1) {
|
|
20
|
+
const item = /^\s+-\s+(.+?)\s*(#.*)?$/.exec(lines[line]);
|
|
21
|
+
if (item === null) {
|
|
22
|
+
if (/^\S/.test(lines[line])) {
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
globs.push(unquote(item[1]));
|
|
28
|
+
}
|
|
29
|
+
return globs;
|
|
30
|
+
}
|
|
31
|
+
function segmentPattern(part) {
|
|
32
|
+
let pattern = "";
|
|
33
|
+
for (let at = 0; at < part.length; at += 1) {
|
|
34
|
+
const char = part[at];
|
|
35
|
+
if (char === "*") {
|
|
36
|
+
pattern += "[^/]*";
|
|
37
|
+
}
|
|
38
|
+
else if (char === "?") {
|
|
39
|
+
pattern += "[^/]";
|
|
40
|
+
}
|
|
41
|
+
else if (char === "{") {
|
|
42
|
+
const close = part.indexOf("}", at);
|
|
43
|
+
if (close === -1) {
|
|
44
|
+
pattern += "\\{";
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
pattern += `(?:${part
|
|
48
|
+
.slice(at + 1, close)
|
|
49
|
+
.split(",")
|
|
50
|
+
.map(segmentPattern)
|
|
51
|
+
.join("|")})`;
|
|
52
|
+
at = close;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
pattern += char.replace(/[.+^$()|[\]\\]/g, "\\$&");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return pattern;
|
|
59
|
+
}
|
|
60
|
+
function globPattern(glob) {
|
|
61
|
+
const body = glob
|
|
62
|
+
.replace(/^\.\//, "")
|
|
63
|
+
.replace(/\/+$/, "")
|
|
64
|
+
.split("/")
|
|
65
|
+
.map((part) => (part === "**" ? "(?:.*)" : segmentPattern(part)))
|
|
66
|
+
.join("/");
|
|
67
|
+
return new RegExp(`^${body.replaceAll("/(?:.*)", "(?:/.*)?")}$`);
|
|
68
|
+
}
|
|
69
|
+
export function workspaceMatches(globs, member) {
|
|
70
|
+
let matched = false;
|
|
71
|
+
for (const glob of globs) {
|
|
72
|
+
if (glob.startsWith("!")) {
|
|
73
|
+
if (globPattern(glob.slice(1)).test(member)) {
|
|
74
|
+
matched = false;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
else if (globPattern(glob).test(member)) {
|
|
78
|
+
matched = true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return matched;
|
|
82
|
+
}
|
|
83
|
+
const LINT_CONFIG = /^(biome\.jsonc?|eslint\.config\.[cm]?[jt]s|\.eslintrc(\..+)?|\.prettierrc(\..+)?|prettier\.config\.[cm]?[jt]s)$/;
|
|
84
|
+
export function enclosingPnpmWorkspace(directory) {
|
|
85
|
+
const project = path.resolve(directory);
|
|
86
|
+
let root = project;
|
|
87
|
+
for (;;) {
|
|
88
|
+
const file = path.join(root, WORKSPACE_FILE);
|
|
89
|
+
if (existsSync(file)) {
|
|
90
|
+
if (root === project) {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
const member = path.relative(root, project).split(path.sep).join("/");
|
|
94
|
+
let entries = [];
|
|
95
|
+
try {
|
|
96
|
+
entries = readdirSync(root);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
root,
|
|
102
|
+
file: path.relative(project, file).split(path.sep).join("/"),
|
|
103
|
+
member,
|
|
104
|
+
matched: workspaceMatches(packagesOf(readFileSync(file, "utf8")), member),
|
|
105
|
+
lintConfigs: entries.filter((entry) => LINT_CONFIG.test(entry)).sort(),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (root === path.dirname(root)) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
root = path.dirname(root);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
@@ -13,7 +13,12 @@ const LOCKFILES = [
|
|
|
13
13
|
["package-lock.json", "npm"],
|
|
14
14
|
["pnpm-lock.yaml", "pnpm"],
|
|
15
15
|
["yarn.lock", "yarn"],
|
|
16
|
+
["bun.lock", "bun"],
|
|
17
|
+
["bun.lockb", "bun"],
|
|
16
18
|
];
|
|
19
|
+
export function lockfilesIn(directory) {
|
|
20
|
+
return LOCKFILES.filter(([file]) => existsSync(path.join(directory, file))).map(([, manager]) => manager);
|
|
21
|
+
}
|
|
17
22
|
function dependencyRange(manifest, name) {
|
|
18
23
|
return manifest.dependencies?.[name] ?? manifest.devDependencies?.[name];
|
|
19
24
|
}
|
|
@@ -82,8 +87,8 @@ export function inspectProject(directory, catalog) {
|
|
|
82
87
|
typescriptMajor >= FIRST_UNSUPPORTED_TYPESCRIPT) {
|
|
83
88
|
throw new AvProjectRefusedError(`this project declares typescript ${typescript}, and TypeScript ${FIRST_UNSUPPORTED_TYPESCRIPT} has no classic compiler API for Aventara's generate steps to read types with; use TypeScript 5.7 to 6`);
|
|
84
89
|
}
|
|
85
|
-
const lockfiles =
|
|
86
|
-
const known = lockfiles.filter((manager) => manager
|
|
90
|
+
const lockfiles = lockfilesIn(directory);
|
|
91
|
+
const known = lockfiles.filter((manager) => manager === "npm" || manager === "pnpm");
|
|
87
92
|
if (lockfiles.includes("yarn") && known.length === 0) {
|
|
88
93
|
throw new AvProjectRefusedError("this project has only a yarn.lock, and aventara init supports npm and pnpm");
|
|
89
94
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function channelOf(version) {
|
|
2
|
+
return /-rc\.\d+$/.test(version) ? "rc" : "latest";
|
|
3
|
+
}
|
|
4
|
+
const PACKAGE = "@aventara/cli";
|
|
5
|
+
const TIMEOUT_MS = 1500;
|
|
6
|
+
const DEFAULT_REGISTRY = "https://registry.npmjs.org/";
|
|
7
|
+
export function registryNewestRelease(registry) {
|
|
8
|
+
const base = registry === undefined || registry === "" ? DEFAULT_REGISTRY : registry;
|
|
9
|
+
return async (channel) => {
|
|
10
|
+
try {
|
|
11
|
+
const response = await fetch(new URL(`-/package/${PACKAGE.replace("/", "%2f")}/dist-tags`, base.endsWith("/") ? base : `${base}/`), { signal: AbortSignal.timeout(TIMEOUT_MS) });
|
|
12
|
+
if (!response.ok) {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
const tags = (await response.json());
|
|
16
|
+
const named = tags[channel];
|
|
17
|
+
return typeof named === "string" ? named : undefined;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function parse(version) {
|
|
25
|
+
const match = /^(\d+)\.(\d+)\.(\d+)(?:-rc\.(\d+))?$/.exec(version);
|
|
26
|
+
if (match === null) {
|
|
27
|
+
return undefined;
|
|
28
|
+
}
|
|
29
|
+
const [, major, minor, patch, rc] = match;
|
|
30
|
+
return {
|
|
31
|
+
core: [Number(major), Number(minor), Number(patch)],
|
|
32
|
+
...(rc === undefined ? {} : { rc: Number(rc) }),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
export function isNewer(candidate, pinned) {
|
|
36
|
+
const a = parse(candidate);
|
|
37
|
+
const b = parse(pinned);
|
|
38
|
+
if (a === undefined || b === undefined) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
for (let at = 0; at < 3; at += 1) {
|
|
42
|
+
const difference = a.core[at] - b.core[at];
|
|
43
|
+
if (difference !== 0) {
|
|
44
|
+
return difference > 0;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (a.rc === undefined || b.rc === undefined) {
|
|
48
|
+
return a.rc === undefined && b.rc !== undefined;
|
|
49
|
+
}
|
|
50
|
+
return a.rc > b.rc;
|
|
51
|
+
}
|
|
52
|
+
export async function releaseLines(input) {
|
|
53
|
+
const newest = input.read === undefined
|
|
54
|
+
? undefined
|
|
55
|
+
: await input.read(channelOf(input.pinned));
|
|
56
|
+
if (newest === undefined || !isNewer(newest, input.pinned)) {
|
|
57
|
+
return `Aventara ${input.pinned}\n`;
|
|
58
|
+
}
|
|
59
|
+
const take = input.packageManager === "pnpm"
|
|
60
|
+
? `pnpm --config.minimum-release-age=0 dlx ${PACKAGE}@${newest} ${input.command} …`
|
|
61
|
+
: `npx ${PACKAGE}@${newest} ${input.command} …, with @aventara/* excluded from npm's min-release-age`;
|
|
62
|
+
return `Aventara ${input.pinned} (newest: ${newest}, held back by a release-age delay or not yet in your registry; to take it: ${take})\n`;
|
|
63
|
+
}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { closeSync, constants, existsSync, fstatSync, openSync, readSync, } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
const SCOPE = "@aventara";
|
|
5
|
+
export function npmRegistryOf(input) {
|
|
6
|
+
const scopeKey = `${SCOPE}:registry`;
|
|
7
|
+
const layers = [
|
|
8
|
+
environmentLayer(input.env),
|
|
9
|
+
fileLayer(path.join(localPrefixOf(input.cwd), ".npmrc"), undefined),
|
|
10
|
+
fileLayer(nonEmpty(input.env.npm_config_userconfig) ??
|
|
11
|
+
nonEmpty(input.env.NPM_CONFIG_USERCONFIG) ??
|
|
12
|
+
path.join(input.home ?? homedir(), ".npmrc"), input.env),
|
|
13
|
+
];
|
|
14
|
+
for (const key of [scopeKey, "registry"]) {
|
|
15
|
+
for (const layer of layers) {
|
|
16
|
+
const value = layer.get(key);
|
|
17
|
+
if (value !== undefined) {
|
|
18
|
+
return value;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return undefined;
|
|
23
|
+
}
|
|
24
|
+
const KEYS = new Set([`${SCOPE}:registry`, "registry"]);
|
|
25
|
+
const MAX_NPMRC_BYTES = 64 * 1024;
|
|
26
|
+
function environmentLayer(env) {
|
|
27
|
+
const layer = new Map();
|
|
28
|
+
for (const key of KEYS) {
|
|
29
|
+
const value = nonEmpty(env[`npm_config_${key}`]) ??
|
|
30
|
+
nonEmpty(env[`NPM_CONFIG_${key.toUpperCase()}`]);
|
|
31
|
+
if (value === undefined) {
|
|
32
|
+
layer.delete(key);
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
layer.set(key, value);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return layer;
|
|
39
|
+
}
|
|
40
|
+
function fileLayer(file, env) {
|
|
41
|
+
const layer = new Map();
|
|
42
|
+
const text = regularFileText(file);
|
|
43
|
+
if (text === undefined) {
|
|
44
|
+
return layer;
|
|
45
|
+
}
|
|
46
|
+
for (const raw of text.split(/\r?\n/)) {
|
|
47
|
+
const line = raw.trim();
|
|
48
|
+
const equals = line.indexOf("=");
|
|
49
|
+
if (equals <= 0 || line.startsWith(";") || line.startsWith("#")) {
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const key = line.slice(0, equals).trim();
|
|
53
|
+
if (!KEYS.has(key)) {
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
const written = unquote(line.slice(equals + 1).trim());
|
|
57
|
+
const value = nonEmpty(env === undefined
|
|
58
|
+
? written.includes("${")
|
|
59
|
+
? undefined
|
|
60
|
+
: written
|
|
61
|
+
: expand(written, env));
|
|
62
|
+
if (value === undefined) {
|
|
63
|
+
layer.delete(key);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
layer.set(key, value);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return layer;
|
|
70
|
+
}
|
|
71
|
+
function regularFileText(file) {
|
|
72
|
+
let descriptor;
|
|
73
|
+
try {
|
|
74
|
+
descriptor = openSync(file, constants.O_RDONLY | (constants.O_NONBLOCK ?? 0));
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
try {
|
|
80
|
+
const stats = fstatSync(descriptor);
|
|
81
|
+
if (!stats.isFile() || stats.size > MAX_NPMRC_BYTES) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
const buffer = Buffer.alloc(MAX_NPMRC_BYTES + 1);
|
|
85
|
+
const read = readSync(descriptor, buffer, 0, buffer.length, 0);
|
|
86
|
+
return read > MAX_NPMRC_BYTES
|
|
87
|
+
? undefined
|
|
88
|
+
: buffer.toString("utf8", 0, read);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
closeSync(descriptor);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function localPrefixOf(cwd) {
|
|
98
|
+
for (let at = path.resolve(cwd);; at = path.dirname(at)) {
|
|
99
|
+
if (existsSync(path.join(at, "package.json")) ||
|
|
100
|
+
existsSync(path.join(at, "node_modules"))) {
|
|
101
|
+
return at;
|
|
102
|
+
}
|
|
103
|
+
if (path.dirname(at) === at) {
|
|
104
|
+
return path.resolve(cwd);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
function unquote(value) {
|
|
109
|
+
return value.length >= 2 &&
|
|
110
|
+
(value.startsWith('"') || value.startsWith("'")) &&
|
|
111
|
+
value.endsWith(value[0])
|
|
112
|
+
? value.slice(1, -1)
|
|
113
|
+
: value;
|
|
114
|
+
}
|
|
115
|
+
function expand(value, env) {
|
|
116
|
+
return value.replace(/\$\{([^}]+)\}/g, (_, name) => env[name] ?? "");
|
|
117
|
+
}
|
|
118
|
+
function nonEmpty(value) {
|
|
119
|
+
return value === undefined || value === "" ? undefined : value;
|
|
120
|
+
}
|
|
@@ -4,6 +4,7 @@ import { confirmConflicts } from "../apply/conflict.confirmer.js";
|
|
|
4
4
|
import { matchInstalledOrm, matchOrmSetup, } from "../catalog/catalog.matcher.js";
|
|
5
5
|
import { planInit } from "../plan/project.planner.js";
|
|
6
6
|
import { detectPackageManager } from "../project/package-manager.detector.js";
|
|
7
|
+
import { enclosingPnpmWorkspace } from "../project/pnpm-workspace.locator.js";
|
|
7
8
|
import { AvProjectRefusedError, inspectProject, } from "../project/project.inspector.js";
|
|
8
9
|
import { scanServices } from "../project/service.detector.js";
|
|
9
10
|
import { templatesFor } from "../templates/template.registry.js";
|
|
@@ -40,6 +41,9 @@ export async function initProject(command, io) {
|
|
|
40
41
|
? await prepareReuse(command, io, inspection, detectedPackageManager)
|
|
41
42
|
: await prepareFresh(command, io, inspection, detectedPackageManager);
|
|
42
43
|
const { entry, provider, hosted, packageManager } = prepared;
|
|
44
|
+
const workspace = packageManager === "pnpm"
|
|
45
|
+
? enclosingPnpmWorkspace(io.directory)
|
|
46
|
+
: undefined;
|
|
43
47
|
const plan = planInit({
|
|
44
48
|
inspection,
|
|
45
49
|
entry,
|
|
@@ -49,12 +53,17 @@ export async function initProject(command, io) {
|
|
|
49
53
|
skipInstall: command.skipInstall,
|
|
50
54
|
read,
|
|
51
55
|
warnings: prepared.warnings,
|
|
56
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
52
57
|
});
|
|
53
58
|
await confirmConflicts(plan.conflicts, {
|
|
54
59
|
yes: command.yes,
|
|
55
60
|
prompter: io.prompter,
|
|
61
|
+
command: "init",
|
|
56
62
|
});
|
|
57
63
|
const writes = plan.conflicts.length === 0 ? plan.keeping : plan.replacing;
|
|
64
|
+
if (io.release !== undefined) {
|
|
65
|
+
io.stdout(await io.release());
|
|
66
|
+
}
|
|
58
67
|
const progress = io.progress ??
|
|
59
68
|
createStepReporter({
|
|
60
69
|
write: io.stderr,
|
|
@@ -81,7 +90,7 @@ export async function initProject(command, io) {
|
|
|
81
90
|
if (!command.skipInstall) {
|
|
82
91
|
await progress.step(`Installing dependencies (${packageManager} install)`, async () => {
|
|
83
92
|
const installed = await io.runner(packageManager, ["install"], {
|
|
84
|
-
cwd: io.directory,
|
|
93
|
+
cwd: workspace?.root ?? io.directory,
|
|
85
94
|
onOutput: progress.output,
|
|
86
95
|
});
|
|
87
96
|
if (installed.code !== 0) {
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { confirmConflicts } from "../apply/conflict.confirmer.js";
|
|
5
|
+
import { stripLintTooling } from "../apply/lint-tooling.stripper.js";
|
|
4
6
|
import { DEFAULT_HOST, hostOf, } from "../catalog/host-entry.interface.js";
|
|
5
7
|
import { AvCliCommandError, } from "../command/command.parser.js";
|
|
8
|
+
import { rootAllowBuilds } from "../plan/project.planner.js";
|
|
6
9
|
import { detectPackageManager } from "../project/package-manager.detector.js";
|
|
10
|
+
import { enclosingPnpmWorkspace } from "../project/pnpm-workspace.locator.js";
|
|
7
11
|
import { AvProjectRefusedError } from "../project/project.inspector.js";
|
|
12
|
+
import { templatesFor, } from "../templates/template.registry.js";
|
|
8
13
|
import { resolveAnswers } from "../wizard/answer.resolver.js";
|
|
9
14
|
import { NEW_QUESTIONS, } from "../wizard/wizard.questions.js";
|
|
10
15
|
import { AvInstallFailedError, initProject, printNextSteps, } from "./init.orchestrator.js";
|
|
@@ -14,6 +19,7 @@ const NEST_CLI = "@nestjs/cli@12.0.8";
|
|
|
14
19
|
const NEST_APP_MODULE_SHA256 = "fe98c4cd2552342272494652287c02e4ef58db3072fcb18513ea831c60f85778";
|
|
15
20
|
const NEXT_VERSION_VARIABLE = "AVENTARA_NEXT_VERSION";
|
|
16
21
|
const NEXT_GENERATOR = "create-next-app";
|
|
22
|
+
const NEXT_MAJOR = "16";
|
|
17
23
|
const VERSION = /^[0-9A-Za-z][0-9A-Za-z.+-]*$/;
|
|
18
24
|
const NEXT_LAYOUT_FLAGS = [
|
|
19
25
|
"--ts",
|
|
@@ -58,14 +64,17 @@ const GENERATIONS = {
|
|
|
58
64
|
};
|
|
59
65
|
},
|
|
60
66
|
next: ({ command, io, name, packageManager }) => {
|
|
67
|
+
if (command.skipLint === true) {
|
|
68
|
+
throw new AvCliCommandError("--skip-lint takes out nest new's linter and formatter; create-next-app asks about its own linter, so answer it there or pass its flag after --");
|
|
69
|
+
}
|
|
61
70
|
const pinned = io.env[NEXT_VERSION_VARIABLE];
|
|
62
71
|
if (pinned !== undefined && pinned !== "" && !VERSION.test(pinned)) {
|
|
63
|
-
throw new AvCliCommandError(`${NEXT_VERSION_VARIABLE} is ${JSON.stringify(pinned)}, which is not a version of ${NEXT_GENERATOR}; set it to one such as 16.4.0, or unset it for the
|
|
72
|
+
throw new AvCliCommandError(`${NEXT_VERSION_VARIABLE} is ${JSON.stringify(pinned)}, which is not a version of ${NEXT_GENERATOR}; set it to one such as 16.4.0, or unset it for the newest ${NEXT_MAJOR}.x`);
|
|
64
73
|
}
|
|
65
74
|
if (!command.yes && !io.prompter.interactive) {
|
|
66
75
|
throw new AvCliCommandError(`${NEXT_GENERATOR} asks its own questions, and nobody can answer them here (stdin is not a terminal); pass --yes to take its defaults, and its own flags after --`);
|
|
67
76
|
}
|
|
68
|
-
const generator = `${NEXT_GENERATOR}@${pinned === undefined || pinned === "" ?
|
|
77
|
+
const generator = `${NEXT_GENERATOR}@${pinned === undefined || pinned === "" ? NEXT_MAJOR : pinned}`;
|
|
69
78
|
const [runner, prefix] = oneOff(packageManager);
|
|
70
79
|
return {
|
|
71
80
|
title: `Creating the Next.js project (${runner} ${[...prefix, generator, name].join(" ")})`,
|
|
@@ -86,6 +95,9 @@ const GENERATIONS = {
|
|
|
86
95
|
};
|
|
87
96
|
},
|
|
88
97
|
};
|
|
98
|
+
function withoutRelease({ release: _, ...io }) {
|
|
99
|
+
return io;
|
|
100
|
+
}
|
|
89
101
|
export async function runNew(command, io) {
|
|
90
102
|
const host = hostOf((command.host ?? DEFAULT_HOST));
|
|
91
103
|
const answers = await resolveAnswers(NEW_QUESTIONS, {
|
|
@@ -106,8 +118,13 @@ export async function runNew(command, io) {
|
|
|
106
118
|
packageManager === undefined) {
|
|
107
119
|
throw new Error("the new questions settled without every answer");
|
|
108
120
|
}
|
|
121
|
+
GENERATIONS[host.alias]({ command, io, name, packageManager });
|
|
122
|
+
const surroundings = command.skipGit
|
|
123
|
+
? undefined
|
|
124
|
+
: await gitSurroundings(io.runner, io.cwd);
|
|
125
|
+
const skipGit = command.skipGit || surroundings === "inside-work-tree";
|
|
109
126
|
const generation = GENERATIONS[host.alias]({
|
|
110
|
-
command,
|
|
127
|
+
command: { ...command, skipGit },
|
|
111
128
|
io,
|
|
112
129
|
name,
|
|
113
130
|
packageManager,
|
|
@@ -116,15 +133,34 @@ export async function runNew(command, io) {
|
|
|
116
133
|
if (existsSync(directory) && readdirSync(directory).length > 0) {
|
|
117
134
|
throw new AvProjectRefusedError(`aventara new creates ${directory}, which already exists and is not empty; choose another name, or run aventara init inside an existing project`);
|
|
118
135
|
}
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
|
|
136
|
+
const workspace = packageManager === "pnpm" ? enclosingPnpmWorkspace(directory) : undefined;
|
|
137
|
+
if (workspace !== undefined) {
|
|
138
|
+
const edit = rootAllowBuilds(readFileSync(path.join(workspace.root, "pnpm-workspace.yaml"), "utf8"), workspace, templatesFor(orm).providers[db]?.nativeBuilds ?? []);
|
|
139
|
+
if (edit.kind === "edit") {
|
|
140
|
+
await confirmConflicts([edit.conflict], {
|
|
141
|
+
yes: command.yes,
|
|
142
|
+
prompter: io.prompter,
|
|
143
|
+
command: "new",
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
if (io.release !== undefined) {
|
|
148
|
+
io.stdout(await io.release());
|
|
149
|
+
}
|
|
150
|
+
if (host.alias === "nest" &&
|
|
151
|
+
command.skipLint !== true &&
|
|
152
|
+
workspace !== undefined &&
|
|
153
|
+
workspace.lintConfigs.length > 0) {
|
|
154
|
+
io.stderr(`aventara: the workspace root has its own ${workspace.lintConfigs.join(", ")}; pass --skip-lint to leave out nest new's oxlint and prettier.\n`);
|
|
155
|
+
}
|
|
122
156
|
const progress = createStepReporter({
|
|
123
157
|
write: io.stderr,
|
|
124
158
|
mode: io.progressMode,
|
|
125
159
|
total: (generation.interactive ? 1 : 2) +
|
|
126
160
|
(command.skipInstall ? 0 : 1) +
|
|
127
|
-
(
|
|
161
|
+
(surroundings === undefined || surroundings === "inside-work-tree"
|
|
162
|
+
? 0
|
|
163
|
+
: 1),
|
|
128
164
|
verbose: command.verbose === true,
|
|
129
165
|
});
|
|
130
166
|
const generate = async () => {
|
|
@@ -148,6 +184,9 @@ export async function runNew(command, io) {
|
|
|
148
184
|
await progress.step(generation.title, generate);
|
|
149
185
|
}
|
|
150
186
|
generation.check(directory);
|
|
187
|
+
if (command.skipLint === true) {
|
|
188
|
+
stripLintTooling(directory);
|
|
189
|
+
}
|
|
151
190
|
const { nextSteps } = await initProject({
|
|
152
191
|
command: "init",
|
|
153
192
|
given: {
|
|
@@ -159,11 +198,14 @@ export async function runNew(command, io) {
|
|
|
159
198
|
skipGit: false,
|
|
160
199
|
yes: true,
|
|
161
200
|
}, {
|
|
162
|
-
...io,
|
|
201
|
+
...withoutRelease(io),
|
|
163
202
|
directory,
|
|
164
203
|
progress,
|
|
165
204
|
});
|
|
166
|
-
if (surroundings
|
|
205
|
+
if (surroundings === "inside-work-tree") {
|
|
206
|
+
io.stdout(`aventara: ${await commitScaffold(io.runner, directory, surroundings, io.cwd)}.\n`);
|
|
207
|
+
}
|
|
208
|
+
else if (surroundings !== undefined) {
|
|
167
209
|
const committed = await progress.step("Committing the scaffold", () => commitScaffold(io.runner, directory, surroundings, io.cwd));
|
|
168
210
|
io.stdout(`aventara: ${committed}.\n`);
|
|
169
211
|
}
|
|
@@ -1,9 +1,22 @@
|
|
|
1
|
-
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { detectPackageManager } from "../project/package-manager.detector.js";
|
|
4
|
+
import { lockfilesIn } from "../project/project.inspector.js";
|
|
3
5
|
import { planAgentSkill } from "../skill/agent-skill.planner.js";
|
|
4
6
|
export class AvSkillRefusedError extends Error {
|
|
5
7
|
name = "AvSkillRefusedError";
|
|
6
8
|
}
|
|
9
|
+
function lockfileManager(directory) {
|
|
10
|
+
for (let at = directory;; at = path.dirname(at)) {
|
|
11
|
+
const found = lockfilesIn(at);
|
|
12
|
+
if (found.length > 0) {
|
|
13
|
+
return found.find((manager) => manager === "npm" || manager === "pnpm");
|
|
14
|
+
}
|
|
15
|
+
if (path.dirname(at) === at || existsSync(path.join(at, ".git"))) {
|
|
16
|
+
return undefined;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
7
20
|
function readText(file) {
|
|
8
21
|
try {
|
|
9
22
|
return readFileSync(file, "utf8");
|
|
@@ -34,6 +47,10 @@ export function runSkill(command, io) {
|
|
|
34
47
|
const plan = planAgentSkill({
|
|
35
48
|
read: (file) => readText(path.join(io.directory, file)),
|
|
36
49
|
coreVersion,
|
|
50
|
+
packageManager: detectPackageManager({
|
|
51
|
+
lockfile: lockfileManager(io.directory),
|
|
52
|
+
userAgent: io.env?.npm_config_user_agent,
|
|
53
|
+
}),
|
|
37
54
|
...(errors === undefined ? {} : { errors }),
|
|
38
55
|
force: command.force,
|
|
39
56
|
});
|