@abcnews/aunty 17.0.0-next.4 → 17.0.0-next.5
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/CONTRIBUTING.md +6 -22
- package/README.md +23 -0
- package/TEMPLATES.md +112 -0
- package/eslint.config.mjs +6 -1
- package/package.json +25 -10
- package/src/bin/aunty.js +72 -0
- package/src/bin/commander.ts +174 -0
- package/src/commands/build/index.ts +23 -0
- package/src/commands/create/index.ts +148 -0
- package/src/commands/create/types.ts +7 -0
- package/src/commands/deploy/index.ts +40 -32
- package/src/commands/migrate/index.ts +320 -0
- package/src/commands/release/index.ts +127 -0
- package/src/commands/release-check/git.ts +14 -2
- package/src/commands/release-check/index.ts +139 -35
- package/src/commands/serve/index.ts +44 -0
- package/src/lib/colours.json +12 -0
- package/src/lib/constants.ts +3 -0
- package/src/{commands/deploy → lib}/ftp.ts +49 -23
- package/src/lib/git.ts +93 -0
- package/src/lib/initHelpers.ts +208 -0
- package/src/lib/semver.ts +50 -0
- package/src/lib/terminal.ts +85 -9
- package/src/lib/util.ts +66 -3
- package/src/lib/vite.ts +179 -0
- package/src/types.ts +16 -0
- package/templates/index.ts +11 -0
- package/templates/svelte/base/README.md +26 -0
- package/templates/svelte/base/contents/.editorconfig +12 -0
- package/templates/svelte/base/contents/.prettierrc +7 -0
- package/templates/svelte/base/contents/.vscode/extensions.json +3 -0
- package/templates/svelte/base/contents/LICENSE +21 -0
- package/templates/svelte/base/contents/README.md +16 -0
- package/templates/svelte/base/contents/_gitignore +24 -0
- package/templates/svelte/base/contents/index.html +22 -0
- package/templates/svelte/base/contents/package-lock.json +1373 -0
- package/templates/svelte/base/contents/package.json +29 -0
- package/templates/svelte/base/contents/src/App.svelte +21 -0
- package/templates/svelte/base/contents/src/assets/favicon.svg +4 -0
- package/templates/svelte/base/contents/src/components/Worm/Worm.svelte +59 -0
- package/templates/svelte/base/contents/src/components/Worm/worm.svg +4 -0
- package/templates/svelte/base/contents/src/iframe.ts +15 -0
- package/templates/svelte/base/contents/src/index.ts +26 -0
- package/templates/svelte/base/contents/svelte.config.js +2 -0
- package/templates/svelte/base/contents/tsconfig.app.json +22 -0
- package/templates/svelte/base/contents/tsconfig.json +7 -0
- package/templates/svelte/base/contents/tsconfig.node.json +28 -0
- package/templates/svelte/base/contents/vite-env.d.ts +7 -0
- package/templates/svelte/base/contents/vite.config.ts +40 -0
- package/templates/svelte/base/init.ts +39 -0
- package/templates/svelte/index.ts +52 -0
- package/templates/svelte/patch-builder/contents/builder/index.html +39 -0
- package/templates/svelte/patch-builder/contents/src/components/Builder/Builder.svelte +15 -0
- package/templates/svelte/patch-builder/init.ts +43 -0
- package/templates/svelte/patch-js/README.md +16 -0
- package/templates/svelte/patch-js/init.ts +61 -0
- package/templates/svelte/patch-odyssey/init.ts +10 -0
- package/templates/svelte/patch-scrollyteller/README.md +6 -0
- package/templates/svelte/patch-scrollyteller/contents/index.html +35 -0
- package/templates/svelte/patch-scrollyteller/contents/src/App.svelte +37 -0
- package/templates/svelte/patch-scrollyteller/contents/src/index.ts +36 -0
- package/templates/svelte/patch-scrollyteller/contents/src/types.ts +2 -0
- package/templates/svelte/patch-scrollyteller/init.ts +20 -0
- package/templates/vanilla/base/contents/LICENSE +21 -0
- package/templates/vanilla/base/contents/_gitignore +24 -0
- package/templates/vanilla/base/contents/index.html +22 -0
- package/templates/vanilla/base/contents/package-lock.json +999 -0
- package/templates/vanilla/base/contents/package.json +25 -0
- package/templates/vanilla/base/contents/public/favicon.svg +1 -0
- package/templates/vanilla/base/contents/public/icons.svg +24 -0
- package/templates/vanilla/base/contents/src/assets/hero.png +0 -0
- package/templates/vanilla/base/contents/src/assets/javascript.svg +1 -0
- package/templates/vanilla/base/contents/src/assets/vite.svg +1 -0
- package/templates/vanilla/base/contents/src/iframe.ts +17 -0
- package/templates/vanilla/base/contents/src/index.ts +17 -0
- package/templates/vanilla/base/contents/src/style.css +8 -0
- package/templates/vanilla/base/contents/tsconfig.app.json +23 -0
- package/templates/vanilla/base/contents/tsconfig.json +7 -0
- package/templates/vanilla/base/contents/tsconfig.node.json +28 -0
- package/templates/vanilla/base/contents/vite-env.d.ts +7 -0
- package/templates/vanilla/base/contents/vite.config.js +38 -0
- package/templates/vanilla/base/init.ts +33 -0
- package/templates/vanilla/index.ts +34 -0
- package/templates/vanilla/patch-js/README.md +0 -0
- package/templates/vanilla/patch-js/init.ts +27 -0
- package/test/helpers.ts +198 -0
- package/test/semver.test.ts +45 -0
- package/test/templates-e2e.test.ts +268 -0
- package/tsconfig.json +1 -0
- package/src/bin/aunty.ts +0 -60
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { intro, outro, confirm, log, cancel } from "@clack/prompts";
|
|
1
|
+
import { intro, outro, confirm, log, cancel, isCancel } from "@clack/prompts";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import pc from "picocolors";
|
|
4
|
-
import { FtpClient } from "
|
|
5
|
-
import {
|
|
4
|
+
import { FtpClient } from "../../lib/ftp.ts";
|
|
5
|
+
import { formatSize, findProjectDetails } from "../../lib/util.ts";
|
|
6
6
|
import { getHeader, spin } from "../../lib/terminal.ts";
|
|
7
|
+
import {
|
|
8
|
+
BUILD_DIRECTORY_NAME,
|
|
9
|
+
FTP_PROJECTS_PATH,
|
|
10
|
+
PUBLIC_PROJECTS_URL,
|
|
11
|
+
} from "../../lib/constants.ts";
|
|
7
12
|
import { getFileInventory } from "./fs.ts";
|
|
8
|
-
import { BUILD_DIRECTORY_NAME } from "../../lib/constants.ts";
|
|
9
13
|
import slugify from "slugify";
|
|
10
14
|
|
|
11
15
|
interface DeployOptions {
|
|
@@ -13,32 +17,29 @@ interface DeployOptions {
|
|
|
13
17
|
buildDir?: string;
|
|
14
18
|
dryRun?: boolean;
|
|
15
19
|
force?: boolean;
|
|
20
|
+
skipHeader?: boolean;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
23
|
/**
|
|
19
24
|
* The main entry point for the 'aunty deploy' command.
|
|
20
25
|
*/
|
|
21
26
|
export async function run(options: DeployOptions = {}): Promise<number> {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
if (!options.skipHeader) {
|
|
28
|
+
intro(
|
|
29
|
+
getHeader(
|
|
30
|
+
pc.dim("aunty"),
|
|
31
|
+
`deploy${options.dryRun ? ` ${pc.cyan("[dry]")}` : ""}`,
|
|
32
|
+
{ colour: "red" },
|
|
33
|
+
),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
30
36
|
|
|
31
37
|
// 1. Load config
|
|
32
|
-
const
|
|
33
|
-
name: string;
|
|
34
|
-
version: string;
|
|
35
|
-
} | null;
|
|
38
|
+
const details = await findProjectDetails(process.cwd());
|
|
36
39
|
|
|
37
|
-
if (!
|
|
38
|
-
log.error(`package.json not found in ${projectRoot}`);
|
|
39
|
-
return 1;
|
|
40
|
-
}
|
|
40
|
+
if (!details) return 1;
|
|
41
41
|
|
|
42
|
+
const { root: projectRoot, pkg: config } = details;
|
|
42
43
|
const { name, version } = config;
|
|
43
44
|
if (!name || !version) {
|
|
44
45
|
cancel("Missing name or version in package.json");
|
|
@@ -50,17 +51,19 @@ export async function run(options: DeployOptions = {}): Promise<number> {
|
|
|
50
51
|
projectRoot,
|
|
51
52
|
options.buildDir || BUILD_DIRECTORY_NAME,
|
|
52
53
|
);
|
|
53
|
-
const nameSlug = (slugify as
|
|
54
|
+
const nameSlug = (slugify as unknown as (s: string, o: object) => string)(
|
|
55
|
+
name,
|
|
56
|
+
{ strict: true },
|
|
57
|
+
);
|
|
54
58
|
const targetFolder = options.destDir || version;
|
|
55
|
-
const remoteDir =
|
|
56
|
-
const publicUrl =
|
|
57
|
-
log.info(`${pc.bold("Remote dir:")} ${pc.dim(remoteDir)}`);
|
|
59
|
+
const remoteDir = path.join(FTP_PROJECTS_PATH, nameSlug, targetFolder, "/");
|
|
60
|
+
const publicUrl = `${PUBLIC_PROJECTS_URL}${nameSlug}/${targetFolder}/`;
|
|
58
61
|
|
|
59
62
|
// 4. File Inventory & Size Check
|
|
60
63
|
let inventory;
|
|
61
64
|
try {
|
|
62
65
|
inventory = await getFileInventory(localDir);
|
|
63
|
-
} catch
|
|
66
|
+
} catch {
|
|
64
67
|
cancel(
|
|
65
68
|
`Build directory not found at ${pc.cyan(localDir)}. Have you run the build command?`,
|
|
66
69
|
);
|
|
@@ -73,7 +76,10 @@ export async function run(options: DeployOptions = {}): Promise<number> {
|
|
|
73
76
|
}
|
|
74
77
|
|
|
75
78
|
const list = inventory
|
|
76
|
-
.map(
|
|
79
|
+
.map(
|
|
80
|
+
(f: { relPath: string; size: number }) =>
|
|
81
|
+
` ${pc.dim(f.relPath)} (${formatSize(f.size)})`,
|
|
82
|
+
)
|
|
77
83
|
.join("\n");
|
|
78
84
|
log.step(`Found ${pc.bold(inventory.length)} files to deploy:\n${list}`);
|
|
79
85
|
|
|
@@ -85,9 +91,11 @@ export async function run(options: DeployOptions = {}): Promise<number> {
|
|
|
85
91
|
// 5. Credential Test & Confirmation
|
|
86
92
|
const ftpClient = new FtpClient();
|
|
87
93
|
try {
|
|
88
|
-
await ftpClient.
|
|
94
|
+
await ftpClient.connect();
|
|
89
95
|
} catch (err) {
|
|
90
|
-
|
|
96
|
+
log.error(
|
|
97
|
+
`FTP connection failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
98
|
+
);
|
|
91
99
|
return 1;
|
|
92
100
|
}
|
|
93
101
|
|
|
@@ -102,7 +110,7 @@ export async function run(options: DeployOptions = {}): Promise<number> {
|
|
|
102
110
|
initialValue: false,
|
|
103
111
|
});
|
|
104
112
|
|
|
105
|
-
if (!shouldOverwrite ||
|
|
113
|
+
if (!shouldOverwrite || isCancel(shouldOverwrite)) {
|
|
106
114
|
cancel("Deploy cancelled.");
|
|
107
115
|
return 0;
|
|
108
116
|
}
|
|
@@ -132,7 +140,8 @@ export async function run(options: DeployOptions = {}): Promise<number> {
|
|
|
132
140
|
uploadSpinner.message(`${countStr}/${totalFilesStr} ${info.name}`);
|
|
133
141
|
}
|
|
134
142
|
});
|
|
135
|
-
|
|
143
|
+
|
|
144
|
+
uploadSpinner.stop(`Upload finished`);
|
|
136
145
|
} catch (err) {
|
|
137
146
|
uploadSpinner.cancel("Upload failed");
|
|
138
147
|
ftpClient.close();
|
|
@@ -141,7 +150,6 @@ export async function run(options: DeployOptions = {}): Promise<number> {
|
|
|
141
150
|
|
|
142
151
|
ftpClient.close();
|
|
143
152
|
|
|
144
|
-
|
|
145
|
-
outro(pc.green("Deploy complete!"));
|
|
153
|
+
outro(`${pc.bold("Released to:")} ${pc.cyan(publicUrl)}`);
|
|
146
154
|
return 0;
|
|
147
155
|
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file
|
|
3
|
+
* A command to migrate an old Aunty Webpack project to Aunty Vite.
|
|
4
|
+
*
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { intro, outro, confirm, log, cancel, isCancel } from "@clack/prompts";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import fs from "node:fs/promises";
|
|
10
|
+
import pc from "picocolors";
|
|
11
|
+
import { $, glob } from "zx";
|
|
12
|
+
import { getHeader, spin } from "../../lib/terminal.ts";
|
|
13
|
+
import { findProjectDetails, loadJson } from "../../lib/util.ts";
|
|
14
|
+
import * as git from "../../lib/git.ts";
|
|
15
|
+
import { installAunty } from "../../lib/initHelpers.ts";
|
|
16
|
+
|
|
17
|
+
interface MigrateOptions {
|
|
18
|
+
yes?: boolean;
|
|
19
|
+
skipGit?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Migrates a legacy HTML file by removing legacy scripts and comments,
|
|
24
|
+
* injecting the new Vite entry script, writing it to the destination, and deleting the source.
|
|
25
|
+
*/
|
|
26
|
+
async function migrateHtmlFile(
|
|
27
|
+
srcPath: string,
|
|
28
|
+
destPath: string,
|
|
29
|
+
entryFile: string,
|
|
30
|
+
): Promise<void> {
|
|
31
|
+
let content = await fs.readFile(srcPath, "utf-8");
|
|
32
|
+
|
|
33
|
+
// Remove legacy script tags (e.g. referencing index.js, index_modern.js, etc.)
|
|
34
|
+
content = content.replace(
|
|
35
|
+
/<script\s+src=["'](?:index\.js|index_modern\.js)["']\s*><\/script>/gi,
|
|
36
|
+
"",
|
|
37
|
+
);
|
|
38
|
+
// Remove all HTML comments
|
|
39
|
+
content = content.replace(/<!--[\s\S]*?-->/g, "");
|
|
40
|
+
|
|
41
|
+
// Update inline script tags that dispatch 'odyssey:api' to be modules so they run after deferred module entry points.
|
|
42
|
+
content = content.replace(/<script([\s\S]*?)>([\s\S]*?)<\/script>/gi, (match, attributes, innerContent) => {
|
|
43
|
+
const hasOdysseyApi = innerContent.includes("odyssey:api");
|
|
44
|
+
const isClassicScript = !attributes.trim() || attributes.includes('type="text/javascript"') || attributes.includes("type='text/javascript'");
|
|
45
|
+
|
|
46
|
+
if (hasOdysseyApi && isClassicScript) {
|
|
47
|
+
return `<script type="module">${innerContent}</script>`;
|
|
48
|
+
}
|
|
49
|
+
return match;
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Inject the new Vite entry point
|
|
53
|
+
const scriptTag = `<script type="module" src="/${entryFile}"></script>`;
|
|
54
|
+
if (content.includes("</head>")) {
|
|
55
|
+
content = content.replace("</head>", ` ${scriptTag}\n </head>`);
|
|
56
|
+
} else {
|
|
57
|
+
content = scriptTag + "\n" + content;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
61
|
+
await fs.writeFile(destPath, content);
|
|
62
|
+
await fs.unlink(srcPath);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The main entry point for the 'aunty migrate' command.
|
|
67
|
+
*/
|
|
68
|
+
export async function run(options: MigrateOptions = {}): Promise<number> {
|
|
69
|
+
intro(getHeader(pc.dim("aunty"), "migrate", { colour: "rainbow" }));
|
|
70
|
+
|
|
71
|
+
// 1. Load Project Details & Sanity Check on package.json presence
|
|
72
|
+
const details = await findProjectDetails(process.cwd());
|
|
73
|
+
if (!details) {
|
|
74
|
+
cancel(
|
|
75
|
+
"Could not find package.json. Please run this command inside an aunty project.",
|
|
76
|
+
);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
const baseDir = details.root;
|
|
80
|
+
|
|
81
|
+
// Check for aunty.config.js in the root of the repo
|
|
82
|
+
const configPath = path.join(baseDir, "aunty.config.js");
|
|
83
|
+
const hasConfig = await fs.access(configPath).then(() => true).catch(() => false);
|
|
84
|
+
if (hasConfig) {
|
|
85
|
+
log.error(
|
|
86
|
+
`Found legacy ${pc.cyan("aunty.config.js")} at the root of the project. We cannot migrate this project automatically. Please delete or rename the file before continuing, and configure Vite manually after migration.`
|
|
87
|
+
);
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// 2. Git Safety Checks (bypassed if options.skipGit is true)
|
|
92
|
+
if (!options.skipGit) {
|
|
93
|
+
const gitAccessible = await git.isAccessible();
|
|
94
|
+
const insideRepo = gitAccessible ? await git.isInsideRepo() : false;
|
|
95
|
+
|
|
96
|
+
if (!gitAccessible) {
|
|
97
|
+
log.error(
|
|
98
|
+
"Git is not accessible (please ensure git is installed and Xcode/licenses are accepted).",
|
|
99
|
+
);
|
|
100
|
+
return 1;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (!insideRepo) {
|
|
104
|
+
log.error("The current directory is not a git repository.");
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Check for uncommitted changes
|
|
109
|
+
if (!(await git.isClean())) {
|
|
110
|
+
log.error(
|
|
111
|
+
"You have uncommitted changes. Please commit or stash them before migrating.",
|
|
112
|
+
);
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Check remote sync
|
|
117
|
+
if (await git.hasRemote()) {
|
|
118
|
+
if (await git.isBehindRemote()) {
|
|
119
|
+
log.error(
|
|
120
|
+
"Your local branch is behind the remote. Please pull the latest changes before migrating.",
|
|
121
|
+
);
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 3. Sanity Checks for index.html and index entrypoint
|
|
128
|
+
const publicHtmlPath = path.join(baseDir, "public/index.html");
|
|
129
|
+
try {
|
|
130
|
+
await fs.access(publicHtmlPath);
|
|
131
|
+
} catch {
|
|
132
|
+
log.error(
|
|
133
|
+
`Could not find public/index.html. This project does not seem to match the expected legacy structure.`,
|
|
134
|
+
);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const hasTS = await fs
|
|
139
|
+
.access(path.join(baseDir, "src/index.ts"))
|
|
140
|
+
.then(() => true)
|
|
141
|
+
.catch(() => false);
|
|
142
|
+
const hasJS = await fs
|
|
143
|
+
.access(path.join(baseDir, "src/index.js"))
|
|
144
|
+
.then(() => true)
|
|
145
|
+
.catch(() => false);
|
|
146
|
+
|
|
147
|
+
if (!hasTS && !hasJS) {
|
|
148
|
+
log.error("Could not find src/index.ts or src/index.js entry point.");
|
|
149
|
+
return 1;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const entryFile = hasTS ? "src/index.ts" : "src/index.js";
|
|
153
|
+
|
|
154
|
+
log.message("Attempt to migrate an Aunty Webpack project to Aunty Vite.");
|
|
155
|
+
log.message(
|
|
156
|
+
"This is a fairly naive and destructive process, and may not work for your specific configuration.",
|
|
157
|
+
);
|
|
158
|
+
log.message(
|
|
159
|
+
"Please make take a backup or ensure Git is pushed before proceeding.",
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
// 4. Initial Confirmation Prompt (bypassed if options.yes is true)
|
|
163
|
+
if (!options.yes) {
|
|
164
|
+
const shouldContinue = await confirm({
|
|
165
|
+
message: "Are you sure you want to migrate this project to Vite?",
|
|
166
|
+
initialValue: true,
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
if (!shouldContinue || isCancel(shouldContinue)) {
|
|
170
|
+
cancel("Migration cancelled.");
|
|
171
|
+
return 0;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 5. Migration Execution
|
|
176
|
+
const migrationSpinner = spin("Migrating configuration files...");
|
|
177
|
+
try {
|
|
178
|
+
const templateDir = path.resolve(
|
|
179
|
+
import.meta.dirname,
|
|
180
|
+
"../../../templates/svelte/base/contents",
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
// Copy Vite config files
|
|
184
|
+
const filesToCopy = [
|
|
185
|
+
"vite.config.ts",
|
|
186
|
+
"svelte.config.js",
|
|
187
|
+
"tsconfig.json",
|
|
188
|
+
"tsconfig.app.json",
|
|
189
|
+
"tsconfig.node.json",
|
|
190
|
+
];
|
|
191
|
+
|
|
192
|
+
for (const file of filesToCopy) {
|
|
193
|
+
const srcPath = path.join(templateDir, file);
|
|
194
|
+
const destPath = path.join(baseDir, file);
|
|
195
|
+
await fs.copyFile(srcPath, destPath);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Merge .gitignore
|
|
199
|
+
const templateGitignorePath = path.join(templateDir, "_gitignore");
|
|
200
|
+
const targetGitignorePath = path.join(baseDir, ".gitignore");
|
|
201
|
+
let existingGitignore = "";
|
|
202
|
+
try {
|
|
203
|
+
existingGitignore = await fs.readFile(targetGitignorePath, "utf-8");
|
|
204
|
+
} catch {}
|
|
205
|
+
const templateGitignore = await fs.readFile(templateGitignorePath, "utf-8");
|
|
206
|
+
|
|
207
|
+
const uniqueRules = Array.from(
|
|
208
|
+
new Set(
|
|
209
|
+
[
|
|
210
|
+
...existingGitignore.split(/\r?\n/),
|
|
211
|
+
...templateGitignore.split(/\r?\n/),
|
|
212
|
+
]
|
|
213
|
+
.map((line) => line.trim())
|
|
214
|
+
.filter((line) => line && !line.startsWith("#")),
|
|
215
|
+
),
|
|
216
|
+
);
|
|
217
|
+
await fs.writeFile(targetGitignorePath, uniqueRules.join("\n") + "\n");
|
|
218
|
+
|
|
219
|
+
// Move index.html
|
|
220
|
+
const destHtmlPath = path.join(baseDir, "index.html");
|
|
221
|
+
await migrateHtmlFile(publicHtmlPath, destHtmlPath, entryFile);
|
|
222
|
+
|
|
223
|
+
// Check for legacy builder in public/builder/index.html
|
|
224
|
+
const publicBuilderHtmlPath = path.join(baseDir, "public/builder/index.html");
|
|
225
|
+
const hasLegacyBuilder = await fs
|
|
226
|
+
.access(publicBuilderHtmlPath)
|
|
227
|
+
.then(() => true)
|
|
228
|
+
.catch(() => false);
|
|
229
|
+
|
|
230
|
+
if (hasLegacyBuilder) {
|
|
231
|
+
const destBuilderHtmlPath = path.join(baseDir, "builder/index.html");
|
|
232
|
+
await migrateHtmlFile(publicBuilderHtmlPath, destBuilderHtmlPath, entryFile);
|
|
233
|
+
|
|
234
|
+
// Clean up legacy builder directory inside public if it exists and is empty
|
|
235
|
+
try {
|
|
236
|
+
await fs.rmdir(path.join(baseDir, "public/builder"));
|
|
237
|
+
} catch {}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
migrationSpinner.stop(
|
|
241
|
+
"Configuration files updated and index.html migrated successfully" +
|
|
242
|
+
(hasLegacyBuilder ? " (including builder)" : ""),
|
|
243
|
+
);
|
|
244
|
+
} catch (err: any) {
|
|
245
|
+
migrationSpinner.cancel("Failed during file migration step");
|
|
246
|
+
log.error(err.message || String(err));
|
|
247
|
+
return 1;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// 6. Update package.json
|
|
251
|
+
const pkgSpinner = spin("Updating package.json dependencies and scripts...");
|
|
252
|
+
try {
|
|
253
|
+
const templateDir = path.resolve(
|
|
254
|
+
import.meta.dirname,
|
|
255
|
+
"../../../templates/svelte/base/contents",
|
|
256
|
+
);
|
|
257
|
+
const pkgPath = path.join(baseDir, "package.json");
|
|
258
|
+
const pkg = await loadJson<any>(pkgPath);
|
|
259
|
+
const templatePkg = await loadJson<any>(
|
|
260
|
+
path.join(templateDir, "package.json"),
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
pkg.type = "module";
|
|
264
|
+
|
|
265
|
+
// Update scripts
|
|
266
|
+
if (!pkg.scripts) pkg.scripts = {};
|
|
267
|
+
pkg.scripts.dev = "vite serve";
|
|
268
|
+
pkg.scripts.build = "vite build";
|
|
269
|
+
pkg.scripts.check =
|
|
270
|
+
"svelte-check --tsconfig ./tsconfig.app.json && tsc -p tsconfig.node.json";
|
|
271
|
+
pkg.scripts.format = "prettier --write .";
|
|
272
|
+
if (pkg.scripts.start === "aunty serve") {
|
|
273
|
+
delete pkg.scripts.start;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Merge dependencies & devDependencies
|
|
277
|
+
if (!pkg.dependencies) pkg.dependencies = {};
|
|
278
|
+
if (!pkg.devDependencies) pkg.devDependencies = {};
|
|
279
|
+
|
|
280
|
+
const oldDeps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
|
|
281
|
+
const hasSassDep = "sass" in oldDeps || "node-sass" in oldDeps;
|
|
282
|
+
const scssFiles = await glob("src/**/*.s[ac]ss", { cwd: baseDir });
|
|
283
|
+
const hasSass = hasSassDep || scssFiles.length > 0;
|
|
284
|
+
|
|
285
|
+
Object.assign(pkg.dependencies, templatePkg.dependencies || {});
|
|
286
|
+
Object.assign(pkg.devDependencies, templatePkg.devDependencies || {});
|
|
287
|
+
|
|
288
|
+
if (hasSass) {
|
|
289
|
+
pkg.devDependencies["sass-embedded"] = "^1.83.0";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// Save package.json
|
|
293
|
+
await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
294
|
+
|
|
295
|
+
// Set @abcnews/aunty to latest or local file
|
|
296
|
+
await installAunty(baseDir);
|
|
297
|
+
|
|
298
|
+
pkgSpinner.stop("package.json updated");
|
|
299
|
+
} catch (err: any) {
|
|
300
|
+
pkgSpinner.cancel("Failed to update package.json");
|
|
301
|
+
log.error(err.message || String(err));
|
|
302
|
+
return 1;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// 7. NPM Install
|
|
306
|
+
const installSpinner = spin("Installing dependencies via npm...");
|
|
307
|
+
try {
|
|
308
|
+
await $({ cwd: baseDir })`npm install`.quiet();
|
|
309
|
+
installSpinner.stop("Dependencies installed");
|
|
310
|
+
} catch (err: any) {
|
|
311
|
+
installSpinner.cancel("Failed to install dependencies.");
|
|
312
|
+
log.error(`npm install failed: ${err.message || String(err)}`);
|
|
313
|
+
return 1;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
outro(
|
|
317
|
+
"Migration completed successfully! You can now run 'npm run dev' to start development.",
|
|
318
|
+
);
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { intro, outro, select, cancel, isCancel, log } from "@clack/prompts";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import semver from "semver";
|
|
4
|
+
import { $ } from "zx";
|
|
5
|
+
import { getHeader, spin } from "../../lib/terminal.ts";
|
|
6
|
+
import { findProjectDetails } from "../../lib/util.ts";
|
|
7
|
+
import { getVersionOptions } from "../../lib/semver.ts";
|
|
8
|
+
import { run as runReleaseCheck } from "../release-check/index.ts";
|
|
9
|
+
import { run as runDeploy } from "../deploy/index.ts";
|
|
10
|
+
import * as git from "../../lib/git.ts";
|
|
11
|
+
|
|
12
|
+
interface ReleaseOptions {
|
|
13
|
+
version?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The main entry point for the 'aunty release' command.
|
|
18
|
+
*/
|
|
19
|
+
export async function run(options: ReleaseOptions = {}): Promise<number> {
|
|
20
|
+
intro(getHeader(pc.dim("aunty"), "release", { colour: "yellow" }));
|
|
21
|
+
|
|
22
|
+
// 1. Load project details
|
|
23
|
+
const details = await findProjectDetails(process.cwd());
|
|
24
|
+
if (!details) {
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const { pkg } = details;
|
|
29
|
+
const currentVersion = pkg.version;
|
|
30
|
+
|
|
31
|
+
if (!currentVersion) {
|
|
32
|
+
cancel("No version found in package.json, please set up your project.");
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!semver.valid(currentVersion)) {
|
|
37
|
+
cancel(
|
|
38
|
+
`Current version ${pc.bold(currentVersion)} is not a valid semver version`,
|
|
39
|
+
);
|
|
40
|
+
return 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
let nextVersion: string | undefined = undefined;
|
|
44
|
+
|
|
45
|
+
// 2. Handle override version option if provided
|
|
46
|
+
if (options.version) {
|
|
47
|
+
if (!semver.valid(options.version)) {
|
|
48
|
+
cancel(
|
|
49
|
+
`The version specified (${pc.bold(options.version)}) is not a valid semver version`,
|
|
50
|
+
);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
nextVersion = options.version;
|
|
54
|
+
log.info(`Using specified version: ${pc.green(nextVersion)}`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// 3. Determine version bump options dynamically
|
|
58
|
+
if (!nextVersion) {
|
|
59
|
+
const selectOptions = getVersionOptions(currentVersion);
|
|
60
|
+
|
|
61
|
+
const bumpSelection = await select({
|
|
62
|
+
message: "What package version bump is this release?",
|
|
63
|
+
options: selectOptions,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
if (isCancel(bumpSelection)) {
|
|
67
|
+
cancel("Release cancelled");
|
|
68
|
+
return 1;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
nextVersion = bumpSelection as string;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 4. Run pre-release checks
|
|
75
|
+
const checkResult = await runReleaseCheck({
|
|
76
|
+
skipHeader: true,
|
|
77
|
+
quiet: true,
|
|
78
|
+
projectName: details.pkg.name,
|
|
79
|
+
version: nextVersion,
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
if (checkResult !== 0) {
|
|
83
|
+
outro(pc.red("Release checks failed."));
|
|
84
|
+
return checkResult;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 5. Bump version locally
|
|
88
|
+
try {
|
|
89
|
+
await $`npm version ${nextVersion}`;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
outro(
|
|
92
|
+
pc.red(
|
|
93
|
+
`Failed to set version: ${err instanceof Error ? err.message : String(err)}`,
|
|
94
|
+
),
|
|
95
|
+
);
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 6. Push local changes and version tag to remote
|
|
100
|
+
const pushSpinner = spin("Pushing release & tag to remote...");
|
|
101
|
+
try {
|
|
102
|
+
await $`git push origin HEAD --follow-tags`.quiet();
|
|
103
|
+
pushSpinner.stop("Pushed to remote");
|
|
104
|
+
} catch (err) {
|
|
105
|
+
pushSpinner.cancel("Push failed");
|
|
106
|
+
outro(
|
|
107
|
+
pc.red(
|
|
108
|
+
`Failed to push to remote: ${err instanceof Error ? err.message : String(err)}`,
|
|
109
|
+
),
|
|
110
|
+
);
|
|
111
|
+
await git.rollback(nextVersion);
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 7. Deploy the release
|
|
116
|
+
const deployResult = await runDeploy({ skipHeader: true });
|
|
117
|
+
|
|
118
|
+
if (deployResult !== 0) {
|
|
119
|
+
outro(
|
|
120
|
+
pc.red(
|
|
121
|
+
"Deployment failed. You can retry manually with `npm run deploy`.",
|
|
122
|
+
),
|
|
123
|
+
);
|
|
124
|
+
return deployResult;
|
|
125
|
+
}
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
@@ -8,7 +8,19 @@ export async function isAccessible(): Promise<boolean> {
|
|
|
8
8
|
try {
|
|
9
9
|
await $`git --version`.quiet();
|
|
10
10
|
return true;
|
|
11
|
-
} catch
|
|
11
|
+
} catch {
|
|
12
|
+
return false;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Checks if the current directory is an initialized git repository.
|
|
18
|
+
*/
|
|
19
|
+
export async function isInsideRepo(): Promise<boolean> {
|
|
20
|
+
try {
|
|
21
|
+
await $`git rev-parse --is-inside-work-tree`.quiet();
|
|
22
|
+
return true;
|
|
23
|
+
} catch {
|
|
12
24
|
return false;
|
|
13
25
|
}
|
|
14
26
|
}
|
|
@@ -45,7 +57,7 @@ export async function hasRemote(): Promise<boolean> {
|
|
|
45
57
|
try {
|
|
46
58
|
await $`git rev-parse --abbrev-ref @{u}`.quiet();
|
|
47
59
|
return true;
|
|
48
|
-
} catch
|
|
60
|
+
} catch {
|
|
49
61
|
return false;
|
|
50
62
|
}
|
|
51
63
|
}
|